diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 4b5b456a93..d296a49ed4 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -3,6 +3,8 @@ "allow": [ "Bash(yarn nx g:*)", "Bash(npx vitest:*)", + "Bash(xargs:*)", + "Bash(dotnet build:*)", "Bash(git show:*)" ] } diff --git a/.github/scripts/CheckDocsSyntax/CheckDocsSyntax.csproj b/.github/scripts/CheckDocsSyntax/CheckDocsSyntax.csproj new file mode 100644 index 0000000000..b17b6c0369 --- /dev/null +++ b/.github/scripts/CheckDocsSyntax/CheckDocsSyntax.csproj @@ -0,0 +1,16 @@ + + + + Exe + net10.0 + enable + enable + Volo.Abp.Docs.SyntaxCheck + CheckDocsSyntax + + + + + + + diff --git a/.github/scripts/CheckDocsSyntax/Program.cs b/.github/scripts/CheckDocsSyntax/Program.cs new file mode 100644 index 0000000000..a57406f30c --- /dev/null +++ b/.github/scripts/CheckDocsSyntax/Program.cs @@ -0,0 +1,347 @@ +using System.Text.Json; +using Scriban; +using Scriban.Runtime; +using Scriban.Syntax; + +// Validates the Scriban template syntax embedded in `docs/en/` Markdown files. +// +// For each input file we run `Template.Parse` and a strict-mode render with the +// same parameter set the docs renderer injects at runtime (each docs-params.json +// `` and its `_Value` companion, plus Document_Language_Code, +// Document_Version and Release_Status). StrictVariables is enabled on purpose so +// references that would otherwise be silently rendered as empty strings surface +// as build failures here. +// +// Known limitations: +// - Partial template inlining is not executed: partial bodies are loaded from +// external storage at render time, so they cannot be resolved in CI. Files +// under `docs/en/` currently have no `//[doc-template]` references; if one is +// added later, errors inside the partial body must be reviewed manually. +// - Cookie- and query-string-driven parameter overrides are not injected, but +// their keys still resolve to empty strings because they layer on top of the +// same `` / `_Value` entries that are already injected. + +namespace Volo.Abp.Docs.SyntaxCheck; + +internal static class Program +{ + private const string DefaultDocsRoot = "docs/en"; + private const string DocsParamsFileName = "docs-params.json"; + + private static readonly string[] BuiltInVariables = + { + "Document_Language_Code", + "Document_Version", + "Release_Status" + }; + + public static int Main(string[] args) + { + var useGitHubAnnotations = Environment.GetEnvironmentVariable("GITHUB_ACTIONS") == "true"; + + var inputPaths = args.Length == 0 + ? new[] { DefaultDocsRoot } + : args; + + var files = new List(); + foreach (var path in inputPaths) + { + if (File.Exists(path)) + { + if (path.EndsWith(".md", StringComparison.OrdinalIgnoreCase)) + { + files.Add(Path.GetFullPath(path)); + } + } + else if (Directory.Exists(path)) + { + foreach (var file in Directory.EnumerateFiles(path, "*.md", SearchOption.AllDirectories)) + { + files.Add(Path.GetFullPath(file)); + } + } + else + { + Console.Error.WriteLine($"WARN: path does not exist: {path}"); + } + } + + if (files.Count == 0) + { + Console.WriteLine("No markdown files to check."); + return 0; + } + + Dictionary renderParameters; + try + { + renderParameters = BuildRenderParameters(files); + } + catch (Exception ex) + { + Console.Error.WriteLine($"ERROR: failed to load docs-params: {ex.Message}"); + if (useGitHubAnnotations) + { + Console.WriteLine($"::error::docs-params: {EscapeAnnotation(ex.Message)}"); + } + return 1; + } + + var errorCount = 0; + var warningCount = 0; + var fileIssueCount = 0; + var repoRoot = TryFindRepoRoot(Directory.GetCurrentDirectory()); + + foreach (var file in files) + { + var fileIssues = CheckFile(file, renderParameters); + if (fileIssues.Count == 0) + { + continue; + } + + fileIssueCount++; + + foreach (var issue in fileIssues) + { + if (issue.Severity == IssueSeverity.Error) + { + errorCount++; + } + else + { + warningCount++; + } + + var displayPath = repoRoot != null + ? Path.GetRelativePath(repoRoot, file) + : file; + + var severityLabel = issue.Severity == IssueSeverity.Error ? "error" : "warning"; + + Console.WriteLine( + $"{displayPath}:{issue.Line}:{issue.Column}: {severityLabel}: [{issue.Kind}] {issue.Message}"); + + if (useGitHubAnnotations) + { + var command = issue.Severity == IssueSeverity.Error ? "error" : "warning"; + Console.WriteLine( + $"::{command} file={displayPath},line={issue.Line},col={issue.Column}::" + + $"{issue.Kind}: {EscapeAnnotation(issue.Message)}"); + } + } + } + + Console.WriteLine(); + Console.WriteLine($"Checked {files.Count} markdown file(s). " + + $"{fileIssueCount} file(s) with issues, " + + $"{errorCount} error(s), {warningCount} warning(s)."); + + if (errorCount > 0 || warningCount > 0) + { + Console.WriteLine(); + Console.WriteLine("Tip: wrap inline Scriban-looking text with `{%{{{ ... }}}%}` " + + "or wrap whole code blocks with `{%{` ... `}%}` to escape Scriban parsing."); + } + + return errorCount > 0 ? 1 : 0; + } + + private static List CheckFile(string file, IReadOnlyDictionary renderParameters) + { + var issues = new List(); + string content; + try + { + content = File.ReadAllText(file); + } + catch (Exception ex) + { + issues.Add(new Issue("Read", 1, 1, ex.Message, IssueSeverity.Error)); + return issues; + } + + var template = Template.Parse(content, file); + + foreach (var message in template.Messages) + { + var severity = message.Type switch + { + Scriban.Parsing.ParserMessageType.Error => IssueSeverity.Error, + Scriban.Parsing.ParserMessageType.Warning => IssueSeverity.Warning, + _ => (IssueSeverity?)null + }; + + if (severity is null) + { + continue; + } + + var kind = severity == IssueSeverity.Error ? "ScribanParseError" : "ScribanParseWarning"; + + issues.Add(new Issue( + kind, + message.Span.Start.Line + 1, + message.Span.Start.Column + 1, + message.Message, + severity.Value)); + } + + if (template.HasErrors) + { + return issues; + } + + try + { + var context = new TemplateContext + { + StrictVariables = true + }; + + var scriptObject = new ScriptObject(); + foreach (var entry in renderParameters) + { + scriptObject[entry.Key] = entry.Value; + } + + context.PushGlobal(scriptObject); + template.Render(context); + } + catch (ScriptRuntimeException ex) + { + issues.Add(new Issue( + "ScribanRenderError", + ex.Span.Start.Line + 1, + ex.Span.Start.Column + 1, + ex.OriginalMessage, + IssueSeverity.Error)); + } + catch (Exception ex) + { + issues.Add(new Issue("ScribanRenderError", 1, 1, ex.Message, IssueSeverity.Error)); + } + + return issues; + } + + private static Dictionary BuildRenderParameters(IEnumerable files) + { + // Reproduces the keys the docs renderer places into its parameter + // dictionary before rendering a documentation page. + var parameters = new Dictionary(StringComparer.Ordinal); + + foreach (var name in BuiltInVariables) + { + parameters[name] = string.Empty; + } + + foreach (var paramName in DiscoverParameterNames(files)) + { + parameters[paramName] = string.Empty; + parameters[paramName + "_Value"] = string.Empty; + } + + return parameters; + } + + private static HashSet DiscoverParameterNames(IEnumerable files) + { + var names = new HashSet(StringComparer.Ordinal); + var visitedDirs = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var file in files) + { + var dir = Path.GetDirectoryName(file); + while (!string.IsNullOrEmpty(dir) && visitedDirs.Add(dir)) + { + var candidate = Path.Combine(dir, DocsParamsFileName); + if (File.Exists(candidate)) + { + AddNamesFromDocsParamsFile(candidate, names); + } + + var parent = Path.GetDirectoryName(dir); + if (string.IsNullOrEmpty(parent) || parent == dir) + { + break; + } + + dir = parent; + } + } + + return names; + } + + private static void AddNamesFromDocsParamsFile(string path, HashSet sink) + { + // A malformed docs-params.json would silently shrink the injected + // variable set and turn parameter file regressions into hard-to-debug + // "variable not found" errors on otherwise-fine markdown. Surface the + // failure directly so the contributor fixes the JSON instead. + using var doc = JsonDocument.Parse(File.ReadAllText(path)); + + if (!doc.RootElement.TryGetProperty("parameters", out var parameters) || + parameters.ValueKind != JsonValueKind.Array) + { + throw new InvalidDataException( + $"{path}: expected a top-level `parameters` array."); + } + + foreach (var parameter in parameters.EnumerateArray()) + { + if (parameter.TryGetProperty("name", out var nameElement) && + nameElement.ValueKind == JsonValueKind.String) + { + var name = nameElement.GetString(); + if (!string.IsNullOrWhiteSpace(name)) + { + sink.Add(name); + } + } + } + } + + private static string? TryFindRepoRoot(string startDir) + { + var current = new DirectoryInfo(startDir); + while (current != null) + { + var gitPath = Path.Combine(current.FullName, ".git"); + if (Directory.Exists(gitPath) || File.Exists(gitPath)) + { + return current.FullName; + } + + current = current.Parent; + } + + return null; + } + + private static string EscapeAnnotation(string text) + { + // GitHub workflow command escaping. `%` must be encoded first so the + // sequences we introduce below are not double-escaped. + return text + .Replace("%", "%25") + .Replace("\r", "%0D") + .Replace("\n", "%0A") + .Replace(":", "%3A") + .Replace(",", "%2C"); + } + + private enum IssueSeverity + { + Warning, + Error + } + + private readonly record struct Issue( + string Kind, + int Line, + int Column, + string Message, + IssueSeverity Severity); +} diff --git a/.github/workflows/angular.yml b/.github/workflows/angular.yml index cd48c0448a..841a0d944d 100644 --- a/.github/workflows/angular.yml +++ b/.github/workflows/angular.yml @@ -15,6 +15,11 @@ on: - synchronize - reopened - ready_for_review + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read diff --git a/.github/workflows/auto-pr.yml b/.github/workflows/auto-pr.yml index 6f36836d6c..90bd550d9d 100644 --- a/.github/workflows/auto-pr.yml +++ b/.github/workflows/auto-pr.yml @@ -1,13 +1,13 @@ -name: Merge branch dev with rel-10.3 +name: Merge branch dev with rel-10.4 on: push: branches: - - rel-10.3 + - rel-10.4 permissions: contents: read jobs: - merge-dev-with-rel-10-3: + merge-dev-with-rel-10-4: permissions: contents: write # for peter-evans/create-pull-request to create branch pull-requests: write # for peter-evans/create-pull-request to create a PR @@ -18,14 +18,14 @@ jobs: ref: dev - name: Reset promotion branch run: | - git fetch origin rel-10.3:rel-10.3 - git reset --hard rel-10.3 + git fetch origin rel-10.4:rel-10.4 + git reset --hard rel-10.4 - name: Create Pull Request uses: peter-evans/create-pull-request@v3 with: - branch: auto-merge/rel-10-3/${{github.run_number}} - title: Merge branch dev with rel-10.3 - body: This PR generated automatically to merge dev with rel-10.3. Please review the changed files before merging to prevent any errors that may occur. + branch: auto-merge/rel-10-4/${{github.run_number}} + title: Merge branch dev with rel-10.4 + body: This PR generated automatically to merge dev with rel-10.4. Please review the changed files before merging to prevent any errors that may occur. draft: true token: ${{ github.token }} - name: Merge Pull Request @@ -33,5 +33,5 @@ jobs: GH_TOKEN: ${{ secrets.BOT_SECRET }} run: | gh pr ready - gh pr review auto-merge/rel-10-3/${{github.run_number}} --approve - gh pr merge auto-merge/rel-10-3/${{github.run_number}} --merge --auto --delete-branch + gh pr review auto-merge/rel-10-4/${{github.run_number}} --approve + gh pr merge auto-merge/rel-10-4/${{github.run_number}} --merge --auto --delete-branch diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 86df2d8748..b1bc675156 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -21,6 +21,9 @@ on: - '.github/workflows/build-and-test.yml' pull_request: + branches: + - dev + - 'rel-*' paths: - 'framework/**/*.cs' - 'framework/**/*.cshtml' @@ -42,8 +45,14 @@ on: - synchronize - reopened - ready_for_review + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read + id-token: write jobs: build-test: @@ -51,14 +60,20 @@ jobs: timeout-minutes: 50 if: ${{ !github.event.pull_request.draft }} steps: - - uses: jlumbroso/free-disk-space@main - - uses: PSModule/install-powershell@v1 - with: - Version: latest - - uses: actions/checkout@v2 - - uses: actions/setup-dotnet@master + - uses: jlumbroso/free-disk-space@v1.3.1 + - name: Install PowerShell + run: sudo apt-get install -y powershell + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 with: dotnet-version: 10.0.x + - name: Cache NuGet packages + uses: actions/cache@v4 + with: + path: ~/.nuget/packages + key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', 'Directory.Packages.props') }} + restore-keys: | + ${{ runner.os }}-nuget- - name: Build All run: ./build-all.ps1 working-directory: ./build @@ -70,4 +85,8 @@ jobs: shell: pwsh - name: Codecov - uses: codecov/codecov-action@v2 + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} + uses: codecov/codecov-action@v5 + with: + use_oidc: true + fail_ci_if_error: true diff --git a/.github/workflows/cancel-workflow.yml b/.github/workflows/cancel-workflow.yml index 039943ad66..4d868f6b85 100644 --- a/.github/workflows/cancel-workflow.yml +++ b/.github/workflows/cancel-workflow.yml @@ -1,17 +1,21 @@ +# This workflow is intentionally disabled. +# The workflows that previously depended on this file now handle cancellation +# natively via per-workflow `concurrency` blocks. +# The styfle/cancel-workflow-action has been archived upstream and is no longer maintained. +# +# To re-enable manual cancellation, change `on: workflow_dispatch` back to `on: [push]` +# and restore the styfle step, but the native `concurrency` approach is preferred. name: cancel-workflow -on: [push] +on: + workflow_dispatch: permissions: contents: read jobs: cancel: - permissions: - actions: write # for styfle/cancel-workflow-action to cancel/stop running workflows - name: 'Cancel Previous Runs' + name: 'Disabled - See file header comment' runs-on: ubuntu-latest - timeout-minutes: 3 + timeout-minutes: 1 steps: - - uses: styfle/cancel-workflow-action@0.6.0 - with: - workflow_id: 10629,1299107,2792859,8268314 - access_token: ${{ github.token }} + - name: No-op + run: echo "Cancellation is handled via concurrency groups in each workflow." diff --git a/.github/workflows/check-docs-syntax.yml b/.github/workflows/check-docs-syntax.yml new file mode 100644 index 0000000000..3a3b272e94 --- /dev/null +++ b/.github/workflows/check-docs-syntax.yml @@ -0,0 +1,221 @@ +# Validates Scriban template syntax in PR-changed Markdown files under docs/en/, +# so escape issues are caught before they reach the published documentation. + +name: Check Docs Syntax + +on: + pull_request: + paths: + - 'docs/en/**/*.md' + - 'docs/en/docs-params.json' + - '.github/scripts/CheckDocsSyntax/**' + - '.github/workflows/check-docs-syntax.yml' + +permissions: + contents: read + pull-requests: write + +jobs: + check-scriban-syntax: + name: Validate Scriban syntax in docs/en + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + + - name: Build syntax checker + run: dotnet build .github/scripts/CheckDocsSyntax/CheckDocsSyntax.csproj -c Release --nologo -v minimal + + - name: Get changed markdown files + id: changed + uses: actions/github-script@v7 + with: + script: | + const prNumber = context.payload.pull_request.number; + const changed = []; + let paramsChanged = false; + let page = 1; + while (true) { + const { data: files } = await github.rest.pulls.listFiles({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + per_page: 100, + page, + }); + const PARAMS_PATH = 'docs/en/docs-params.json'; + for (const f of files) { + const isMutation = + f.status === 'added' || f.status === 'modified' || f.status === 'renamed'; + if (!isMutation) continue; + // For renames, GitHub puts the new path in `filename` and the + // old one in `previous_filename`. Detect docs-params.json on + // either side so renames into / out of that path still trigger + // the parameter-file validation path. + if (f.filename === PARAMS_PATH || f.previous_filename === PARAMS_PATH) { + paramsChanged = true; + } + if (f.filename.startsWith('docs/en/') && f.filename.endsWith('.md')) { + changed.push(f.filename); + } + } + if (files.length < 100) break; + page++; + } + core.setOutput('files', changed.join('\n')); + core.setOutput('count', changed.length.toString()); + core.setOutput('paramsChanged', paramsChanged ? 'true' : 'false'); + core.info(`Markdown files to check: ${changed.length}`); + core.info(`docs-params.json changed: ${paramsChanged}`); + for (const f of changed) { + core.info(` - ${f}`); + } + + - name: Run syntax checker + id: checker + if: steps.changed.outputs.count != '0' || steps.changed.outputs.paramsChanged == 'true' + env: + CHANGED_FILES: ${{ steps.changed.outputs.files }} + PARAMS_CHANGED: ${{ steps.changed.outputs.paramsChanged }} + run: | + mapfile -t files <<< "$CHANGED_FILES" + args=() + for f in "${files[@]}"; do + if [ -n "$f" ] && [ -f "$f" ]; then + args+=("$f") + fi + done + + if [ ${#args[@]} -eq 0 ]; then + if [ "$PARAMS_CHANGED" = "true" ] && [ -f "docs/en/index.md" ]; then + # No markdown changed, but docs-params.json did. Run the checker + # against a single known-clean page so BuildRenderParameters / + # docs-params.json parsing actually executes and fails fast on a + # malformed parameter file. + echo "docs-params.json changed but no markdown changed; validating params via docs/en/index.md." + args+=("docs/en/index.md") + else + echo "No existing markdown files to check (all changes are deletions)." + exit 0 + fi + fi + + # Capture the checker's stdout so a follow-up step can post it as a PR + # comment when the run fails, while still streaming it to the job log. + set -o pipefail + dotnet run --project .github/scripts/CheckDocsSyntax/CheckDocsSyntax.csproj \ + -c Release --no-build -- "${args[@]}" 2>&1 | tee checker-output.txt + + - name: Upsert PR comment on failure + if: failure() && steps.checker.conclusion == 'failure' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const MARKER = ''; + const prNumber = context.payload.pull_request.number; + + let report = ''; + try { + report = fs.readFileSync('checker-output.txt', 'utf8').trim(); + } catch (e) { + report = '(checker output was not captured)'; + } + + const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; + const body = [ + MARKER, + '### Docs syntax check failed', + '', + 'The Scriban syntax checker reported issues in the Markdown files this PR changes. Wrap inline Scriban-looking text with `{%{{{ ... }}}%}` or wrap whole code blocks with `{%{` ... `}%}` to keep it from being parsed as a template.', + '', + '
Checker output', + '', + '```', + report, + '```', + '', + '
', + '', + `[Full run log](${runUrl})`, + ].join('\n'); + + // Find an existing bot comment to update (idempotent across re-runs). + let existing = null; + for (let page = 1; ; page++) { + const { data } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + per_page: 100, + page, + }); + existing = data.find(c => c.body && c.body.startsWith(MARKER)); + if (existing || data.length < 100) break; + } + + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + core.info(`Updated existing bot comment (#${existing.id}).`); + } else { + const { data: created } = await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body, + }); + core.info(`Created bot comment (#${created.id}).`); + } + + - name: Resolve previous failure comment on success + # Clear any stale failure comment whenever this workflow run is green, + # even if the syntax checker step was skipped (e.g. when a later + # commit reverts the earlier failure so no markdown files appear in + # the PR's net diff). + if: success() + uses: actions/github-script@v7 + with: + script: | + const MARKER = ''; + const prNumber = context.payload.pull_request.number; + + for (let page = 1; ; page++) { + const { data } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + per_page: 100, + page, + }); + + const existing = data.find(c => c.body && c.body.startsWith(MARKER)); + if (existing) { + const body = [ + MARKER, + '### Docs syntax check passed', + '', + 'The previously reported issues are no longer present in this PR.', + ].join('\n'); + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + core.info(`Cleared bot comment (#${existing.id}).`); + break; + } + + if (data.length < 100) break; + } diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d1f6c0c503..16e0443f08 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -29,6 +29,10 @@ on: - reopened - ready_for_review +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read diff --git a/.github/workflows/image-compression.yml b/.github/workflows/image-compression.yml index 9eef6db59b..ed4111cdd3 100644 --- a/.github/workflows/image-compression.yml +++ b/.github/workflows/image-compression.yml @@ -12,6 +12,11 @@ on: - synchronize - reopened - ready_for_review + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: if: github.event.pull_request.head.repo.full_name == github.repository && !github.event.pull_request.draft @@ -23,5 +28,3 @@ jobs: - name: Compress Images uses: calibreapp/image-actions@main - with: - githubToken: ${{ secrets.GITHUB_TOKEN }} diff --git a/Directory.Packages.props b/Directory.Packages.props index 4b5cfa90f1..c3cf4389e5 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -23,6 +23,7 @@ + @@ -54,66 +55,66 @@ - + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - + + + - + - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + @@ -122,7 +123,7 @@ - + @@ -132,11 +133,11 @@ - - - - - + + + + + @@ -169,17 +170,17 @@ - + - - + + - - - + + + @@ -194,6 +195,6 @@ - + \ No newline at end of file diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index bb6c1aeef4..61cf14b67c 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -348,15 +348,58 @@ "CompanySize": "Company size", "DetailTrialLicense": "Details", "Requested": "Requested", + "Pending": "Pending", + "Running": "Running", "Activated": "Activated", "PurchasedToNormalLicense": "Purchased", "Expired": "Expired", "TrialLicenseDeletionWarningMessage": "Are you sure you want to delete the trial license? Trial license, organization, support accounts will be deleted!", "LicenseCategoryFilter": "License category", "Permission:SendWelcomeEmail": "Send Welcome Email", + "Permission:ProvisionExistingOrganizationsAi": "Provision Existing Organizations AI", + "Permission:AiProviderKeyLimit": "Manage AI Provider Key Limit", "SendWelcomeEmail": "Send Welcome Email", "SendWelcomeEmailWarningMessage": "Are you sure you want to send welcome email to the organization members?", "SendWelcomeEmailSuccessMessage": "Welcome email sent successfully!", + "ProvisionExistingOrganizationsAi": "Provision Existing Organizations AI", + "ProvisionExistingOrganizationsAiConfirmation": "This will enable AI assisted development for all active organizations, grant included AI credits, and provision provider keys in the background. Do you want to continue?", + "DeleteExistingOrganizationsAiCredentials": "Delete Existing Organizations AI Keys", + "DeleteExistingOrganizationsAiCredentialsConfirmation": "This will revoke existing OpenRouter keys referenced by organizations and remove stored AI credentials from the database so provisioning can be retried. Do you want to continue?", + "ExistingOrganizationsAiOperationAlreadyRunning": "Another existing organizations AI operation is already running.", + "ExistingOrganizationsAiBackfillAlreadyRunning": "An existing organizations AI provisioning job is already running.", + "ExistingOrganizationsAiBackfillMissingManagementApiKey": "OpenRouter management API key is not configured for the admin application. Configure AiAssistedDevelopment:Providers:OpenRouter:ManagementApiKey before starting this operation.", + "NoActiveOrganizationsFoundForAiBackfill": "No active organizations were found for AI provisioning.", + "NoOrganizationsFoundForAiCredentialCleanup": "No organizations with AI credentials were found for cleanup.", + "ExistingOrganizationsAiBackfillNotFound": "The existing organizations AI provisioning operation was not found.", + "ExistingOrganizationsAiBackfillCompleted": "Existing organizations AI provisioning completed successfully.", + "ExistingOrganizationsAiBackfillFailed": "Existing organizations AI provisioning failed.", + "ExistingOrganizationsAiCredentialCleanupCompleted": "Existing organizations AI credential cleanup completed successfully.", + "ExistingOrganizationsAiCredentialCleanupFailed": "Existing organizations AI credential cleanup failed.", + "CurrentOrganization": "Current organization", + "AiProviderKeyLimit": "ABP AI Agent Provider Key Limit", + "AiProviderKeyMissing": "No provider key exists for this organization.", + "AiProviderKeyMissingDescription": "Create a provider key before setting the ABP AI Agent usable limit.", + "CreateAiProviderKey": "Create provider key", + "ProviderUsableLimit": "Provider usable limit", + "CustomerVisibleCredits": "Customer visible credits", + "ProviderRemaining": "Provider remaining", + "ProviderUsed": "Provider used", + "GrossToUsableRatio": "Gross to usable ratio", + "LastSync": "Last sync", + "NeverSynced": "Never synced", + "NotSynced": "Not synced", + "NewProviderUsableLimit": "New provider usable limit", + "NewProviderUsableLimitDescription": "This is the absolute OpenRouter usable USD limit. Customer-visible credits are calculated from the configured gross-to-usable ratio.", + "CustomerVisibleGrossPreview": "Customer visible gross preview", + "AiProviderLimitMustBeNonNegative": "AI provider limit must be greater than or equal to 0.", + "AiProviderLimitCannotBeLowerThanUsage": "AI provider limit cannot be lower than current provider usage.", + "AiProviderKeyLimitUpdateConfirmation": "Set OpenRouter usable limit to ${0}? Customer-visible credits will become ${1}.", + "Processed": "Processed", + "Succeeded": "Succeeded", + "Failed": "Failed", + "Cancelled": "Cancelled", + "CompletedAt": "Completed at", + "LastError": "Last error", "Activate": "Activate", "ActivateTrialLicenseWarningMessage": " When you activate a trial license, a welcome e-mail will be sent to the user. Do you want to activate it?", "ActivateTrialLicenseSuccessMessage": "Activated successfully and the welcome e-mail sent to the organization members.", diff --git a/common.props b/common.props index f952304d6f..368b7f88a8 100644 --- a/common.props +++ b/common.props @@ -1,8 +1,8 @@ latest - 10.4.0-preview - 5.4.0-preview + 10.5.0-preview + 5.5.0-preview $(NoWarn);CS1591;CS0436 https://abp.io/assets/abp_nupkg.png https://abp.io/ diff --git a/docs/en/Blog-Posts/2026-04-15 v10_3_Release_Stable/POST.md b/docs/en/Blog-Posts/2026-04-15 v10_3_Release_Stable/POST.md new file mode 100644 index 0000000000..ba905ce330 --- /dev/null +++ b/docs/en/Blog-Posts/2026-04-15 v10_3_Release_Stable/POST.md @@ -0,0 +1,72 @@ +# ABP.IO Platform 10.3 Final Has Been Released! + +We are glad to announce that [ABP](https://abp.io/) 10.3 stable version has been released. + +## What's New With Version 10.3? + +All the new features were explained in detail in the [10.3 RC Announcement Post](https://abp.io/community/announcements/announcing-abp-10-3-release-candidate-hgnpr9jq), so there is no need to review them again. You can check it out for more details. + +## Getting Started with 10.3 + +### How to Upgrade an Existing Solution + +You can upgrade your existing solutions with either ABP Studio or ABP CLI. In the following sections, both approaches are explained: + +### Upgrading via ABP Studio + +If you are already using the ABP Studio, you can upgrade it to the latest version. ABP Studio periodically checks for updates in the background, and when a new version of ABP Studio is available, you will be notified through a modal. Then, you can update it by confirming the opened modal. See [the documentation](https://abp.io/docs/latest/studio/installation#upgrading) for more info. + +After upgrading the ABP Studio, then you can open your solution in the application, and simply click the **Upgrade ABP Packages** action button to instantly upgrade your solution: + +![](upgrade-abp-packages.png) + +### Upgrading via ABP CLI + +Alternatively, you can upgrade your existing solution via ABP CLI. First, you need to install the ABP CLI or upgrade it to the latest version. + +If you haven't installed it yet, you can run the following command: + +```bash +dotnet tool install -g Volo.Abp.Studio.Cli +``` + +Or to update the existing CLI, you can run the following command: + +```bash +dotnet tool update -g Volo.Abp.Studio.Cli +``` + +After installing/updating the ABP CLI, you can use the [`update` command](https://abp.io/docs/latest/CLI#update) to update all the ABP related NuGet and NPM packages in your solution as follows: + +```bash +abp update +``` + +You can run this command in the root folder of your solution to update all ABP related packages. + +## Migration Guides + +There are some important changes in this version that may affect your application. Please read the migration guide carefully, if you are upgrading from v10.2 or earlier versions: [ABP Version 10.3 Migration Guide](https://abp.io/docs/10.3/release-info/migration-guides/abp-10-3) + +## Community News + +### New ABP Community Articles + +As always, exciting articles have been contributed by the ABP community. I will highlight some of them here: + +- [Liming Ma](https://abp.io/community/members/maliming) has published 6 new posts: + - [Dynamic Events in ABP](https://abp.io/community/articles/dynamic-events-in-abp-dukq95m1) + - [Dynamic Background Jobs and Workers in ABP](https://abp.io/community/articles/dynamic-background-jobs-and-workers-in-abp-wfdkdsq9) + - [Shared User Accounts in ABP Multi-Tenancy](https://abp.io/community/articles/shared-user-accounts-in-abp-multitenancy-mf3bkg79) + - [Secure Client Authentication with private_key_jwt in ABP 10.3](https://abp.io/community/articles/secure-client-authentication-with-privatekeyjwt-in-abp-b2rf18bc) + - [Operation Rate Limiting in ABP Framework](https://abp.io/community/articles/operation-rate-limiting-in-abp-framework-f4jtd6sn) + - [Resource-Based Authorization in ABP Framework](https://abp.io/community/articles/resourcebased-authorization-in-abp-framework-choku1sn) +- [One Endpoint, Many AI Clients: Turning ABP Workspaces into OpenAI-Compatible Models](https://abp.io/community/articles/turning-abp-workspaces-into-openai-compatible-endpoints-u3ls1gp4) by [Engincan Veske](https://abp.io/community/members/EngincanV) +- [Automatically Validate Your Documentation: How We Built a Tutorial Validator](https://abp.io/community/articles/automatically-validate-your-documentation-m3ozgkhv) by [Mansur Besleney](https://abp.io/community/members/mansur.besleney) +- [Automate Localhost Access for Expo: A Guide to Dynamic Cloudflare Tunnels & Dev Builds](https://abp.io/community/articles/automate-localhost-access-for-expo-a-guide-to-dynamic-7cblqtj3) by [Sumeyye Kurtulus](https://abp.io/community/members/sumeyye.kurtulus) + +Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://abp.io/community/posts/create) to the ABP Community. + +## About the Next Version + +The next feature version will be 10.4. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version. diff --git a/docs/en/Blog-Posts/2026-04-15 v10_3_Release_Stable/cover-image.png b/docs/en/Blog-Posts/2026-04-15 v10_3_Release_Stable/cover-image.png new file mode 100644 index 0000000000..bf1112ca48 Binary files /dev/null and b/docs/en/Blog-Posts/2026-04-15 v10_3_Release_Stable/cover-image.png differ diff --git a/docs/en/Blog-Posts/2026-04-15 v10_3_Release_Stable/upgrade-abp-packages.png b/docs/en/Blog-Posts/2026-04-15 v10_3_Release_Stable/upgrade-abp-packages.png new file mode 100644 index 0000000000..4ec1d19589 Binary files /dev/null and b/docs/en/Blog-Posts/2026-04-15 v10_3_Release_Stable/upgrade-abp-packages.png differ diff --git a/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/POST.md b/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/POST.md new file mode 100644 index 0000000000..95a83adec6 --- /dev/null +++ b/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/POST.md @@ -0,0 +1,214 @@ +# ABP Platform 10.4 RC Has Been Released + +We are happy to release [ABP](https://abp.io) version **10.4 RC** (Release Candidate). This blog post introduces the new features and important changes in this new version. + +Try this version and provide feedback for a more stable version of ABP v10.4! Thanks to you in advance. + +## Get Started with the 10.4 RC + +You can check the [Get Started page](https://abp.io/get-started) to see how to get started with ABP. You can either download [ABP Studio](https://abp.io/get-started#abp-studio-tab) (**recommended**, if you prefer a user-friendly GUI application - desktop application) or use the [ABP CLI](https://abp.io/docs/latest/cli). + +> The v10.4 RC versions of ABP Studio and the ABP CLI are still being tested and will be released shortly. + +By default, ABP Studio uses stable versions to create solutions. Therefore, if you want to create a solution with a preview version, first you need to create a solution and then switch your solution to the preview version from the ABP Studio UI: + +![studio-switch-to-preview](studio-switch-to-preview.png) + +## Migration Guide + +There are no explicitly marked breaking changes in this version. However, there are still some important migration notes for specific scenarios. Please check the migration guide if you are upgrading from v10.3 or earlier: [ABP Version 10.4 Migration Guide](https://abp.io/docs/10.4/release-info/migration-guides/abp-10-4). + +## What's New with ABP v10.4? + +In this section, I will introduce some major features released in this version. +Here is a brief list of titles explained in the next sections: + +- URL-Based Localization +- Localization File Splitting +- Blazor UI: MudBlazor Support +- Identity: Single-Use Email/SMS 2FA Token Providers +- Account Pro: Passwordless Email Login +- AI Management: MCP Server Enhancements +- LeptonX: URL-Based Localization and Theme Improvements +- Dependency and Security Updates + +### URL-Based Localization + +ABP v10.4 introduces URL-based localization support. You can now embed the culture directly in the URL path, such as `/tr/products` or `/en/about`. + +This is especially useful for public websites, documentation sites, e-commerce applications, and any application that needs SEO-friendly and shareable localized URLs. Instead of relying only on query string, cookie, or browser language detection, the selected culture can be part of the URL itself. + +You can enable it with a single configuration: + +```csharp +Configure(options => +{ + options.UseRouteBasedCulture = true; +}); +``` + +When enabled, ABP automatically handles route registration, URL generation, menu links, and language switching for MVC/Razor Pages, Blazor, and Angular UIs. + +For Angular applications, route trees can be wrapped with `withOptionalRouteCulturePrefix` so the same route configuration can handle both `/identity/users` and `/en/identity/users`: + +```typescript +import { Routes } from '@angular/router'; +import { withOptionalRouteCulturePrefix } from '@abp/ng.core'; + +const appRoutesCore: Routes = [ + // ... your routes +]; + +export const appRoutes = withOptionalRouteCulturePrefix(appRoutesCore); +``` + +For Blazor applications, ABP built-in module pages already include culture-aware route variants. If you have your own Blazor pages, add culture route variants manually: + +```razor +@page "/Products" +@page "/{culture}/Products" +``` + +> See the [URL-Based Localization](https://abp.io/docs/10.4/framework/fundamentals/url-based-localization) documentation and [#25174](https://github.com/abpframework/abp/pull/25174) for details. + +### Localization File Splitting + +ABP localization resources can now use multiple JSON files for the same culture. This is useful for large modules or applications where keeping all localization texts in a single `en.json` file becomes difficult to maintain. + +For example, you can split a resource by feature: + +```text +Localization/ ++-- MyResource/ + +-- en.json + +-- en_Authors.json + +-- en_Books.json + +-- en_Users.json +``` + +ABP merges these files into the same localization dictionary. Files are sorted by name before merging, and if the same key exists in multiple files, the value from the last file wins. + +> See the [Localization](https://abp.io/docs/10.4/framework/fundamentals/localization) documentation and [#25227](https://github.com/abpframework/abp/pull/25227) for details. + +### Blazor UI: MudBlazor Support + +ABP v10.4 starts the [MudBlazor](https://mudblazor.com/) integration work for the Blazor UI stack. + +This release adds MudBlazor-based package infrastructure, template integration, and module/theme support needed to build ABP Blazor applications with MudBlazor. Blazorise and MudBlazor are now supported side by side, the LeptonX theme works with both UI libraries, and when creating a new Blazor project you can pick which UI library to use. + +This is a major UI foundation change, so we especially encourage Blazor users to try the RC and share feedback before the stable release. + +***Selecting the UI library when creating a new Blazor project in ABP Studio:*** + +![mud-studio](mud-studio.png) + +***MudBlazor-based application home page:*** + +![mud-index](mud-index.png) + +***MudBlazor-based Identity management page:*** + +![mud-identity](mud-identity.png) + +> See [#25235](https://github.com/abpframework/abp/pull/25235) for details. + +### Identity: Single-Use Email/SMS 2FA Token Providers + +ABP v10.4 improves the security model for email and SMS two-factor authentication codes. + +Email and phone verification codes now use ABP's single-use token providers. Generated codes are encrypted, stored with an absolute expiration time, and consumed after successful validation. Generating a new code invalidates the previous one. + +You can configure token lifetime and code length: + +```csharp +Configure(options => +{ + options.TokenLifespan = TimeSpan.FromMinutes(5); + options.CodeLength = 8; +}); + +Configure(options => +{ + options.TokenLifespan = TimeSpan.FromMinutes(2); +}); +``` + +The authenticator app provider is not affected and continues to use the standard TOTP approach. + +> See the [Two Factor Authentication](https://abp.io/docs/10.4/modules/identity/two-factor-authentication) documentation and [#25316](https://github.com/abpframework/abp/pull/25316) for details. + +### Account Pro: Passwordless Email Login + +ABP Commercial v10.4 RC introduces passwordless email login for the Account Pro module. + +Users can sign in by receiving an email login link and/or a one-time password (OTP), depending on the configured login type. Administrators can enable the feature, choose the login mode, and configure token lifetime from the account settings. + +![account-settings](account-settings.png) + +The feature is designed with security in mind: + +- Login links and OTPs are single-use. +- Resending a login email invalidates previous tokens. +- Token operations respect the current tenant context. +- Rate limiting helps protect against brute-force and email spam scenarios. +- Email enumeration behavior follows the existing account security setting. + +This feature is especially useful for applications that want a smoother sign-in experience without removing the tenant-aware and security-focused account flow of ABP. + +***"Login via email":*** + +![login-via-email](login-via-email.png) + +***Type the One-time Password (OTP) to login:*** + +![login-via-email2](login-via-email2.png) + +### AI Management: MCP Server Enhancements + +The [AI Management module](https://abp.io/docs/latest/modules/ai-management) continues to improve its MCP (Model Context Protocol) support. + +In this release, MCP server configuration has been enhanced for `stdio` transport scenarios and workspace relationships. This makes it easier to connect local or process-based MCP servers to AI workspaces and use their tools from the chat playground. + +### LeptonX: URL-Based Localization and Theme Improvements + +LeptonX has been updated to work with the new URL-based localization flow across UI types, including Angular language switching and culture-aware navigation. + +This release also includes several theme improvements and fixes, such as PathBase-safe menu links, improved custom select synchronization, sidebar menu re-binding after async rendering, and MudBlazor-related theme support. + +### Dependency and Security Updates + +ABP v10.4 RC includes several dependency updates and security-related package bumps: + +- OpenIddict upgraded to **7.5.0** +- MongoDB.Driver upgraded to **3.8.0** +- Microsoft/System package updates for CVE-2026-40372 +- `System.Security.Cryptography.Xml` upgraded to **10.0.6** +- `@abp/lodash` lodash dependency updated + +> Check [Package Version Changes](https://abp.io/docs/10.4/package-version-changes) document for all updates. + +### Other Improvements and Enhancements + +- **Virtual File System**: `ReplaceEmbeddedByPhysical` can now receive exclusion filters, which gives developers more control over included/excluded physical files during development ([#25284](https://github.com/abpframework/abp/pull/25284)). +- **Exception logging**: Complex objects in exception data are now serialized more clearly in logs ([#25267](https://github.com/abpframework/abp/pull/25267)). +- **Feature management**: Improved batch state checker performance and added `RequireFeaturesSimpleBatchStateChecker` ([#25276](https://github.com/abpframework/abp/pull/25276)). +- **RabbitMQ**: Fixed a potential hang while acquiring a closed channel after RabbitMQ restart ([#25311](https://github.com/abpframework/abp/pull/25311)). +- **Shared user accounts**: Improved shared-user lookup and two-factor authentication flows for shared user scenarios. +- **Account and SaaS modules**: Improved shared-user invitation and account-page flows in tenant user sharing scenarios. + +## Community News + +### New ABP Community Articles + +As always, exciting articles have been contributed by the ABP community. I will highlight some of them here: + +- [Stop Sprinkling [RequiresFeature] Everywhere — A Centralized Feature Gate for ABP.IO](https://abp.io/community/articles/stop-sprinkling-requiresfeature-everywhere-a-centralized-7znie818) by [Mohammad AlMohammad AlMahmoud](https://abp.io/community/members/Mohammad97Dev) +- [Top AI Coding Models in 2026: Which One Should Developers Actually Use?](https://abp.io/community/articles/top-ai-coding-models-in-2026-which-one-should-developers-use-rivh8x15) by [Alper Ebiçoğlu](https://abp.io/community/members/alper) + +Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://abp.io/community/posts/create) to the ABP Community. + +## Conclusion + +This version comes with some new features and a lot of enhancements to the existing features. You can see the [Road Map](https://abp.io/docs/10.4/release-info/road-map) documentation to learn about the release schedule and planned features for the next releases. Please try ABP v10.4 RC and provide feedback to help us release a more stable version. + +Thanks for being a part of this community! \ No newline at end of file diff --git a/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/account-settings.png b/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/account-settings.png new file mode 100644 index 0000000000..49f483f795 Binary files /dev/null and b/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/account-settings.png differ diff --git a/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/cover-image.png b/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/cover-image.png new file mode 100644 index 0000000000..9ebe8ca13a Binary files /dev/null and b/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/cover-image.png differ diff --git a/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/login-via-email.png b/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/login-via-email.png new file mode 100644 index 0000000000..026c1af5be Binary files /dev/null and b/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/login-via-email.png differ diff --git a/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/login-via-email2.png b/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/login-via-email2.png new file mode 100644 index 0000000000..02a5702b98 Binary files /dev/null and b/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/login-via-email2.png differ diff --git a/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/mud-identity.png b/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/mud-identity.png new file mode 100644 index 0000000000..76f3cdf37c Binary files /dev/null and b/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/mud-identity.png differ diff --git a/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/mud-index.png b/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/mud-index.png new file mode 100644 index 0000000000..b123c69f13 Binary files /dev/null and b/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/mud-index.png differ diff --git a/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/mud-studio.png b/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/mud-studio.png new file mode 100644 index 0000000000..8c8cde3732 Binary files /dev/null and b/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/mud-studio.png differ diff --git a/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/studio-switch-to-preview.png b/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/studio-switch-to-preview.png new file mode 100644 index 0000000000..62fd4d165e Binary files /dev/null and b/docs/en/Blog-Posts/2026-04-29 v10_4_Preview/studio-switch-to-preview.png differ diff --git a/docs/en/Blog-Posts/2026-05-14 v10_4_Release_Stable/POST.md b/docs/en/Blog-Posts/2026-05-14 v10_4_Release_Stable/POST.md new file mode 100644 index 0000000000..bc176a406e --- /dev/null +++ b/docs/en/Blog-Posts/2026-05-14 v10_4_Release_Stable/POST.md @@ -0,0 +1,89 @@ +# ABP.IO Platform 10.4 Final Has Been Released! + +We are glad to announce that [ABP](https://abp.io/) 10.4 stable version has been released. + +## What's New With Version 10.4? + +All the new features were explained in detail in the [10.4 RC Announcement Post](https://abp.io/community/announcements/announcing-abp-10-4-release-candidate-7ukyudm0), so there is no need to review them again. You can check it out for more details. + +## Getting Started with 10.4 + +### How to Upgrade an Existing Solution + +You can upgrade your existing solutions with either ABP Studio or ABP CLI. In the following sections, both approaches are explained: + +### Upgrading via ABP Studio + +If you are already using the ABP Studio, you can upgrade it to the latest version. ABP Studio periodically checks for updates in the background, and when a new version of ABP Studio is available, you will be notified through a modal. Then, you can update it by confirming the opened modal. See [the documentation](https://abp.io/docs/latest/studio/installation#upgrading) for more info. + +After upgrading the ABP Studio, then you can open your solution in the application, and simply click the **Upgrade ABP Packages** action button to instantly upgrade your solution: + +![](upgrade-abp-packages.png) + +### Upgrading via ABP CLI + +Alternatively, you can upgrade your existing solution via ABP CLI. First, you need to install the ABP CLI or upgrade it to the latest version. + +If you haven't installed it yet, you can run the following command: + +```bash +dotnet tool install -g Volo.Abp.Studio.Cli +``` + +Or to update the existing CLI, you can run the following command: + +```bash +dotnet tool update -g Volo.Abp.Studio.Cli +``` + +After installing/updating the ABP CLI, you can use the [`update` command](https://abp.io/docs/latest/CLI#update) to update all the ABP related NuGet and NPM packages in your solution as follows: + +```bash +abp update +``` + +You can run this command in the root folder of your solution to update all ABP related packages. + +## Migration Guides + +There are no explicitly marked breaking changes in this version. However, there are still some important migration notes for specific scenarios. Please read the migration guide carefully, if you are upgrading from v10.3 or earlier versions: [ABP Version 10.4 Migration Guide](https://abp.io/docs/10.4/release-info/migration-guides/abp-10-4) + +## Community News + +### Highlights from the ABP Community + +There have been some important announcements for the ABP community recently. Here are two highlights you may want to check out: + +#### React UI for ABP Framework Is Finally Here + +![React UI for ABP Framework Is Finally Here](https://abp.io/api/posts/cover-picture-source/3a2114d0-5518-38a8-d9b3-ab5100b587a4?v=20260508112328) + +React support has been one of the most requested topics in the ABP community, and with ABP 10.4, it becomes a first-class UI option in the modern template system. The new React UI is designed for teams that want ABP on the backend and React on the frontend while keeping ABP's built-in application features such as authentication, authorization, localization, multi-tenancy, modularity, runtime configuration, and deployment. + +Modern React solutions include your own React application as real source code in the solution, plus the ABP Admin Console for standard module administration screens. This means your product UI stays fully under your control, while ABP still provides a consistent and upgradeable administration experience. + +The React stack is built with familiar modern tools, including Vite, TypeScript, TanStack Router, TanStack Query, Axios, Zod, React Hook Form, Tailwind CSS, shadcn/ui, and Vitest. You can create a React UI solution with the `--modern` flag or by selecting the modern template flow in ABP Studio. You can read the announcement here: [React UI for ABP Framework Is Finally Here](https://abp.io/community/announcements/react-ui-for-abp-framework-is-finally-here-7rfmgb2v). + +#### Introducing ABP Studio AI Agent + +![Introducing ABP Studio AI Agent](https://abp.io/api/posts/cover-picture-source/3a212ebc-06c1-e10f-f83c-a90079f988c1?v=20260508112328) + +ABP Studio now introduces ABP Agent, a deeply integrated AI coding assistant that understands ABP solutions as complete systems, not just as files in folders. It is aware of ABP concepts such as modules, layers, aggregate roots, repositories, application services, DTOs, permissions, localization, event bus, distributed cache, background jobs, and module dependencies. + +ABP Agent works in three modes: Agent mode for implementation, Plan mode for read-only investigation and planning, and Ask mode for Q&A and explanations. It can use ABP Studio's analysis engine to understand the solution structure, build affected projects, start or restart applications, run tasks, generate proxies, add migrations, and inspect runtime feedback such as exceptions, logs, HTTP requests, and distributed events. + +The announcement also highlights the broader development loop around ABP Agent: solution runner integration, custom workflows, task runner support, Git and GitHub integration, AI-generated commit messages, and ABP-aware AI code review. You can read the announcement here: [Introducing ABP Studio AI Agent](https://abp.io/community/announcements/introducing-abp-studio-ai-agent-o1ni0toc). + +### New ABP Community Articles + +As always, exciting articles have been contributed by the ABP community. I will highlight some of them here: + +- [ABP in the AI Era: Surviving, Evolving, and Staying Relevant](https://abp.io/community/articles/abp-in-the-ai-era-surviving-evolving-and-staying-relevant-6gyfjfpe) by [Engincan Veske](https://abp.io/community/members/EngincanV) +- [Stop Sprinkling [RequiresFeature] Everywhere — A Centralized Feature Gate for ABP.IO](https://abp.io/community/articles/stop-sprinkling-requiresfeature-everywhere-a-centralized-7znie818) by [Mohammad AlMohammad AlMahmoud](https://abp.io/community/members/Mohammad97Dev) +- [Top AI Coding Models in 2026: Which One Should Developers Actually Use?](https://abp.io/community/articles/top-ai-coding-models-in-2026-which-one-should-developers-use-rivh8x15) by [Alper Ebiçoğlu](https://abp.io/community/members/alper) + +Thanks to the ABP Community for all the content they have published. You can also [post your ABP related (text or video) content](https://abp.io/community/posts/create) to the ABP Community. + +## About the Next Version + +The next feature version will be 10.5. You can follow the [release planning here](https://github.com/abpframework/abp/milestones). Please [submit an issue](https://github.com/abpframework/abp/issues/new) if you have any problems with this version. diff --git a/docs/en/Blog-Posts/2026-05-14 v10_4_Release_Stable/cover-image.png b/docs/en/Blog-Posts/2026-05-14 v10_4_Release_Stable/cover-image.png new file mode 100644 index 0000000000..151794e421 Binary files /dev/null and b/docs/en/Blog-Posts/2026-05-14 v10_4_Release_Stable/cover-image.png differ diff --git a/docs/en/Blog-Posts/2026-05-14 v10_4_Release_Stable/upgrade-abp-packages.png b/docs/en/Blog-Posts/2026-05-14 v10_4_Release_Stable/upgrade-abp-packages.png new file mode 100644 index 0000000000..4ec1d19589 Binary files /dev/null and b/docs/en/Blog-Posts/2026-05-14 v10_4_Release_Stable/upgrade-abp-packages.png differ diff --git a/docs/en/Community-Articles/13-05-2026-new-react-native/images/abp-studio.png b/docs/en/Community-Articles/13-05-2026-new-react-native/images/abp-studio.png new file mode 100644 index 0000000000..aec394f571 Binary files /dev/null and b/docs/en/Community-Articles/13-05-2026-new-react-native/images/abp-studio.png differ diff --git a/docs/en/Community-Articles/13-05-2026-new-react-native/images/discovery.png b/docs/en/Community-Articles/13-05-2026-new-react-native/images/discovery.png new file mode 100644 index 0000000000..2a0a2f8d0c Binary files /dev/null and b/docs/en/Community-Articles/13-05-2026-new-react-native/images/discovery.png differ diff --git a/docs/en/Community-Articles/13-05-2026-new-react-native/images/job-feed.png b/docs/en/Community-Articles/13-05-2026-new-react-native/images/job-feed.png new file mode 100644 index 0000000000..e71309b378 Binary files /dev/null and b/docs/en/Community-Articles/13-05-2026-new-react-native/images/job-feed.png differ diff --git a/docs/en/Community-Articles/13-05-2026-new-react-native/images/negotiation.png b/docs/en/Community-Articles/13-05-2026-new-react-native/images/negotiation.png new file mode 100644 index 0000000000..6334f79fcd Binary files /dev/null and b/docs/en/Community-Articles/13-05-2026-new-react-native/images/negotiation.png differ diff --git a/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-dark.png b/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-dark.png new file mode 100644 index 0000000000..c60ec7bb66 Binary files /dev/null and b/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-dark.png differ diff --git a/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-light.png b/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-light.png new file mode 100644 index 0000000000..002ca91121 Binary files /dev/null and b/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-light.png differ diff --git a/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-new-bottom-tab-menu.png b/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-new-bottom-tab-menu.png new file mode 100644 index 0000000000..8008f221a5 Binary files /dev/null and b/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-new-bottom-tab-menu.png differ diff --git a/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-new-drawer-menu.png b/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-new-drawer-menu.png new file mode 100644 index 0000000000..572cfa1c52 Binary files /dev/null and b/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-new-drawer-menu.png differ diff --git a/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-old-menu.png b/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-old-menu.png new file mode 100644 index 0000000000..83b2bb969b Binary files /dev/null and b/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-old-menu.png differ diff --git a/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-small-1.png b/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-small-1.png new file mode 100644 index 0000000000..dd6a38cac2 Binary files /dev/null and b/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-small-1.png differ diff --git a/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-small-2.png b/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-small-2.png new file mode 100644 index 0000000000..24298a9140 Binary files /dev/null and b/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-small-2.png differ diff --git a/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-small-3.png b/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-small-3.png new file mode 100644 index 0000000000..9203e3a3c6 Binary files /dev/null and b/docs/en/Community-Articles/13-05-2026-new-react-native/images/rn-small-3.png differ diff --git a/docs/en/Community-Articles/13-05-2026-new-react-native/post.md b/docs/en/Community-Articles/13-05-2026-new-react-native/post.md new file mode 100644 index 0000000000..02570f0e18 --- /dev/null +++ b/docs/en/Community-Articles/13-05-2026-new-react-native/post.md @@ -0,0 +1,165 @@ +# A New Look for ABP React Native: NativeWind, Modernization & Two Sample Apps + +## Introduction + +Mobile is increasingly the first surface users meet a product on, and the bar for what a mobile UI is supposed to look and feel like has moved a lot in the last few years. ABP has had a solid React Native template for a long time, but "solid" and "modern" are not the same thing — and the gap was starting to show. + +If you've been following ABP's mobile story, you know the React Native template has been useful but a bit stuck in time: a wall of `StyleSheet.create()` blocks per screen, inline color literals, a mixed `.js` / `.tsx` codebase, and a navigator tree that carried features most teams never used. This post is about what changed, why we changed it, and what's next. + +We approached the refresh in two passes: first a thorough cleanup of legacy screens, dead components, and unused locales, and then a full styling-layer rewrite around NativeWind v4 with a shadcn-style design token system. Along the way we also built two sample apps on top of the new template, so the changes aren't just theoretical — they've been exercised against real screens and real flows. + +The changes ship to both the **`react-native`** template and the **`microservice/apps/mobile/react-native`** template, so layered, single-layer (no-layers), and microservice solutions all get the same modernized mobile experience. + +## Why we modernized + +The previous template used React Native Paper plus hand-written `StyleSheet.create()` objects under every component. That works, but it has a real cost over time: + +- **No design tokens.** Spacing, radii, and colors lived as raw numbers and hex strings scattered across screens. Changing one accent color meant touching dozens of files. +- **Dark mode was a manual switch.** Every screen had to read theme colors and pass them down through `style` props — easy to forget, easy to drift. +- **Dead code accumulated.** Older tenant management, dashboard widgets, and SVG illustrations had built up — useful in 2021, but mostly noise in 2026. +- **Mixed JS/TS.** Some screens were still plain `.js`, which got in the way of type safety and consistent tooling. + +We wanted the same outcome a modern web template gives you: open a screen, see structured layout, see semantic class names, change one token in a config file and watch it ripple everywhere. NativeWind v4 lets us bring that exact feel to React Native — Tailwind CSS class names compile at build time, so the runtime stays small and predictable. + +## Part 1 — Template cleanup + +Before the styling rewrite, the template needed a serious trim. + +**Removed (legacy / unused):** + +- `Dashboard/` (HostDashboard, TenantDashboard, EditionUsageWidget, ErrorRateWidget) — these widgets were demo-only and rarely fit real apps. +- `CreateUpdateTenant/`, `CreateUpdateUser/`, `TenantsNavigator`, `UsersNavigator` — full administrative CRUD belongs in the admin UI, not on mobile. +- Long tail of one-off components: `DataList`, `DateRangePicker`, `Select`, `ListMenu`, `LoadingButton`, `TenantBox`, `AddIcon`, `CancelButton`, `NoRecordSvg`, `AnalysisSvg`. +- Hooks: `UsePermission`, `UseAuthAndTokenExchange`, `UseLocalizedTitle`, `PermissionHOC`. +- 18 of 20 locale files. Only **`en`** and **`tr`** ship by default — the rest are easy to add back per project, but bundling 20 locales no one ships was waste. + +**Added / upgraded:** + +- New **`LoginScreen.tsx`** (replacing the legacy `LoginScreen.js`), plus brand-new `RegisterScreen`, `ForgotPasswordScreen`, and `ResetPasswordScreen` — the full account flow is now first-class. +- **`AppContainer.tsx` / `AppContent.tsx`** split, so app-level providers and bootstrapping are cleanly separated from navigation. +- **`app.config.js`** replacing the static `app.json`, enabling environment-driven Expo config. +- **`scripts/tunnel.js`** — automates the Cloudflare tunnel flow we covered in [Automate Localhost Access for Expo](https://abp.io/community/articles/automate-localhost-access-for-expo-a-guide-to-dynamic-7cblqtj3). +- New docs: `docs/UPGRADE.md` and `docs/permission-guide.md`. + +The result: a smaller, sharper template that fits how teams actually use ABP on mobile today. + +## Part 2 — NativeWind v4 and a new visual system + +With the template trimmed, the styling layer got a full modernization. + +**The stack:** + +- **NativeWind v4** — Tailwind CSS for React Native. Class names compile at build time, so the runtime cost is minimal. +- **Tailwind CSS 3.4** — single source of truth for design tokens via `tailwind.config.js`. +- **shadcn-style neutral palette** — zinc-based color system with semantic tokens (`background`, `foreground`, `card`, `muted`, `accent`, `border`, `destructive`). Same vocabulary you already know from the web template. +- **React Native Paper** is still in the box, but **only for `TextInput`** (outlined mode, error states, icons). Everything else moved to NativeWind. +- **Ionicons** (`@expo/vector-icons`) replaces Paper icons across the UI. + +**What this looks like in practice:** + +```tsx +// Before — separate styles object, inline colors + + {title} + + +const styles = StyleSheet.create({ + card: { padding: 16, backgroundColor: '#fff', borderRadius: 12, /* ... */ }, + title: { fontSize: 18, fontWeight: '600', color: '#18181b' }, +}); + +// After — class names, dark mode included + + {title} + +``` + +That `dark:` variant is the big quality-of-life win. Dark mode is no longer something each screen has to handle by hand — it's a config-level concern, applied through semantic tokens, and consistent across every surface. Here's the new `LoginScreen` in both modes — same code, same components, just the theme switch flipped: + + + + + + +
LoginScreen — light mode
Light
LoginScreen — dark mode
Dark
+ +**Visual touches that come along for the ride:** + +- **Hero `HomeScreen`** with the app logo and feature cards. +- **iOS-style grouped settings cards** in `SettingsScreen`. +- **Centered card containers + logo headers** on login / register / password screens. +- `src/theme/spacing.ts` and `src/theme/shape.ts` are gone — those tokens now live in `tailwind.config.js` where they belong. + +### Navigation, rethought + +Navigation is where the template change is felt the most. The old template gave you a single drawer menu and that was it — everything lived behind a hamburger. The new template keeps the drawer but adds a proper **`BottomTabNavigator`** alongside it, plus an **`AccountNavigator`** for the user/account area. That brings the navigation in line with what users actually expect from a modern mobile app: primary destinations one tap away on the bottom tab bar, secondary destinations and global actions tucked into the drawer. + + + + + + + +
Old drawer menu
Before — single drawer menu
New drawer menu
After — revamped drawer
New bottom tab menu
After — new bottom tab bar
+ +## Two sample apps we built + +To stress-test the new template and to give the community something concrete to learn from, we built **two sample apps** on top of it: + +### Habit Tracker — a minimal demo + +I built **Habit Tracker** — a single-feature demo where you keep a list of daily habits and tick them off as you go through your day. Build a habit, mark it done, watch the streak grow. That's the whole loop. + +The point here wasn't to ship a feature-rich productivity tool, it was to show the smallest meaningful surface you can build on top of the new template without losing the auth flow, theming, or navigation defaults. Everything around your one feature — login/register, drawer + bottom tab navigation, light/dark mode, localized strings — comes from the template. You add the feature, and the template carries everything else. + + + + + + + +
Habit Tracker — screen 1Habit Tracker — screen 2Habit Tracker — screen 3
+ +### Hanova — a home-services demo + +We have also built **Hanova** that is a two-sided home-services sample where customers find local providers, request a job, negotiate the price, and chat through to confirmation. Pick a role, log in, browse open work or open requests, accept a booking, message the other party. That's basically the loop. + +The point here wasn't about shipping a full Uber-for-plumbers product, but it was to show a **realistic but focused** domain you can grow on top of the ABP single-layer template without rebuilding authentication, navigation, or theming from scratch. Everything around the marketplace — login/register, role selection, bottom-tab navigation, light/dark mode, localized strings, OAuth, and API wiring — comes from the template. You add the booking flow (discovery, job feed, negotiation, messaging), and the template carries everything else. Two pre-seeded personas (`ayse.kaya` / `mehmet.yilmaz`, password `Demo@1234`) populate every screen on first launch so you can explore both sides immediately. + + + + + + + +
Hanova — Discovery / browse providersHanova — Bookings / job feedHanova — Messages / negotiation
+ +Both apps will be available shortly — they're useful as reference implementations, and they're also useful as a kind of regression check on the template itself. + +## Try it yourself + +If you're starting fresh, you'll pick up the new template automatically. The easiest way is through **ABP Studio** — just enable the mobile platform and pick **React Native** from the wizard: + +![Selecting React Native as the mobile platform in ABP Studio](images/abp-studio.png) + +Or via CLI: + +```bash +abp new Acme.BookStore --template app-pro --mobile react-native +``` + +For the microservice solution template: + +```bash +abp new Acme.BookStore --template microservice-pro --mobile react-native +``` + +Open the generated `react-native/` (or `apps/mobile/react-native/`) folder, run `npm install`, then `npx expo start`, and you'll be looking at the new UI within seconds. + +If you're **upgrading an existing project**, check the new `docs/UPGRADE.md` in the template — it walks through the moving pieces (Babel/Metro config, the new `global.css` import, the `nativewind-env.d.ts` ambient types, and the locale trim). + +## Summary + +The ABP React Native template is in a much better place than it was a few months ago. The legacy screens and a long tail of dead components are gone, the styling layer is now a proper token-based system powered by **NativeWind v4** and a shadcn-style neutral palette, and **dark mode** is no longer something each screen has to think about — it's just there. Navigation got a real refresh too: the old single-drawer pattern is now a revamped drawer plus a `BottomTabNavigator` and `AccountNavigator`, which is the structure most modern mobile apps actually use. + +We also built **two sample apps** on top of the new template — **Habit Tracker**, a focused single-feature one, and **Hanova**, a more substantial demo — so the changes are exercised against real screens and real flows, not just template snapshots. If you create a new ABP solution with a React Native mobile app, all of this is what you get out of the box; if you're upgrading, the new `docs/UPGRADE.md` walks you through it. Try the template, build something on top of it, and share what you make — the whole point of moving to a token-based system is that it should be easy for everyone to extend, not just us. diff --git a/docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/abp-react-app-screenshot.png b/docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/abp-react-app-screenshot.png new file mode 100644 index 0000000000..18dc768de9 Binary files /dev/null and b/docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/abp-react-app-screenshot.png differ diff --git a/docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/abp-react-ui-modern-template-demo.gif b/docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/abp-react-ui-modern-template-demo.gif new file mode 100644 index 0000000000..e0b67ca604 Binary files /dev/null and b/docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/abp-react-ui-modern-template-demo.gif differ diff --git a/docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/abp-studio-project-creation-react.png b/docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/abp-studio-project-creation-react.png new file mode 100644 index 0000000000..77ca1eac3f Binary files /dev/null and b/docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/abp-studio-project-creation-react.png differ diff --git a/docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/cover.png b/docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/cover.png new file mode 100644 index 0000000000..d2af96722d Binary files /dev/null and b/docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/cover.png differ diff --git a/docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/react-ui-and-admin-console.png b/docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/react-ui-and-admin-console.png new file mode 100644 index 0000000000..3ea11871ba Binary files /dev/null and b/docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/images/react-ui-and-admin-console.png differ diff --git a/docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/post.md b/docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/post.md new file mode 100644 index 0000000000..2c6e7ede87 --- /dev/null +++ b/docs/en/Community-Articles/2026-03-12-official-react-ui-for-abp-framework/post.md @@ -0,0 +1,116 @@ +# React UI for ABP Framework Is Finally Here + +If you have followed ABP for a while, you probably know that React support has been one of the most requested topics in the community. + +With **ABP 10.4.0-rc.1**, that wait ends. React in ABP is no longer just something people ask about, hope for, or imagine as the next step. You can now create it, run it, and explore it today as a beta/preview experience in the modern template system. + +As part of the ABP Framework team, and as one of the developers working on this React effort, I am genuinely happy to finally share it. This RC gives the community an early chance to try it, share feedback, and help us polish the final details before **ABP 10.4 stable**, where we plan to make the React UI generally available. + +![abp-studio-project-creation-react](images/abp-studio-project-creation-react.png) + +## Why this matters + +ABP Framework has always been about helping teams build modern, maintainable, production-ready applications faster. With the new React UI, we are extending that same vision to teams who want ABP on the backend and React on the frontend without losing the built-in application features that make ABP productive from day one. + +This is not another empty starter. The goal is a **first-class UI option** that fits into the ABP application startup experience and works naturally with familiar ABP concepts such as authentication, authorization, localization, multi-tenancy, modularity, runtime configuration, and deployment. + +There is one important detail: the React UI belongs to ABP's **modern template system**. You create it with the `--modern` flag in the ABP CLI or by selecting the modern template flow in ABP Studio. You can find the technical documentation here: [React UI documentation](https://abp.io/docs/10.4/framework/ui/react). + +## A quick look at the architecture + +The final shape is clearer now: a modern React solution gives you a real React application in the solution, plus the ABP administration experience. + +First, there is **your React application**. In the modern templates, this lives directly in the solution as a real app under `react/` or `apps/react/`. It contains the frontend code you work with every day, including pages, components, routing, API integration, runtime configuration, and authentication setup. + +Second, there is the **ABP Admin Console**. The Admin Console is a pre-built React application that provides the standard ABP module management pages. It is delivered through the `Volo.Abp.AdminConsole` NuGet package, so it can evolve with ABP package updates while your own React application stays focused on your product's business features. + +For layered and single-layer modern applications, the Admin Console is hosted by the backend and served under `/admin-console/*`. For microservice solutions, it runs as a separate React app under `apps/react-admin-console/`, with its own runtime configuration and the same `/admin-console/` base path. In both cases, the main React app can link users into the Admin Console when they need full administrative screens. + +This split is a practical design choice. Your business UI stays yours, while administration capabilities remain available, consistent, and upgradeable. +![react-ui-and-admin-console](images/react-ui-and-admin-console.png) + +## A different frontend philosophy + +One of the most important things to understand is that this React UI is **not** being shaped with exactly the same architecture as some previous UI options. + +We are not trying to ship the whole frontend experience as a closed set of page implementations coming from npm packages. Instead, the generated solution includes the actual page code inside the app itself. You can open it, understand it, refactor it, redesign it, and adapt it without fighting against a packaged black box. + +The Admin Console covers ABP's standard module administration pages. Your own React application remains intentionally open and direct. That gives teams a good balance: built-in administrative power from ABP, and full ownership of the product-facing frontend. + +## Built for AI-driven development + +The new React UI is also shaped for the era of **AI-assisted development**. + +React, TypeScript, Vite, TanStack Router, TanStack Query, Axios, Zod, React Hook Form, and shadcn/ui are technologies that modern coding assistants understand very well. Just as importantly, the generated application contains real frontend code in the solution. That gives AI tools and coding agents concrete project context to read, extend, and refactor. + +This direction also fits the broader ABP AI story. ABP Studio already includes an AI assistant experience, and the new **ABP AI Agent** is being introduced to bring code generation, project understanding, issue fixing, and natural-language application evolution directly into the ABP workflow. You can follow that work here: [The Future of ABP Studio: AI Agent + Code Generation](https://abp.io/community/events/community-talks/the-future-of-abp-studio-ai-agent-code-generation-live-fekeoyjr). For the wider toolset, see the [ABP AI Toolkit](https://abp.io/ai/toolkit). + +## What the React experience looks like + +The current template already points to the kind of experience React developers expect from a modern application: + +- A Vite-powered React + TypeScript frontend +- TanStack Router for client-side routing +- TanStack Query for server state and data fetching +- OIDC authentication against the ABP Auth Server +- Axios-based HTTP client integration +- Runtime configuration through `dynamic-env.json` +- Localization and permission-aware behavior integrated with ABP application configuration +- Tailwind CSS and shadcn/ui components that live in your project and can be customized directly +- Zod and React Hook Form for form handling and validation +- Vitest for frontend tests +- A dedicated Admin Console for ABP module administration + +Even in its current form, the React UI already feels like a real ABP solution experience, not just a login page plus a few demo screens. + +![abp-react-app-screenshot.png](images/abp-react-app-screenshot.png) + +## More than a hello world + +The generated React app is intentionally small enough to understand, but it is not empty. + +Out of the box, you already get the kind of foundation most teams expect: login, registration, forgot-password and reset-password flows, runtime configuration, localization, permission-aware routing, API proxy generation, and a simple users page that can deep-link into the Admin Console when full user management is needed. + +Depending on the selected options, it can also include a sample Books CRUD page that demonstrates how to build a full create/read/update/delete flow against an ABP backend. + +The Admin Console provides the standard management experience for ABP modules, including identity management, roles, organization units, settings, audit logs, OpenIddict administration, language management, text templates, GDPR, SaaS and tenant management, and other module pages depending on your solution configuration. + +That is the core value: developers get a clean React application to build their product, while ABP continues to provide the administrative capabilities expected from a production-ready application platform. + +## Try it with ABP 10.4 RC + +During the RC period, you can create a modern React solution with ABP 10.4.0-rc.1: + +```bash +abp new Acme.BookStore --template app --modern +``` + +The React UI is the default UI option when `--modern` is used, but you can also pass it explicitly: + +```bash +abp new Acme.BookStore --template app --modern --ui-framework react +``` + +For a single-layer application: + +```bash +abp new Acme.BookStore --template app-nolayers --modern +``` + +For a microservice solution: + +```bash +abp new Acme.BookStore --template microservice --modern +``` + +Once ABP 10.4 stable is released, the same modern React experience is planned to become generally available without needing to target the RC version explicitly. + +![ABP Framework React UI Modern Template Demo](images/abp-react-ui-modern-template-demo.gif) + +## What's next + +The React UI is now real in ABP 10.4 RC, and the final polishing work continues toward the stable release. If you have been waiting for a real React path in ABP, this is the point where it stops being a wish and starts becoming something you can actually build with. + +For me, one of the nicest parts of this RC is that we can finally stop talking about React support in ABP as a future idea and start improving something real together. + +Try it, explore it, and share feedback with us while we keep polishing it for **ABP 10.4 stable**. diff --git a/docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/Post.md b/docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/Post.md new file mode 100644 index 0000000000..fe4bc61636 --- /dev/null +++ b/docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/Post.md @@ -0,0 +1,254 @@ +## Introduction + +AI coding tools went from “cool autocomplete” to “basically your junior dev (who never sleeps)” in just a couple of years. + +In 2026, the landscape is **crowded, competitive, and honestly a bit confusing**. Every model claims to be the best at coding—but depending on what you actually *do* (APIs, frontend, DevOps, debugging), the “best” can change fast. + +So instead of hype, let’s break down the **top AI coding models in 2026**, ranked by: + +* Real-world dev usefulness +* Code quality & correctness +* Context handling +* Tooling ecosystem + +We'll check the AI models against these topics: + +![](pic1.jpg) + +--- + +## 🏆 1. GPT-5.4 (OpenAI) — The All-Round Beast + +Let’s not dance around it—**GPT-5.4 is still the most versatile coding model right now.** + +### Why it’s #1 + +* Extremely strong across **all languages** +* Handles **large codebases** without losing context +* Excellent at: + + * Refactoring + * Architecture suggestions + * Debugging complex issues + +### Where it shines + +* Full-stack development +* API design +* Writing clean, production-ready code + +### Where it struggles + +* Occasionally over-engineers solutions +* Can be slower than lightweight models + +### As a result; + +If you want a **default “just works” coding AI**, this is it. + +--- + +## 🥈 2. Claude 4.7 (Anthropic) — The Clean Code Specialist + +Claude 4.7 has built a reputation for writing code that feels like it came from a senior engineer who drinks too much coffee but cares deeply about readability. + +### Strengths + +* Beautiful, readable code +* Strong reasoning for: + + * Refactoring + * Code reviews + * Documentation + +### Killer feature + +* Massive context window → great for: + + * Large repositories + * Long discussions + * System design + +### Weak spots + +* Slightly less aggressive in solving edge-case bugs +* Sometimes too “safe” in decisions + +### As a result; + +Perfect if you care about **maintainability over raw speed**. + +--- + +## 🥉 3. Gemini 3.1 (Google) — The Multimodal Powerhouse + +Gemini 3.1 is where things get interesting. + +This isn’t just a coding model—it’s a **multi-input problem solver**. + +### What makes it different + +* Understands: + + * Code + * Screenshots + * Diagrams + * Logs + +### Where it dominates + +* Debugging UI issues from screenshots +* DevOps + cloud workflows +* Cross-referencing documentation + +### Downsides + +* Code style can be inconsistent +* Sometimes less deterministic than GPT-5 + +### As a result; + +If your workflow includes **visual debugging or cloud-heavy systems**, this is insanely useful. + +--- + +## ⚡ 4. Mistral Code (Open Models) — The Speed King + +Mistral AI’s coding models are gaining serious attention. + +### Why devs love it + +* Fast +* Cheap (or free if self-hosted) +* Great for: + + * Autocomplete + * Small functions + * Local development + +### Trade-offs + +* Not as strong in deep reasoning +* Limited compared to closed models + +### As a result; + +Best choice for: + +* Privacy-sensitive environments +* Offline/local setups +* Lightweight coding tasks + +--- + +## 🧠 5. Code Llama 4 — The Open-Source Veteran + +Code Llama 4 is still very relevant, especially in enterprise setups. + +### Strengths + +* Fully open-source +* Customizable & fine-tunable +* Good baseline performance + +### Weaknesses + +* Behind top-tier models in reasoning +* Needs tuning for best results + +### As a result; + +If your company says “no cloud AI,” this is your friend. + +--- + +## 📊 Comparison Table Between AI Models + +| Model | Best For | Weakness | +| ------------ | ------------------------ | --------------------- | +| GPT-5.4 | Everything | Slightly slower | +| Claude 4.7 | Clean, maintainable code | Less aggressive fixes | +| Gemini 3.1 | Multimodal workflows | Inconsistent style | +| Mistral Code | Speed & local usage | Shallow reasoning | +| Code Llama 4 | Open-source flexibility | Needs tuning | + +Image Prompt: +A sleek table-style infographic comparing AI models with icons, performance bars, and labels like “Best for speed”, “Best for reasoning”. + +--- + +## 🤔 When to Use What (Real Scenarios) + +### Use GPT-5.4 if: + +* You’re building a full product +* You need architecture + implementation +* You want fewer “AI mistakes” + +--- + +### Use Claude 4.7 if: + +* You’re reviewing code +* You care about readability +* You’re working in a team + +--- + +### Use Gemini 3.1 if: + +* You debug using screenshots/logs +* You work with cloud infrastructure +* You want multimodal workflows + +--- + +### Use Mistral / Code Llama if: + +* You need local/private AI +* You want low cost +* You’re okay trading power for control + +--- + +## 🔌 Where ABP Framework Fits In + +If you're working with **ASP.NET Core and the ABP Framework**, these models can seriously boost productivity: + +* GPT-5.4 → Generate **application services, DTOs, and modules** +* Claude → Clean up **domain layer logic** +* Gemini → Help debug **UI + backend integration issues** + +The sweet spot? + +👉 Use AI to scaffold ABP layers, then refine manually. +That keeps your architecture clean while still saving hours. + +--- + +## 🚨 Reality Check + +AI coding models in 2026 are powerful—but: + +* They still hallucinate edge cases +* They don’t fully understand your business logic +* They can fix somewhere, break another +* They can not fix a bug even after you write 10 different prompts + +So yeah—**don’t ship blind**. + +Treat them like: + +> A fast junior dev… who needs code review. + +--- + +## TL;DR + +👉 There’s no single “winner”—just the best tool for your workflow. + +![](pic2.png) + +--- + +If you're experimenting with these models in real projects (especially with ABP), it's worth trying **multiple models side-by-side**. The differences become obvious *fast*. diff --git a/docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/cover.png b/docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/cover.png new file mode 100644 index 0000000000..e21f08e5bc Binary files /dev/null and b/docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/cover.png differ diff --git a/docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/pic1.jpg b/docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/pic1.jpg new file mode 100644 index 0000000000..9fc5d913ad Binary files /dev/null and b/docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/pic1.jpg differ diff --git a/docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/pic2.png b/docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/pic2.png new file mode 100644 index 0000000000..95c4c84b6c Binary files /dev/null and b/docs/en/Community-Articles/2026-04-17-Top-AI-Coding-Models-2026-Rankings/pic2.png differ diff --git a/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-agent-ai-review.gif b/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-agent-ai-review.gif new file mode 100644 index 0000000000..b293633382 Binary files /dev/null and b/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-agent-ai-review.gif differ diff --git a/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-agent-analyze-engine.png b/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-agent-analyze-engine.png new file mode 100644 index 0000000000..3f851eae1b Binary files /dev/null and b/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-agent-analyze-engine.png differ diff --git a/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-agent-code-generation.gif b/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-agent-code-generation.gif new file mode 100644 index 0000000000..28a2f32ca6 Binary files /dev/null and b/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-agent-code-generation.gif differ diff --git a/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-studio-ai-announcement.md b/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-studio-ai-announcement.md new file mode 100644 index 0000000000..0cd417928c --- /dev/null +++ b/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-studio-ai-announcement.md @@ -0,0 +1,244 @@ +# Introducing ABP Studio AI Agent + +The new ABP Studio release introduces a deeply integrated set of features designed around one idea: an AI coding agent that truly understands ABP solutions, sitting inside an IDE that already knows how to build, run, monitor, and iterate on them. + +At the center is **ABP Agent**, our AI coding assistant. Around it are long-standing ABP Studio capabilities that have been brought together into a single development loop. Together they turn ABP Studio into a single place where you architect and code your ABP solutions. + +![abp-studio-ui](studio-ai-coding-assistant.jpg) + +--- + +## Meet ABP Agent + +![agent-working](abp-agent-code-generation.gif) + +ABP Agent is the AI coding assistant built into ABP Studio. It operates in three modes, each tuned for a different stage of work: + +- **Agent mode**: the implementation mode. The agent reads the solution, writes and edits files, builds the affected projects, runs your apps, watches the runtime, and iterates until the change works end-to-end. +- **Plan mode**: read-only planning. The agent investigates the codebase, researches the official ABP documentation, and produces a structured implementation plan (Problem, Solution, Workflow Diagram, Files Affected, Expected Result). When you are happy with the plan, a single click promotes it into Agent mode and the implementation starts from the plan. +- **Ask mode**: read-only Q&A. The agent explains how something works, draws diagrams, and answers questions about your code without touching any files. + +The agent is **ABP-aware by default**. It is instructed to prefer ABP base classes over plain POCOs, repositories over direct `DbContext` injection, `ApplicationService` over plain services, the ABP permission system over `[Authorize(Roles=…)]`, localized strings over hardcoded text, `BusinessException`/`UserFriendlyException` over plain `Exception`, the distributed cache abstraction over raw memory cache, and background job abstractions over hand-rolled hosted services. When it is unsure about an ABP feature, it consults the official ABP documentation as a primary source of truth, not random blog posts on the web. + +--- + +## Why It Was Needed + +General-purpose AI coding tools (Cursor, Claude Code, Windsurf, opencode and similar) are excellent for horizontal, file-shaped work. They read source files, edit them, and run shell commands. That works well for small scripts or front-end apps. + +ABP solutions are different. A typical ABP solution is **system-shaped**, not just file-shaped: the important context is not only where files are located, but how modules, layers, permissions, contracts, localization, persistence, and UI pieces work together: + +- It is split across multiple modules and layers (Domain, Application, EntityFrameworkCore, HttpApi, Web, etc.) with strict dependency rules. +- It is composed of many runnable units: HTTP services, gateways, identity servers, background workers, SPAs, mobile apps, plus the Docker containers they depend on. +- It follows a strong set of conventions: aggregate roots, repositories, application services, DTOs, permissions, localization, event bus, distributed cache, background jobs. +- It is a *living* system at design time. You don't just read code, you run it, watch logs, follow distributed events, hit HTTP endpoints, and iterate. + +A generic agent has none of that vocabulary. It does not know what a module is, which project is the Domain layer, or that an `ApplicationService` should not depend on a `DbContext` directly. It cannot start "the gateway, the auth server, and the two microservices my React app talks to". It just runs `dotnet run` in some folder and hopes. It cannot tell you that the agent's latest edit caused a runtime exception in the Identity service or made the `OrderPlacedEto` event handler silently fail, because it has no concept of "running app". + +ABP Agent and the surrounding ABP Studio features were built to close exactly that gap. The agent is born inside an IDE that already understands modules, run profiles, builds, migrations, proxies, Docker containers, Kubernetes services, and distributed runtime telemetry, and it uses every one of them. + +--- + +## How ABP Agent Sees Your Application: The Analyze Engine + +![abp-agent-analyze-engine](abp-agent-analyze-engine.png) + +Before ABP Agent answers anything, it needs to *understand* your solution. This is where the **Analyze** feature does the heavy lifting. + +When you open a solution, ABP Studio analyzes every package and builds a structural map: the application's **skeleton**. It identifies what each type actually is in ABP terms: an aggregate root, an entity, a value object, a repository interface or implementation, a domain service, an application service or interface, a DTO, an integration service, a controller, an HTTP API, a background job or worker, an event handler, an ETO, a SignalR hub, a DbContext (EF Core or MongoDB), a permission/feature/setting provider, a global feature, a Mapperly or AutoMapper profile, a fluent validator, a menu contributor, a data seed contributor, an options class, an ABP module with its `[DependsOn]` chain, and many more. + +For each of these, the analyzer captures the structural information that actually matters: properties, method signatures with their line ranges, injected dependencies, base classes, the permission groups and items, the feature tree, the settings catalog, the database tables and collections, without parsing C# at runtime. + +ABP Agent receives this analyzed skeleton at the start of every session. That means: + +- The agent already knows what types exist in each project and what role each one plays, before you ask anything. +- When you say *"add a Category to my Catalog module"*, the agent already knows where the Domain project is, what the existing aggregate roots look like, which DbContext should get the new entity, where the permissions provider lives, and which application service to extend. +- When the agent needs to change an existing type, it can fetch a precise outline (base classes, properties, method signatures with exact line ranges) instead of reading thousands of lines of source. That is dramatically faster, dramatically cheaper, and dramatically more accurate. +- When you modify code, ABP Studio re-analyzes only the affected packages and refreshes the agent's understanding. + +This is what we mean by **solution-aware AI**. The agent does not "look at folders and guess"; it works from a typed, ABP-shaped index of your application. + +--- + +## Native .NET Awareness: No Shell Gymnastics + +A common pattern in generic agentic IDEs is to do everything through the terminal. Building? Run `dotnet build` in a shell. Restarting an app? Spawn a process. Adding a migration? More shell. Checking whether the build succeeded? Parse terminal output and pray. + +ABP Agent does not work through a terminal for these things. Building, running, restarting, adding migrations, generating proxies, installing client-side libraries: all of these are **first-class operations** the agent invokes directly. Build calls return structured results with errors and warnings the agent can act on immediately. Starting an application returns a structured outcome: which apps started, which failed, and a summarized error log for each failure. There is no string-scraping, no "did the spinner stop yet?" guessing. + +The agent also scopes builds intelligently. It can build a single project, a single module, or the entire solution depending on what changed. After editing a few files in your Application layer, it builds just that module (not the whole solution), and only escalates the scope when needed. + +--- + +## Solution Runner & Live Runtime Monitor + +![solution-runner-and-monitor](studio-solution-runner-and-monitor.png) + +This is the half of ABP Agent that no general-purpose AI IDE can replicate, because no general-purpose AI IDE has a first-class runner for distributed .NET solutions. + +**Solution Runner** is the ABP Studio feature that knows about every runnable thing in your solution: web apps, microservices, gateways, identity servers, background workers, CLI applications, mobile and SPA front-ends, plus the Docker containers your stack depends on (databases, caches, message brokers). Apps are grouped into folders and can be launched as a coherent set under a named *run profile*. You start everything with one click; ABP Studio handles ports, dependencies, restart-on-failure, and embedded browser previews. + +ABP Agent talks to the Solution Runner directly. It can: + +- Start, stop, or restart a specific application, every application inside a folder ("Backend/API"), or all applications. +- Start or stop Docker containers separately from applications. +- Run tasks defined in your run profile (database migrations, npm scripts, custom scripts). + +When the agent starts an application that crashes on startup, it does not loop forever restarting it. It captures the recent logs from that application, summarizes the failure, returns a structured report to itself, and uses that to **fix the underlying bug in the code** before trying again. + +But starting apps is only half of it. The **runtime monitor** is the other half. When your applications run under ABP Studio, the IDE collects, in real time: + +- **Exceptions**: type, message, full stack trace, inner exceptions, source application. +- **Logs**: timestamps, log level, application, message. +- **HTTP requests**: method, URL, status code, response time. +- **Distributed events**: name, source, direction (published/received), and payload. + +ABP Agent can query all of this. The development loop becomes: + +> Generate the code → Build the affected module → Restart the affected application → Hit the endpoint or perform the action → Ask the agent to inspect the last exceptions, the failing HTTP requests, and the distributed events → Fix the bug → Repeat. + +That is the loop a generic IDE cannot do, because it cannot see your application after it starts. ABP Agent can. + +--- + +## Custom Workflows & Task Runner Integration: Determinism When You Need It + +LLMs are powerful but non-deterministic. In real teams, some steps must happen **every time** (before or after the agent works) and they must happen in a known order. That is what **Custom Workflows** are for. + +A workflow is a named sequence of deterministic steps with a *Before* phase and an *After* phase. You can configure steps such as: + +- Build (whole solution, specific modules, or specific packages) +- Start, stop, or restart applications (specific apps, an entire folder, or all) +- Start or stop containers +- Run a Task (any custom task defined in your run profile: `npm install`, custom scripts, code generators, database resets, anything your team already wired into ABP Studio's Task Runner) +- Add a database migration +- Install client-side libraries +- Generate C# or Angular client proxies for HTTP APIs + +For example, you can configure a workflow that: + +- **Before** the agent works: starts the required containers and runs a code-generation Task. +- **After** the agent works: builds the affected modules, regenerates client proxies, restarts the gateway and the SPA, and adds a database migration. + +Workflows can be **personal** to you, or **shared with your team** through the solution's run profile file, so the deterministic pre/post pipeline travels with the repository and every developer gets the same behavior. + +The result is a clean separation: the LLM handles the creative, ambiguous middle (designing the change and writing the code), while your workflow guarantees the boring, must-happen steps around it. The agent becomes more deterministic exactly where determinism matters, without losing flexibility where it doesn't. + +The Task Runner integration is what makes the *After* phase especially valuable. You can run absolutely anything as a post-step: npm scripts, custom executables, code generators, integration test runners, lint passes, database refresh scripts. If your team already runs it as part of "I just changed some code, now do X", you can run it automatically after every agent turn. + +--- + +## Git & GitHub: Reviewing and Committing Without Leaving the IDE + +![ai-review](abp-agent-ai-review.gif) + +ABP Studio now ships a full Git client with deep GitHub integration. The goal is simple: once the agent finishes a change, you should be able to review, commit, push, branch, and respond to review feedback **without ever leaving ABP Studio**. + +The Git side covers everything you'd expect: + +- Stage and commit changes with rich, package-grouped change views. +- Create, switch, and merge branches. +- Stash and restore work, including a clear flow for stashing before switching branches. +- View commit history and create branches from any commit. +- Resolve merge conflicts with a built-in conflict editor. +- Push, pull, fetch, with seamless OAuth-based GitHub authentication. + +On the GitHub side: + +- Browse and filter Issues, view their comments, and send an issue (or just its comments) directly to ABP Agent to start working on it. +- View existing Pull Requests, see their requested changes and comments inline, and send the requested-changes feedback straight into ABP Agent so it can address the reviewer's comments. You can send the feedback into the same session you used to write the change, or start a fresh agent session. +- A one-click "Create Pull Request" action opens the new-PR page on GitHub directly from the IDE, pre-targeted at the current branch. +- Jump to any file on GitHub directly from the IDE. + +Two AI-assisted touches make this loop especially smooth: + +- **AI-generated commit messages.** Click "Generate with AI" and ABP Agent writes a Conventional Commits-style message from the staged diff. Edit it if you want, then commit. +- **AI Code Review on the diff.** Select the files you want reviewed, run AI review, and inline suggestions stream into the IDE as the analysis runs. Crucially, this is not a generic code review; it is an **ABP-aware** review. The reviewer looks for ABP-specific pattern violations: plain POCOs where ABP base classes belong, direct `DbContext` injection where a repository should be used, hardcoded strings where localization should be used, plain exceptions where `BusinessException` belongs, role-based authorization where ABP Permissions are the right answer, and so on. When the reviewer is unsure, it consults the official ABP documentation before flagging an issue. + +The result is a *closed loop*: the agent writes the change, you (or the AI reviewer agent) review the diff, you let the agent fix the comments, you commit with an AI-suggested message, you push, and you head to GitHub for the pull request, all from inside ABP Studio. + +--- + +## The End-to-End Development Loop + +Put the pieces together and ABP Studio becomes the single place where the whole development cycle happens: + +1. Open an issue from GitHub, send it to ABP Agent. +2. Switch to Plan mode; ABP Agent investigates the analyzed solution, consults the ABP docs, produces a structured plan. +3. Promote the plan to Agent mode. Implementation starts from the plan. +4. The *Before* workflow runs (start containers, run preparation tasks). +5. ABP Agent writes the code, batching edits across the affected modules, and fixes any compile errors directly. +6. The *After* workflow runs (build the affected modules, install client-side libraries, generate proxies, add a database migration, restart the impacted applications). +7. ABP Agent inspects the runtime monitor (exceptions, logs, HTTP requests, distributed events) and fixes any bug it sees. +8. You (or the AI reviewer) review the diff. Comments are sent back into the same agent session, or into a new one. +9. ABP Agent generates a commit message; you commit and push. +10. Open the new-pull-request page on GitHub with one click from ABP Studio. When reviewers leave requested changes, send them into the same agent session or start a fresh one, and iterate. + +You do not need to switch to a terminal to build. You do not need a separate tool to start your microservices. You do not need a different IDE to watch logs. You do not need a separate window for Git and GitHub. **ABP Studio is the only program you use during development, all in one.** + +--- + +## Learning Over Time + +ABP Agent also has a small but powerful feedback feature: when it makes a mistake and you (or the build, or the ABP docs) correct it, it can save that correction as a **lesson**. Lessons are short, verified notes that the agent carries forward into future turns and future sessions, so the same mistake does not happen twice. Over time, the agent gets better at the specific conventions and quirks of *your* solution, not just generic ABP. + +--- + +## Honest Comparison With Generic AI IDEs + +To be fair: Cursor, Claude Code, Windsurf and opencode are excellent products. They have semantic search, plan/agent modes, sub-agents, file editing, and shell access, and we wouldn't dispute any of that. But for ABP development specifically, here is where ABP Agent is genuinely different: + +| Capability | ABP Agent | Generic AI IDEs | +| --- | --- | --- | +| Aware of ABP roles (aggregate root, app service, repository, DTO, ETO, event handler, etc.) | Yes, structurally indexed | No (flat file/text view) | +| Knows your solution's modules, layers and dependency rules | Yes | No | +| Builds with module/package scope as a first-class operation | Yes, structured build result | No, runs `dotnet build` in a shell and parses output | +| First-class Solution Runner for distributed .NET apps + containers | Yes | No, generic shell processes | +| Restarting an application, opening its URL, waiting for it to be ready | Yes, built in | No | +| Live runtime telemetry as a tool: exceptions, logs, HTTP requests, distributed events | Yes | No | +| ABP-specific patterns checked during AI code review | Yes | Generic patterns only | +| Pre/post workflows with deterministic build / start / migrate / generate-proxies steps | Yes | No first-class concept | +| Task Runner integration for arbitrary pre/post steps owned by your team | Yes | No | +| Adding EF Core migrations as a first-class agent action | Yes | Shell only | +| Generating C#/Angular client proxies as a first-class agent action | Yes | Shell only | +| Native ABP documentation as authoritative source for the agent's decisions | Yes | Generic web search | +| Integrated Git + GitHub (issues, PR viewing, AI review) inside the same window | Yes | Partial / external | + +The pattern is consistent: **anything ABP-specific or runtime-specific belongs to ABP Agent and ABP Studio. Anything general-purpose is something everyone has.** That is the line we drew on purpose. + +--- + +## What This Means For You + +If you build with ABP Framework, this release changes how you work in three concrete ways: + +1. **You stop describing your solution to the AI.** ABP Agent already knows it. +2. **You stop bouncing between tools.** Editor, runner, runtime monitor, Git, GitHub, AI review, they all live in one window with one feedback loop. +3. **You stop accepting non-deterministic side effects.** Custom workflows + Task Runner integration make the boring steps boring again, while the agent focuses on the creative ones. + +This is the first release that brings AI assistance to ABP Studio, and we built it so that it is designed *around* the agent rather than bolted on. We think this is the natural shape of AI-assisted enterprise .NET development. + +--- + +## Short-Term Roadmap + +This release is the first step. The features below are already on our short-term roadmap and will land in upcoming releases: + +- **Debug Mode**: a dedicated mode where you and ABP Agent co-operate when debugging the solution. The agent can follow breakpoints, inspect state, propose fixes mid-session, and re-run after each change. +- **Browser control for the agent**: ABP Agent will be able to drive the embedded browser, browse pages, fill forms, and click buttons, so it can verify a UI flow end-to-end on its own and report what it observed. +- **Custom Workflows improvements**: more step types, richer conditions, finer-grained targets, and better visibility into which workflow ran for which agent turn. +- **GitHub integration improvements**: in-IDE pull request creation (no more jumping to the GitHub website), richer review handling, and more first-class issue and PR actions for the agent. +- **Git integration improvements**: more advanced day-to-day Git operations available without leaving the IDE. +- **Design helper**: ABP Agent will generate images. +- **Create a new project with the agent**: an AI-driven solution creation flow where you describe the application you want and ABP Agent helps choose the right template, modules, and configuration. +- **ABP Suite integration**: ABP Agent will be able to invoke ABP Suite for CRUD-page generation. Instead of asking the LLM to write the full set of layers for a CRUD page (which spends a lot of tokens and time), the agent will hand the task to ABP Suite, get a deterministic, production-quality result back, and continue with the parts that actually need the AI. + +## Live Demo + +We have previewed the ABP Agent in our latest community talk. Click to watch it: + +[![ABP Agent community talk demo video cover image](community-talk-cover-image.png)](https://www.youtube.com/watch?v=GYVFn2lRuWw) + +### Also see + +* https://abp.io/studio/ai-agent \ No newline at end of file diff --git a/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-studio-new-design.png b/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-studio-new-design.png new file mode 100644 index 0000000000..1a008cae57 Binary files /dev/null and b/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/abp-studio-new-design.png differ diff --git a/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/community-talk-cover-image.png b/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/community-talk-cover-image.png new file mode 100644 index 0000000000..bb77b85acb Binary files /dev/null and b/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/community-talk-cover-image.png differ diff --git a/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/cover.png b/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/cover.png new file mode 100644 index 0000000000..9d0d5f5987 Binary files /dev/null and b/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/cover.png differ diff --git a/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/studio-ai-coding-assistant.jpg b/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/studio-ai-coding-assistant.jpg new file mode 100644 index 0000000000..a4edc87715 Binary files /dev/null and b/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/studio-ai-coding-assistant.jpg differ diff --git a/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/studio-solution-runner-and-monitor.png b/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/studio-solution-runner-and-monitor.png new file mode 100644 index 0000000000..012deda724 Binary files /dev/null and b/docs/en/Community-Articles/2026-05-12-Introducing-Abp-Studio-Ai-Agent/studio-solution-runner-and-monitor.png differ diff --git a/docs/en/Community-Articles/2026-05-21-Abp-Agent-Vibe-Architecting/abp-agent-ai-review.gif b/docs/en/Community-Articles/2026-05-21-Abp-Agent-Vibe-Architecting/abp-agent-ai-review.gif new file mode 100644 index 0000000000..b293633382 Binary files /dev/null and b/docs/en/Community-Articles/2026-05-21-Abp-Agent-Vibe-Architecting/abp-agent-ai-review.gif differ diff --git a/docs/en/Community-Articles/2026-05-21-Abp-Agent-Vibe-Architecting/abp-agent-analyze-engine.png b/docs/en/Community-Articles/2026-05-21-Abp-Agent-Vibe-Architecting/abp-agent-analyze-engine.png new file mode 100644 index 0000000000..1e0319ca91 Binary files /dev/null and b/docs/en/Community-Articles/2026-05-21-Abp-Agent-Vibe-Architecting/abp-agent-analyze-engine.png differ diff --git a/docs/en/Community-Articles/2026-05-21-Abp-Agent-Vibe-Architecting/abp-agent-code-generation.gif b/docs/en/Community-Articles/2026-05-21-Abp-Agent-Vibe-Architecting/abp-agent-code-generation.gif new file mode 100644 index 0000000000..28a2f32ca6 Binary files /dev/null and b/docs/en/Community-Articles/2026-05-21-Abp-Agent-Vibe-Architecting/abp-agent-code-generation.gif differ diff --git a/docs/en/Community-Articles/2026-05-21-Abp-Agent-Vibe-Architecting/abp-agent-vibe-architecting-en.md b/docs/en/Community-Articles/2026-05-21-Abp-Agent-Vibe-Architecting/abp-agent-vibe-architecting-en.md new file mode 100644 index 0000000000..7dd357ec52 --- /dev/null +++ b/docs/en/Community-Articles/2026-05-21-Abp-Agent-Vibe-Architecting/abp-agent-vibe-architecting-en.md @@ -0,0 +1,117 @@ +# The Antidote to Vibe Architecting: ABP Studio AI Agent + +Recent discussions in software engineering have started pointing at a quiet but critical side effect of AI-assisted development. The uncomfortable truth is simple: **AI agents no longer just write code, they make architectural decisions, yet almost no one reviews those decisions as architecture.** + +Here is a striking observation: for the *same task*, changing nothing but the wording of your prompt can produce a system whose line count and file count grow several times over. In other words, the words in your prompt shape the architecture of the system. The phenomenon even has a name: **vibe architecting**, architecture that emerges from prompts rather than from deliberate, recorded design. + +In this article I will first lay out the problem, and then show why **ABP Studio AI Agent** is designed precisely to mitigate it. + +![abp-agent-code-generation](abp-agent-code-generation.gif) + +--- + +## What Is "Vibe Architecting"? + +The **vibe coding** popularized by Andrej Karpathy (describing what you want in plain language and letting the AI write the code) has moved far beyond single-line autocomplete. Today's agents spin up entire systems from a single sentence of description. And there is a further step: while writing code, the agent is also **choosing the architecture.** + +We can identify five main **mechanisms** through which agents make hidden architectural decisions: + +1. **Model selection:** Different LLMs produce structurally different code; switching the model selector is itself an architectural choice. +2. **Task decomposition:** How the agent splits work into subtasks determines the module boundaries of the system. +3. **Default configuration:** Without explicit rules, the agent drifts toward defaults inherited from its training data. +4. **Scaffolding and autonomous generation:** A single prompt folds every framework, database, auth, and deployment choice into one interaction, with no visible rationale. +5. **Integration protocols:** How the system connects to the outside world is chosen by the agent or the tool, not by the team. + +Three properties set these decisions apart from human ones: + +- **Scale:** Framework, database, authentication, and deployment are selected in a single interaction, bundled together rather than as separately reviewable choices. +- **Speed:** Decisions a team would debate for days happen in **seconds**, faster than any review process can keep up with. +- **Opacity:** The decisions are buried inside the generated code: no ADR, no design document, no recorded rationale. + +This has two concrete consequences. The first is the **speed-review gap**: the agent builds a system in minutes, while the team needs hours or days to audit it. The second is **convergence onto narrow stacks**: agent-based tools default to the same stack again and again (e.g. React/TypeScript/Tailwind), which concentrates the security attack surface. In short, these decisions take seconds, arrive bundled, and leave no record behind. + +--- + +## The Bridge: The Risk Is Far Greater in Enterprise .NET + +The examples behind these discussions usually revolve around small chatbots. But carry the same mechanisms over to an **enterprise, modular, distributed** solution and the risk multiplies. A hidden architectural decision now looks like this: + +- An `ApplicationService` depending directly on `DbContext` (a layering violation), +- Raw data access instead of a repository, +- A hand-written `[Authorize(Roles=…)]` instead of the ABP permission system, +- Hardcoded text instead of localized strings, +- A wrong module dependency, or a flawed event/flow design. + +None of these stand out in a small prototype; but in an enterprise system with dozens of modules and many services, they turn into **technical debt that piles up unseen.** And this debt starts accumulating before the code even runs, right at the moment of production. + +--- + +## The Prescription: A Three-Layer Governance Framework + +A three-layer framework that maps existing tool mechanisms onto classic software-architecture concepts is a sensible answer to this problem: + +- **Layer 1, Constraints:** Defines what the agent may and may not do. Today, instruction files (AGENTS.md, .cursorrules) and MCP configurations play this role informally; in architecture terms, the equivalent is ADLs and Attribute-Driven Design. +- **Layer 2, Conformance:** Checks the generated code against those constraints. Plan-build flows (the agent proposes before acting) and post-generation hooks are the counterpart of *fitness functions* in evolutionary architecture. +- **Layer 3, Knowledge:** Feeds architectural context back to the agent. Today, repository maps and context files; in architecture terms, ADRs and Architectural Knowledge Management. + +One caveat worth noting: tools that deliver all three layers together, proactively, are still **largely missing** today. And that is exactly where it gets interesting. + +--- + +## ABP Studio AI Agent: The Prescription, Implemented + +ABP Studio AI Agent is not a general-purpose code agent; it is an in-IDE agent that understands ABP solutions *as systems*. Look at its design and you will see it covers all three layers above with surprising clarity. + +### Layer 1, Constraints: ABP-Aware by Default + +The first layer calls for "a constraint layer that tells the agent what it may do." ABP Agent brings this as default behavior. By instruction, the agent prefers: **ABP base classes** over plain POCOs, **repositories** over direct `DbContext`, **`ApplicationService`** over plain services, the **ABP permission system** over `[Authorize(Roles=…)]`, **localized strings** over hardcoded text, **`BusinessException`/`UserFriendlyException`** over plain `Exception`, and the **distributed cache** abstraction over raw in-memory cache. When it is unsure about an ABP feature, it consults the **official ABP documentation** as the authoritative source, not random blog posts. + +On top of that, **Custom Workflows** define the deterministic steps that must run before and after every agent turn, and they can be shared with the team. So the constraints don't live in one developer's head; they live inside the solution. + +### Layer 2, Conformance: Plan Mode + ABP-Aware Review + +![abp-agent-ai-review](abp-agent-ai-review.gif) + +The second layer calls for "plan-build flows and fitness-function-like checks." ABP Agent has two concrete answers to this: + +- **Plan mode:** The agent first inspects the solution in read-only mode, consults the ABP docs, and produces a structured implementation plan (Problem, Solution, Workflow Diagram, Files Affected, Expected Result). Once you approve it, the plan turns into implementation with a single click. This is exactly the "propose before acting" flow the framework asks for. +- **ABP-aware AI code review:** This is not a generic review; it catches **ABP-specific pattern violations**: POCOs in the wrong place, direct `DbContext` injection, hardcoded strings, plain exceptions, role-based authorization, and so on. When unsure, it checks the official ABP docs. This is the concrete form of a **fitness function** that audits generated code against the constraints. + +### Layer 3, Knowledge: Analyze Engine + Lessons + +![abp-agent-analyze-engine](abp-agent-analyze-engine.png) + +The third layer calls for "a knowledge layer that feeds architectural context back to the agent": repository maps, ADRs. ABP Agent's **Analyze engine** is a higher-level version of this: the moment you open a solution, it scans every package and produces a **typed, ABP-role-aware** structural map. It knows what each type actually is: an aggregate root, a repository, an application service, a DTO, an ETO, a permission provider. The agent receives this map at the start of every session; it doesn't look at folders and guess, it **knows** the structure of the solution. + +Add to that **lessons**: when the agent makes a mistake and gets corrected, it records the correction as a short, verified note and carries it into future sessions. This is a living counterpart to the ADR/AKM idea of persisting decisions and their rationale. + +--- + +## The Problem → ABP Agent's Answer + +| The problem being raised | ABP Studio AI Agent's answer | +| --- | --- | +| **Opacity:** decisions buried in code, no rationale | The Analyze engine makes the structure visible; Plan mode turns a decision into a written plan first | +| **Speed-review gap:** agent fast, review slow | Deterministic workflows + ABP-aware review close the loop inside the IDE | +| **Convergence onto narrow stacks / concentrated risk** | ABP already provides a consistent, secure, enterprise stack and conventions | +| **Governance / ADR gap** | Lessons + shared custom workflows put decisions on the record | +| **Implicit coupling:** the prompt dictates the infrastructure | Solution-awareness means infrastructure is determined by the real structure, not by guesswork | + +The pattern is consistent: everything the governance framework says "should exist" is part of ABP Agent's design. + +--- + +## An Honest Boundary + +Let's be clear: the governance framework above is a general, ABP-independent discussion; it wasn't built to promote ABP. Nor are we claiming that "academia recommends ABP." Our claim is more modest and more solid: ABP Studio AI Agent's design **overlaps remarkably** with these **principles**. Vibe architecting is a real risk, and no tool reduces it to zero; but in enterprise .NET development, ABP Agent is built to reduce that risk meaningfully. + +--- + +## Conclusion + +The real question is not whether AI agents make architectural decisions; they do, and that is now an irreversible reality. The real question is this: are those decisions **visible, governed, and reviewable**, or do they get quietly buried in the code and turn into technical debt? + +ABP Studio AI Agent is designed to answer "yes, visible and governed." It surfaces decisions instead of hiding them; it knows the structure of the solution instead of guessing; it learns and keeps a record instead of starting from scratch every time. If you build enterprise software in the age of vibe architecting, that is exactly where the difference lies. + +- **ABP Studio AI Agent:** https://abp.io/studio/ai-agent +- **Live demo (community talk):** https://www.youtube.com/watch?v=GYVFn2lRuWw diff --git a/docs/en/cli/differences-between-old-and-new-cli.md b/docs/en/cli/differences-between-old-and-new-cli.md index 440b785797..2d1cef6f3b 100644 --- a/docs/en/cli/differences-between-old-and-new-cli.md +++ b/docs/en/cli/differences-between-old-and-new-cli.md @@ -7,9 +7,9 @@ # Old ABP CLI vs New ABP CLI -ABP CLI (Command Line Interface) is a command line tool to perform some common operations for ABP based solutions or ABP Studio features. With **v8.2+**, the old/legacy ABP CLI has been replaced with a new [CLI](index.md) system to align with the new templating system and [ABP Studio](../studio/index.md). Also, some superior features/commands have been introduced with the new CLI, such as `kube-connect` and `kube-intercept` commands. +ABP CLI (Command Line Interface) is a command line tool to perform some common operations for ABP based solutions or ABP Studio features. With **v8.2+**, the old/classic ABP CLI has been replaced with a new [CLI](index.md) system to align with the new templating system and [ABP Studio](../studio/index.md). Also, some superior features/commands have been introduced with the new CLI, such as `kube-connect` and `kube-intercept` commands. -In this guide, you will learn the motivation behind this change, some questions that you may have, how to use the old/legacy CLI, its features, and more... +In this guide, you will learn the motivation behind this change, some questions that you may have, how to use the old/classic CLI, its features, and more... ## Reason For The Change diff --git a/docs/en/cli/index.md b/docs/en/cli/index.md index bdbe9493b9..a848320549 100644 --- a/docs/en/cli/index.md +++ b/docs/en/cli/index.md @@ -9,73 +9,75 @@ ABP CLI (Command Line Interface) is a command line tool to perform some common operations for ABP based solutions or [ABP Studio](../studio/index.md) features. +This document describes `Volo.Abp.Studio.Cli`, the ABP CLI package that works with the ABP Studio template system. Modern templates, including React UI support, are available through this package. If you need to run the classic `Volo.Abp.Cli`, pass `--old` at the end of the command. + ## Installation ABP CLI is a [dotnet global tool](https://docs.microsoft.com/en-us/dotnet/core/tools/global-tools). Install it using a command line window: -````bash +```bash dotnet tool install -g Volo.Abp.Studio.Cli -```` +``` To update an existing installation: -````bash +```bash dotnet tool update -g Volo.Abp.Studio.Cli -```` +``` ## Global Options While each command may have a set of options, there are some global options that can be used with any command: -* `--skip-cli-version-check` or `-scvc`: Skips checking the latest version of the ABP CLI. If you don't specify, it will check the latest version and shows a warning message if there is a newer version of the ABP CLI. +- `--skip-cli-version-check` or `-scvc`: Skips checking the latest version of the ABP CLI. If you don't specify, it will check the latest version and shows a warning message if there is a newer version of the ABP CLI. - `--skip-extension-version-check` or `-sevc`: Skips checking the latest version of the ABP CLI extensions. If you don't specify, it will check the latest version and download the latest version if there is a newer version of the ABP CLI extensions. -* `--old`: ABP CLI has two variations: `Volo.Abp.Studio.Cli` and `Volo.Abp.Cli`. New features/templates are added to the `Volo.Abp.Studio.Cli`. But if you want to use the old version, you can use this option **at the end of your commands**. For example, `abp new Acme.BookStore --old`. -* `--help` or `-h`: Shows help for the specified command. +- `--old`: ABP CLI has two variations: `Volo.Abp.Studio.Cli` and `Volo.Abp.Cli`. New features and templates are added to `Volo.Abp.Studio.Cli`. If you want to use the old version, use this option **at the end of your commands**. For example, `abp new Acme.BookStore --old`. +- `--help` or `-h`: Shows help for the specified command. ## Commands Here is the list of all available commands before explaining their details: -* **[`help`](../cli#help)**: Shows help on the usage of the ABP CLI. -* **[`cli`](../cli#cli)**: Update or remove ABP CLI. -* **[`new`](../cli#new)**: Generates a new solution based on the ABP [startup templates](../solution-templates/index.md). -* **[`new-module`](../cli#new-module)**: Generates a new module based on the given template. -* **[`new-package`](../cli#new-package)**: Generates a new package based on the given template. -* **[`update`](../cli#update)**: Automatically updates all ABP related NuGet and NPM packages in a solution. -* **[`clean`](../cli#clean)**: Deletes all `BIN` and `OBJ` folders in the current folder. -* **[`add-package`](../cli#add-package)**: Adds an ABP package to a project. -* **[`add-package-ref`](../cli#add-package-ref)**: Adds package to given project. -* **[`install-module`](../cli#install-module)**: Adds a [multi-package application module](../modules/index.md) to a given module. -* **[`install-local-module`](../cli#install-local-module)**: Installs a local module to given module. -* **[`list-modules`](../cli#list-modules)**: Lists names of application modules. -* **[`list-templates`](../cli#list-templates)**: Lists the names of available templates to create a solution. -* **[`get-source`](../cli#get-source)**: Downloads the source code of a module. -* **[`add-source-code`](../cli#add-source-code)**: Downloads the source code and replaces package references with project references. -* **[`init-solution`](../cli#init-solution)**: Creates ABP Studio configuration files for a given solution. -* **[`kube-connect`](../cli#kube-connect)**: Connects to kubernetes environment. (*Available for* ***Business*** *or higher licenses*) -* **[`kube-intercept`](../cli#kube-intercept)**: Intercepts a service running in Kubernetes environment. (*Available for* ***Business*** *or higher licenses*) -* **[`list-module-sources`](../cli#list-module-sources)**: Lists the remote module sources. -* **[`add-module-source`](../cli#add-module-source)**: Adds a remote module source. -* **[`delete-module-source`](../cli#delete-module-source)**: Deletes a remote module source. -* **[`generate-proxy`](../cli#generate-proxy)**: Generates client side proxies to use HTTP API endpoints. -* **[`remove-proxy`](../cli#remove-proxy)**: Removes previously generated client side proxies. -* **[`switch-to-preview`](../cli#switch-to-preview)**: Switches to the latest preview version of the ABP. -* **[`switch-to-nightly`](../cli#switch-to-nightly)**: Switches to the latest [nightly builds](../release-info/nightly-builds.md) of the ABP related packages on a solution. -* **[`switch-to-stable`](../cli#switch-to-stable)**: Switches to the latest stable versions of the ABP related packages on a solution. -* **[`switch-to-local`](../cli#switch-to-local)**: Changes NuGet package references on a solution to local project references. -* **[`upgrade`](../cli#upgrade)**: It converts the application to use pro modules. -* **[`translate`](../cli#translate)**: Simplifies to translate localization files when you have multiple JSON [localization](../framework/fundamentals/localization.md) files in a source control repository. -* **[`login`](../cli#login)**: Authenticates on your computer with your [abp.io](https://abp.io/) username and password. -* **[`login-info`](../cli#login-info)**: Shows the current user's login information. -* **[`logout`](../cli#logout)**: Logouts from your computer if you've authenticated before. -* **[`bundle`](../cli#bundle)**: Generates script and style references for ABP Blazor and MAUI Blazor project. -* **[`install-libs`](../cli#install-libs)**: Install NPM Packages for MVC / Razor Pages and Blazor Server UI types. -* **[`clear-download-cache`](../cli#clear-download-cache)**: Clears the templates download cache. -* **[`check-extensions`](../cli#check-extensions)**: Checks the latest version of the ABP CLI extensions. -* **[`install-old-cli`](../cli#install-old-cli)**: Installs old ABP CLI. -* **[`mcp-studio`](../cli#mcp-studio)**: Starts ABP Studio MCP bridge for AI tools (requires ABP Studio running). -* **[`generate-razor-page`](../cli#generate-razor-page)**: Generates a page class that you can use it in the ASP NET Core pipeline to return an HTML page. -* **[`generate-jwks`](../cli#generate-jwks)**: Generates an RSA key pair (JWKS public key + PEM private key) for OpenIddict `private_key_jwt` client authentication. +- [help](../cli#help): Shows help on the usage of the ABP CLI. +- [cli](../cli#cli): Update or remove ABP CLI. +- [new](../cli#new): Generates a new solution based on the ABP [startup templates](../solution-templates/index.md). Use `--modern` to create solutions with the modern template system and React UI. +- [new-module](../cli#new-module): Generates a new module based on the given template. Use `--modern` to use modern module templates. +- [new-package](../cli#new-package): Generates a new package based on the given template. +- [update](../cli#update): Automatically updates all ABP related NuGet and NPM packages in a solution. +- [clean](../cli#clean): Deletes all `BIN` and `OBJ` folders in the current folder. +- [add-package](../cli#add-package): Adds an ABP package to a project. +- [add-package-ref](../cli#add-package-ref): Adds package to given project. +- [install-module](../cli#install-module): Adds a [multi-package application module](../modules/index.md) to a given module. +- [install-local-module](../cli#install-local-module): Installs a local module to given module. +- [list-modules](../cli#list-modules): Lists names of application modules. +- [list-templates](../cli#list-templates): Lists the names of available templates to create a solution. +- [get-source](../cli#get-source): Downloads the source code of a module. +- [add-source-code](../cli#add-source-code): Downloads the source code and replaces package references with project references. +- [init-solution](../cli#init-solution): Creates ABP Studio configuration files for a given solution. +- [kube-connect](../cli#kube-connect): Connects to Kubernetes environment. (*Available for* ***Business*** *or higher licenses*) +- [kube-intercept](../cli#kube-intercept): Intercepts a service running in Kubernetes environment. (*Available for* ***Business*** *or higher licenses*) +- [list-module-sources](../cli#list-module-sources): Lists the remote module sources. +- [add-module-source](../cli#add-module-source): Adds a remote module source. +- [delete-module-source](../cli#delete-module-source): Deletes a remote module source. +- [generate-proxy](../cli#generate-proxy): Generates client side proxies to use HTTP API endpoints. +- [remove-proxy](../cli#remove-proxy): Removes previously generated client side proxies. +- [switch-to-preview](../cli#switch-to-preview): Switches to the latest preview version of the ABP. +- [switch-to-nightly](../cli#switch-to-nightly): Switches to the latest [nightly builds](../release-info/nightly-builds.md) of the ABP related packages on a solution. +- [switch-to-stable](../cli#switch-to-stable): Switches to the latest stable versions of the ABP related packages on a solution. +- [switch-to-local](../cli#switch-to-local): Changes NuGet package references on a solution to local project references. +- [upgrade](../cli#upgrade): It converts the application to use pro modules. +- [translate](../cli#translate): Simplifies to translate localization files when you have multiple JSON [localization](../framework/fundamentals/localization.md) files in a source control repository. +- [login](../cli#login): Authenticates on your computer with your [abp.io](https://abp.io/) username and password. +- [login-info](../cli#login-info): Shows the current user's login information. +- [logout](../cli#logout): Logouts from your computer if you've authenticated before. +- [bundle](../cli#bundle): Generates script and style references for ABP Blazor and MAUI Blazor project. +- [install-libs](../cli#install-libs): Install NPM Packages for MVC / Razor Pages and Blazor Server UI types. +- [clear-download-cache](../cli#clear-download-cache): Clears the templates download cache. +- [check-extensions](../cli#check-extensions): Checks the latest version of the ABP CLI extensions. +- [install-old-cli](../cli#install-old-cli): Installs old ABP CLI. +- [mcp-studio](../cli#mcp-studio): Starts ABP Studio MCP bridge for AI tools (requires ABP Studio running). +- [generate-razor-page](../cli#generate-razor-page): Generates a page class that you can use it in the ASP NET Core pipeline to return an HTML page. +- [generate-jwks](../cli#generate-jwks): Generates an RSA key pair (JWKS public key + PEM private key) for OpenIddict `private_key_jwt` client authentication. ### help @@ -83,16 +85,16 @@ Shows basic usages of the ABP CLI. Usage: -````bash +```bash abp help [command-name] -```` +``` Examples: -````bash +```bash abp help # Shows a general help. abp help new # Shows help about the "new" command. -```` +``` ### cli @@ -100,237 +102,344 @@ Update or remove ABP CLI. Usage: -````bash +```bash abp cli [command-name] -```` +``` Examples: -````bash +```bash abp cli update abp cli update --preview abp cli update --version 1.0.0 abp cli remove abp cli check-version abp cli clear-cache -```` +``` ### new -Generates a new solution based on the ABP [startup templates](../solution-templates). See [new solution create sample commands](new-command-samples.md) +Generates a new solution based on the ABP [startup templates](../solution-templates). See [new solution create sample commands](new-command-samples.md). + +The `new` command uses the ABP Studio template system by default. Add `--modern` to create a solution from the modern template system. Modern templates are React-first and are not available through the classic CLI (`--old`). Usage: -````bash +```bash abp new [options] -```` +``` Examples: -````bash -abp new Acme.BookStore -```` +```bash +abp new Acme.BookStore --template app +``` -* `Acme.BookStore` is the solution name here. -* Common convention is to name a solution is like *YourCompany.YourProject*. However, you can use different naming like *YourProject* (single level namespacing) or *YourCompany.YourProduct.YourModule* (three levels namespacing). +- `Acme.BookStore` is the solution name here. +- Common convention is to name a solution is like *YourCompany.YourProject*. However, you can use different naming like *YourProject* (single level namespacing) or *YourCompany.YourProduct.YourModule* (three levels namespacing). For more samples, go to [ABP CLI Create Solution Samples](new-command-samples.md) #### Options -* `--template` or `-t`: Specifies the template name. Default template name is `app`, which generates a application solution. Available templates: - * **`empty`**: Empty solution template. - * **`app`**: Application template. Additional options: - * `--ui-framework` or `-u`: Specifies the UI framework. Default framework is `mvc`. Available frameworks: - * `mvc`: ASP.NET Core MVC. There are some additional options for this template: - * `--tiered`: Creates a tiered solution where Web and Http API layers are physically separated. If not specified, it creates a layered solution which is less complex and suitable for most scenarios. (*Available for* ***Team*** *or higher licenses*) - * `angular`: Angular UI. There are some additional options for this template: - * `--tiered`: The Auth Server project comes as a separate project and runs at a different endpoint. It separates the Auth Server from the API Host application. If not specified, you will have a single endpoint in the server side. (*Available for* ***Team*** *or higher licenses*) - * `--progressive-web-app` or `-pwa`: Specifies the project as Progressive Web Application. - * `blazor-webapp`: Blazor Web App UI. There are some additional options for this template: - * `--tiered`: The Auth Server and the API Host project comes as separate projects and run at different endpoints. It has 3 startup projects: *HttpApi.Host*, *AuthServer* and *Blazor* and and each runs on different endpoints. If not specified, you will have a single endpoint for your web project. - * `--progressive-web-app` or `-pwa`: Specifies the project as Progressive Web Application. - * `blazor`: Blazor UI. There are some additional options for this template: - * `--tiered`The Auth Server project comes as a separate project and runs at a different endpoint. It separates the Auth Server from the API Host application. If not specified, you will have a single endpoint in the server side. (*Available for* ***Team*** *or higher licenses*) - * `--progressive-web-app` or `-pwa`: Specifies the project as Progressive Web Application. - * `blazor-server`: Blazor Server UI. There are some additional options for this template: - * `--tiered`: The Auth Server and the API Host project comes as separate projects and run at different endpoints. It has 3 startup projects: *HttpApi.Host*, *AuthServer* and *Blazor* and and each runs on different endpoints. If not specified, you will have a single endpoint for your web project. (*Available for* ***Team*** *or higher licenses*) - * `maui-blazor`: Blazor Maui UI (*Available for* ***Team*** *or higher licenses*). There are some additional options for this template: - * `--tiered`: The Auth Server and the API Host project comes as separate projects and run at different endpoints. It has 3 startup projects: *HttpApi.Host*, *AuthServer* and *Blazor* and and each runs on different endpoints. If not specified, you will have a single endpoint for your web project. - * `no-ui`: Without UI. No front-end layer will be created. There are some additional options for this template: - * `--tiered`: The Auth Server project comes as a separate project and runs at a different endpoint. It separates the Auth Server from the API Host application. If not specified, you will have a single endpoint in the server side. (*Available for* ***Team*** *or higher licenses*) - * `--mobile` or `-m`: Specifies the mobile application framework. Default value is `none`. Available frameworks: - * `none`: Without any mobile application. - * `react-native`: React Native. This mobile option is only available for active **license owners**. - * `maui`: MAUI. This mobile option is only available for ABP. (*Available for* ***Team*** *or higher licenses*) - * `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers: - * `ef`: Entity Framework Core. - * `mongodb`: MongoDB. - * `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True` for EF Core and it is configured to use the SQL Server. If you want to use the EF Core, but need to change the DBMS, you can change it as [described here](../framework/data/entity-framework-core/other-dbms.md) (after creating the solution). **Note:** When specifying the connection string, make sure to enclose it in double quotes, for example: `--connection-string "Server=localhost;Database=MyProjectName;Trusted_Connection=True"`. - * `--skip-migrations` or `-sm`: Skips the creating initial database migration step. - * `--skip-migrator` or `-smr`: Skips the run database migrator step. - * `--public-website`: Public Website is a front-facing website for describing your project, listing your products and doing SEO for marketing purposes. Users can login and register on your website with this website. This option is only included in PRO templates. - * `--without-cms-kit`: When you add a public website to your solution, it automatically includes the [CmsKit](./../modules/cms-kit-pro/index.md) module. If you don't want to include *CmsKit*, you can use this parameter. - * `--separate-tenant-schema`: Creates a different DbContext for tenant schema. If not specified, the tenant schema is shared with the host schema. This option is only included in PRO templates. - * `--sample-crud-page` or `-scp`: It adds the [BookStore](./../tutorials/book-store/index.md) sample to your solution. - * `--theme` or `-th`: Specifes the theme. Default theme is `leptonx`. Available themes: - * `leptonx`: LeptonX Theme. (*Available for* ***Team*** *or higher licenses*) - * `leptonx-lite`: LeptonX-Lite Theme. - * `basic`: Basic Theme. - * `--use-open-source-template`or `-uost`: Uses the open-source template. (*Available for* ***Team*** *or higher licenses*) - * **`app-nolayers`**: Single-layer application template. Additional options: - * `--ui-framework` or `-u`: Specifies the UI framework. Default framework is `mvc`. Available frameworks: - * `mvc`: ASP.NET Core MVC. There are some additional options for this template: - * `angular`: Angular UI. There are some additional options for this template: - * `blazor`: Blazor UI. There are some additional options for this template: - * `blazor-server`: Blazor Server UI. There are some additional options for this template: - * `no-ui`: Without UI. No front-end layer will be created. There are some additional options for this template: - * `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers: - * `ef`: Entity Framework Core. - * `mongodb`: MongoDB. - * `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True` for EF Core and it is configured to use the SQL Server. If you want to use the EF Core, but need to change the DBMS, you can change it as [described here](../framework/data/entity-framework-core/other-dbms.md) (after creating the solution). **Note:** When specifying the connection string, make sure to enclose it in double quotes, for example: `--connection-string "Server=localhost;Database=MyProjectName;Trusted_Connection=True"`. - * `--skip-migrations` or `-sm`: Skips the creating initial database migration step. - * `--skip-migrator` or `-smr`: Skips the run database migrator step. - * `--sample-crud-page` or `-scp`: It adds the [BookStore](./../tutorials/book-store/index.md) sample to your solution. - * `--theme`: Specifes the theme. Default theme is `leptonx`. Available themes: - * `leptonx`: LeptonX Theme. (*Available for* ***Team*** *or higher licenses*) - * `leptonx-lite`: LeptonX-Lite Theme. - * `basic`: Basic Theme. - * `--use-open-source-template`or `-uost`: Uses the open-source template. (*Available for* ***Team*** *or higher licenses*) - * **`microservice`**: Microservice solution template (*Available for* ***Business*** *or higher licenses*). Additional options: - * `--ui-framework` or `-u`: Specifies the UI framework. Default framework is `mvc`. Available frameworks: - * `mvc`: ASP.NET Core MVC. There are some additional options for this template: - * `angular`: Angular UI. There are some additional options for this template: - * `blazor`: Blazor UI. There are some additional options for this template: - * `blazor-server`: Blazor Server UI. There are some additional options for this template: - * `maui-blazor`: Blazor Maui UI. There are some additional options for this template: - * `no-ui`: Without UI. No front-end layer will be created. There are some additional options for this template: - * `--mobile` or `-m`: Specifies the mobile application framework. Default value is `none`. Available frameworks: - * `none`: Without any mobile application. - * `react-native`: React Native. - * `maui`: MAUI. - * `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers: - * `ef`: Entity Framework Core. - * `mongodb`: MongoDB. - * `--theme`: Specifes the theme. Default theme is `leptonx`. Available themes: - * `leptonx`: LeptonX Theme. - * `basic`: Basic Theme. - * `--public-website`: Public Website is a front-facing website for describing your project, listing your products and doing SEO for marketing purposes. Users can login and register on your website with this website. This option is only included in PRO templates. - * `--no-grafana-dashboard` or `-ngd`: Does not add example Grafana Dashboard to the solution. -* `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory. -* `--local-framework-ref` or `-lfr`: Uses local projects references to the ABP framework instead of using the NuGet packages. It tries to find the paths from `ide-state.json`. The file is located at `%UserProfile%\.abp\studio\ui\ide-state.json` (for Windows) and `~/.abp/studio/ui/ide-state.json` (for MAC). -* `--create-solution-folder` or `-csf`: Specifies if the project will be in a new folder in the output folder or directly the output folder. -* `--database-management-system` or `-dbms`: Sets the database management system. Default is **SQL Server**. Supported DBMS's: - * `SqlServer` - * `MySQL` - * `PostgreSQL` - * `SQLite` (`app` & `app-nolayers`) - * `Oracle` (`app` & `app-nolayers`) - * `Oracle-Devart` (`app` & `app-nolayers`) -* `--dont-run-install-libs`: Skip installing client side packages. -* `--dont-run-bundling`: Skip bundling for Blazor packages. -* `--no-kubernetes-configuration` or `-nkc`: Skips the Kubernetes configuration files. -* `--no-social-logins` or `-nsl`: Skipts the social login configuration. -* `--no-tests` or `-ntp`: Does not add test projects. -* *Module Options*: You can skip some modules if you don't want to add them to your solution, or include if you want them (*Available for* ***Team*** *or higher licenses*). Available commands: - * `-no-saas`: Skips the Saas module. - * `-no-gdpr`: Skips the GDPR module. - * `-no-openiddict-admin-ui`: Skips the OpenIddict Admin UI module. - * `-no-audit-logging`: Skips the Audit Logging module. - * `-no-language-management`: Skips the Language Management module. - * `-no-text-template-management`: Skips the Text Template Management module. - * `-file-management`: Includes the File Management module. - * `-chat`: Includes the Chat module. -* `--legacy`: Generates a legacy solution. - * `trust-version`: Trusts the user's version and does not check if the version exists or not. If the template with the given version is found in the cache, it will be used, otherwise throws an exception. +- `--template` or `-t`: Specifies the template name. Default template name is `app`, which generates an application solution. Available templates: + - **`empty`**: Empty solution template. + - **`app`**: Application template. Additional options: + - `--ui-framework` or `-u`: Specifies the UI framework. Default framework is `mvc`. Available frameworks: + - `mvc`: ASP.NET Core MVC. There are some additional options for this template: + - `--tiered`: Creates a tiered solution where Web and Http API layers are physically separated. If not specified, it creates a layered solution which is less complex and suitable for most scenarios. (*Available for* ***Team*** *or higher licenses*) + - `angular`: Angular UI. There are some additional options for this template: + - `--tiered`: The Auth Server project comes as a separate project and runs at a different endpoint. It separates the Auth Server from the API Host application. If not specified, you will have a single endpoint in the server side. (*Available for* ***Team*** *or higher licenses*) + - `--progressive-web-app` or `-pwa`: Specifies the project as Progressive Web Application. + - `blazor-webapp`: Blazor Web App UI. There are some additional options for this template: + - `--tiered`: The Auth Server and the API Host project comes as separate projects and run at different endpoints. It has 3 startup projects: *HttpApi.Host*, *AuthServer* and *Blazor* and each runs on different endpoints. If not specified, you will have a single endpoint for your web project. + - `--progressive-web-app` or `-pwa`: Specifies the project as Progressive Web Application. + - `blazor`: Blazor UI. There are some additional options for this template: + - `--tiered`: The Auth Server project comes as a separate project and runs at a different endpoint. It separates the Auth Server from the API Host application. If not specified, you will have a single endpoint in the server side. (*Available for* ***Team*** *or higher licenses*) + - `--progressive-web-app` or `-pwa`: Specifies the project as Progressive Web Application. + - `blazor-server`: Blazor Server UI. There are some additional options for this template: + - `--tiered`: The Auth Server and the API Host project comes as separate projects and run at different endpoints. It has 3 startup projects: *HttpApi.Host*, *AuthServer* and *Blazor* and and each runs on different endpoints. If not specified, you will have a single endpoint for your web project. (*Available for* ***Team*** *or higher licenses*) + - `maui-blazor`: Blazor Maui UI (*Available for* ***Team*** *or higher licenses*). There are some additional options for this template: + - `--tiered`: The Auth Server and the API Host project comes as separate projects and run at different endpoints. It has 3 startup projects: *HttpApi.Host*, *AuthServer* and *Blazor* and and each runs on different endpoints. If not specified, you will have a single endpoint for your web project. + - `react`: React SPA UI. Only available when `--modern` flag is used. See [Modern Templates](#modern-templates) below. + - `no-ui`: Without UI. No front-end layer will be created. There are some additional options for this template: + - `--tiered`: The Auth Server project comes as a separate project and runs at a different endpoint. It separates the Auth Server from the API Host application. If not specified, you will have a single endpoint in the server side. (*Available for* ***Team*** *or higher licenses*) + - `--mobile` or `-m`: Specifies the mobile application framework. Default value is `none`. Available frameworks: + - `none`: Without any mobile application. + - `react-native`: React Native. This mobile option is only available for active **license owners**. + - `maui`: MAUI. This mobile option is only available for ABP. (*Available for* ***Team*** *or higher licenses*). Not supported with `--modern`. + - `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers: + - `ef`: Entity Framework Core. + - `mongodb`: MongoDB. + - `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True` for EF Core and it is configured to use the SQL Server. If you want to use the EF Core, but need to change the DBMS, you can change it as [described here](../framework/data/entity-framework-core/other-dbms.md) (after creating the solution). **Note:** When specifying the connection string, make sure to enclose it in double quotes, for example: `--connection-string "Server=localhost;Database=MyProjectName;Trusted_Connection=True"`. + - `--skip-migrations` or `-sm`: Skips the creating initial database migration step. + - `--skip-migrator` or `-smr`: Skips the run database migrator step. + - `--public-website`: Public Website is a front-facing website for describing your project, listing your products and doing SEO for marketing purposes. Users can login and register on your website with this website. This option is only included in PRO templates. + - `--without-cms-kit`: When you add a public website to your solution, it automatically includes the [CmsKit](./../modules/cms-kit-pro/index.md) module. If you don't want to include *CmsKit*, you can use this parameter. + - `--separate-tenant-schema`: Creates a different DbContext for tenant schema. If not specified, the tenant schema is shared with the host schema. This option is only included in PRO templates. + - `--sample-crud-page` or `-scp`: It adds the [BookStore](./../tutorials/book-store/index.md) sample to your solution. + - `--theme` or `-th`: Specifies the theme. Default theme is `leptonx`. Available themes: + - `leptonx`: LeptonX Theme. (*Available for* ***Team*** *or higher licenses*) + - `leptonx-lite`: LeptonX-Lite Theme. + - `basic`: Basic Theme. + - `--use-open-source-template` or `-uost`: Uses the open-source template. (*Available for* ***Team*** *or higher licenses*) + - **`app-nolayers`**: Single-layer application template. Additional options: + - `--ui-framework` or `-u`: Specifies the UI framework. Default framework is `mvc`. Available frameworks: + - `mvc`: ASP.NET Core MVC. There are some additional options for this template: + - `angular`: Angular UI. There are some additional options for this template: + - `blazor`: Blazor UI. There are some additional options for this template: + - `blazor-server`: Blazor Server UI. There are some additional options for this template: + - `react`: React SPA UI. Only available when `--modern` flag is used. See [Modern Templates](#modern-templates) below. + - `no-ui`: Without UI. No front-end layer will be created. There are some additional options for this template: + - `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers: + - `ef`: Entity Framework Core. + - `mongodb`: MongoDB. + - `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True` for EF Core and it is configured to use the SQL Server. If you want to use the EF Core, but need to change the DBMS, you can change it as [described here](../framework/data/entity-framework-core/other-dbms.md) (after creating the solution). **Note:** When specifying the connection string, make sure to enclose it in double quotes, for example: `--connection-string "Server=localhost;Database=MyProjectName;Trusted_Connection=True"`. + - `--skip-migrations` or `-sm`: Skips the creating initial database migration step. + - `--skip-migrator` or `-smr`: Skips the run database migrator step. + - `--sample-crud-page` or `-scp`: It adds the [BookStore](./../tutorials/book-store/index.md) sample to your solution. + - `--theme`: Specifies the theme. Default theme is `leptonx`. Available themes: + - `leptonx`: LeptonX Theme. (*Available for* ***Team*** *or higher licenses*) + - `leptonx-lite`: LeptonX-Lite Theme. + - `basic`: Basic Theme. + - `--use-open-source-template` or `-uost`: Uses the open-source template. (*Available for* ***Team*** *or higher licenses*) + - **`microservice`**: Microservice solution template (*Available for* ***Business*** *or higher licenses*). Additional options: + - `--ui-framework` or `-u`: Specifies the UI framework. Default framework is `mvc`. Available frameworks: + - `mvc`: ASP.NET Core MVC. There are some additional options for this template: + - `angular`: Angular UI. There are some additional options for this template: + - `blazor`: Blazor UI. There are some additional options for this template: + - `blazor-server`: Blazor Server UI. There are some additional options for this template: + - `maui-blazor`: Blazor Maui UI. There are some additional options for this template: + - `react`: React SPA + React Admin Console. Only available when `--modern` flag is used. See [Modern Templates](#modern-templates) below. + - `no-ui`: Without UI. No front-end layer will be created. There are some additional options for this template: + - `--mobile` or `-m`: Specifies the mobile application framework. Default value is `none`. Available frameworks: + - `none`: Without any mobile application. + - `react-native`: React Native. + - `maui`: MAUI. Not supported with `--modern`. + - `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. Available providers: + - `ef`: Entity Framework Core. + - `mongodb`: MongoDB. + - `--theme`: Specifies the theme. Default theme is `leptonx`. Available themes: + - `leptonx`: LeptonX Theme. + - `basic`: Basic Theme. + - `--public-website`: Public Website is a front-facing website for describing your project, listing your products and doing SEO for marketing purposes. Users can login and register on your website with this website. This option is only included in PRO templates. + - `--no-grafana-dashboard` or `-ngd`: Does not add example Grafana Dashboard to the solution. +- `--modern`: Uses the modern variant of the selected template. Modern templates are React-first and use a different template source shipped with ABP Studio, instead of NuGet extension packages. See [Modern Templates](#modern-templates) below. +- `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory. +- `--local-framework-ref` or `-lfr`: Uses local projects references to the ABP framework instead of using the NuGet packages. It tries to find the paths from `ide-state.json`. The file is located at `%UserProfile%\.abp\studio\ui\ide-state.json` (for Windows) and `~/.abp/studio/ui/ide-state.json` (for MAC). +- `--create-solution-folder` or `-csf`: Specifies if the project will be in a new folder in the output folder or directly the output folder. +- `--database-management-system` or `-dbms`: Sets the database management system. Default is **SQL Server**. Supported DBMS's: + - `SqlServer` + - `MySQL` + - `PostgreSQL` + - `SQLite` (`app` & `app-nolayers`) + - `Oracle` (`app` & `app-nolayers`) + - `Oracle-Devart` (`app` & `app-nolayers`) +- `--dont-run-install-libs`: Skip installing client side packages. +- `--dont-run-bundling`: Skip bundling for Blazor packages. +- `--no-kubernetes-configuration` or `-nkc`: Skips the Kubernetes configuration files. +- `--no-social-logins` or `-nsl`: Skips the social login configuration. +- `--no-multi-tenancy`: Disables multi-tenancy support in the generated solution. +- `--no-tests` or `-ntp`: Does not add test projects. +- *Module Options*: You can skip some modules if you don't want to add them to your solution, or include if you want them (*Available for* ***Team*** *or higher licenses*). Available commands: + - `-no-saas`: Skips the Saas module. + - `-no-gdpr`: Skips the GDPR module. + - `-no-openiddict-admin-ui`: Skips the OpenIddict Admin UI module. + - `-no-audit-logging`: Skips the Audit Logging module. + - `-no-language-management`: Skips the Language Management module. + - `-no-text-template-management`: Skips the Text Template Management module. + - `-file-management`: Includes the File Management module. + - `-chat`: Includes the Chat module. + - `--ai-management`: Includes the AI Management module. + - `--ai-providers`: Specifies AI providers (comma-separated). Available values: `Ollama`, `OpenAI`. Requires `--ai-management`. +- `--legacy`: Generates a classic solution. + - `trust-version`: Trusts the user's version and does not check if the version exists or not. If the template with the given version is found in the cache, it will be used, otherwise throws an exception. + +##### Modern Template Options + +The following options apply only when `--modern` is used: + +| Option | Description | Templates | +| --- | --- | --- | +| `--shadcn-theme ` | Sets the shadcn/ui color theme for the generated React apps. See [Shadcn Theme Values](#shadcn-theme-values). | all `--modern` templates | +| `--admin-password ` | Sets the initial admin user password. | all `--modern` templates | +| `--modular` | Generates a modular monolith variant. | `app-nolayers --modern` | +| `--services ` | Adds extra microservice names as a comma-separated list (for example, `Ordering,Shipping`). | `microservice --modern` | + +#### Modern Templates + +Add `--modern` to a supported template to use its modern variant. Modern templates use a different template source, shipped with ABP Studio, compared to classic templates that use NuGet extension packages. They are **React-first** and have a narrower set of supported options. + +```bash +abp new Acme.BookStore --template app --modern +abp new Acme.BookStore --template app-nolayers --modern +abp new Acme.BookStore --template microservice --modern +``` + +| Template + `--modern` | UI Framework | Mobile | +| ----------------------- | ---------------------------------------------------------- | ------------------------ | +| `app --modern` | `react` (default) or `no-ui` | `none` or `react-native` | +| `app-nolayers --modern` | `react` (default) or `no-ui` | `none` or `react-native` | +| `microservice --modern` | `react` (default, includes React Admin Console) or `no-ui` | `none` or `react-native` | + +> Blazor, Angular, MVC, and MAUI Blazor UI frameworks are **not** supported with `--modern`. The `maui` mobile option is also not supported with `--modern`. +> +> Options that are not supported by a modern template are ignored with a warning in the CLI output. +> +> `--modern` can also be used with `--ready-config-path` (`-rcp`) and `--solution-history-id` (`-shi`). In these cases, the template in the JSON configuration or solution history record is mapped to its modern variant. See [Using Existing Configuration](new-command-samples.md#using-existing-configuration) for configuration-file and solution-history examples. + +For `app --modern` and `app-nolayers --modern`, the generated solution includes a `react/` folder for your application. The ABP Admin Console is hosted by the backend through the `Volo.Abp.AdminConsole` package and is served from `/admin-console/`; there is no separate `apps/react-admin-console/` folder. + +When using `--template microservice --modern`, the generated solution includes: + +- `apps/react/` — React SPA (main user-facing application) +- `apps/react-admin-console/` — React Admin Console (administration interface) +- `apps/auth-server/` — OpenIddict authentication server +- `gateways/web/` — YARP reverse proxy for the React apps +- `gateways/mobile/` — Gateway for mobile apps (only when `--mobile` is set) + +Two OpenIddict clients are automatically seeded: `MyProjectName_App` (React SPA) and `MyProjectName_AdminConsole` (React Admin Console). + +Examples: + +```bash +# Modern layered app with React UI +abp new Acme.BookStore --template app --modern + +# Modern single-layer app with React UI +abp new Acme.BookStore --template app-nolayers --modern + +# Modern microservice (React + React Admin Console) +abp new Acme.BookStore --template microservice --modern + +# Modern microservice with no UI +abp new Acme.BookStore --template microservice --modern --ui-framework no-ui + +# Modern single-layer modular monolith +abp new Acme.BookStore --template app-nolayers --modern --modular + +# Modern microservice with PostgreSQL +abp new Acme.BookStore --template microservice --modern --database-management-system postgresql + +# Modern microservice with additional services +abp new Acme.BookStore --template microservice --modern --services Ordering,Shipping + +# Modern microservice with React Native mobile +abp new Acme.BookStore --template microservice --modern --mobile react-native +``` +##### Shadcn Theme Values + +Use `--shadcn-theme ` with `--modern` templates: + +- `slate` (default) +- `pink` +- `blue` +- `turquoise` +- `orange` +- `purple` ### new-module Generates a new module. -````bash +```bash abp new-module [options] -```` +``` Examples: -````bash +```bash abp new-module Acme.BookStore -t module:ddd -```` +``` #### options -* `--template` or `-t`: Specifies the template name. Default template name is `module:ddd`, which generates a DDD module. Module templates are provided by the main template, see their own startup template documentation for available modules. `empty:empty` and `module:ddd` template is available for all solution structure. -* `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory. -* `--target-solution` or `-ts`: If set, the new module will be added to the given solution. Otherwise the new module will added to the closest solution in the file system. If no solution found, it will throw an error. -* `--solution-folder` or `-sf`: Specifies the target folder in the [Solution Explorer](../studio/solution-explorer.md#folder) virtual folder system. -* `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. This option is only available if the module template supports it. You can add multiple values separated by commas, such as `ef,mongodb` if the module template supports it. Available providers: - * `ef`: Entity Framework Core. - * `mongodb`: MongoDB. -* `--ui-framework` or `-u`: Specifies the UI framework. This option is only available if the module template supports it. You can add multiple values separated by commas, such as `mvc,angular` if the module template supports it. Available frameworks: - * `mvc`: ASP.NET Core MVC. - * `angular`: Angular UI. - * `blazor`: Blazor UI. - * `blazor-server`: Blazor Server UI. +- `--template` or `-t`: Specifies the template name. Default template name is `module:ddd`, which generates a DDD module. Module templates are provided by the main template, see their own startup template documentation for available modules. `empty:empty` and `module:ddd` template is available for all solution structure. +- `--modern`: Uses the modern variant of the selected module template. +- `--output-folder` or `-o`: Specifies the output folder. Default value is the current directory. +- `--target-solution` or `-ts`: If set, the new module will be added to the given solution. Otherwise the new module will added to the closest solution in the file system. If no solution found, it will throw an error. +- `--solution-folder` or `-sf`: Specifies the target folder in the [Solution Explorer](../studio/solution-explorer.md#folder) virtual folder system. +- `--database-provider` or `-d`: Specifies the database provider. Default provider is `ef`. This option is only available if the module template supports it. You can add multiple values separated by commas, such as `ef,mongodb` if the module template supports it. Available providers: + - `ef`: Entity Framework Core. + - `mongodb`: MongoDB. +- `--ui-framework` or `-u`: Specifies the UI framework. This option is only available if the module template supports it. You can add multiple values separated by commas, such as `mvc,angular` if the module template supports it. Available frameworks: + - `mvc`: ASP.NET Core MVC. + - `angular`: Angular UI. + - `blazor`: Blazor UI. + - `blazor-server`: Blazor Server UI. + +#### Modern Module Templates + +Add `--modern` to use the modern module variant: + +```bash +abp new-module Acme.BookStore.Orders --modern +abp new-module Acme.BookStore.Orders --modern -t module:ddd +abp new-module Acme.BookStore.Orders --modern -t module:standard +``` + +When the target solution itself is modern, the modern module variant is selected automatically even if `--modern` is not passed. + +Options that are not supported by a modern module template are ignored with a warning in the CLI output. ### new-package Generates a new package. -````bash +```bash abp new-package [options] -```` +``` Examples: -````bash +```bash abp new-package --name Acme.BookStore.Domain --template lib.domain -```` +``` #### options -* `--template` or `-t`: Specifies the template name. This parameter doesn't have a default value and must be set. Available templates and their sub-options: - * `lib.class-library` - * `lib.domain-shared` - * `--add-localization`: Includes default localization configuration & language files. - * `lib.domain` - * `--add-settings`: Includes default settings configuration. - * `--add-db-properties`: Includes the default Database Properties class. - * `--add-domain-shared`: Includes an additional Domain Shared package. - * `lib.application-contracts` - * `lib.application` - * `--add-mapperly`: Adds Mapperly configuration. - * `--add-application-contracts`: Includes an additional contracts package. - * `lib.ef` - * `--include-migrations`: Allows migration operations on this package. - * `--connection-string-name`: Default value is the last part of the package's namespace (or package name simply). - * `--connection-string`: Connection string value. The default value is null. You can set it later. **Note:** When specifying the connection string, make sure to enclose it in double quotes, for example: `--connection-string "Server=localhost;Database=MyProjectName;Trusted_Connection=True"`. - * `lib.mongodb` - * `lib.http-api` - * `lib.http-api-client` - * `lib.mvc` - * `--add-mapperly`: Adds Mapperly configuration. - * `lib.blazor` - * `--add-mapperly`: Adds Mapperly configuration. - * `--add-menu-contributors`: Includes default menu contributors. - * `lib.blazor-wasm` - * `lib.blazor-server` - * `host.http-api` - * `--add-serilog`: Includes Serilog configuration. - * `--add-swagger`: Includes Swagger configuration. - * `host.mvc` - * `--add-serilog`: Includes Serilog configuration. - * `--add-swagger`: Includes Swagger configuration. - * `host.blazor-wasm` - * `--backend`: Name of the backend project in the module (not path). - * `host.blazor-server` - * `abp.console` - * `csharp.console` - * `csharp.library` -* `--module-file` or `-m`: If set, the new package will be added to the given module. Otherwise the new package will added to the closest module in the file system. If no module found, it will throw an error. -* `--name` or `-n`: Specifies the name of the package. If not set, a name based on the template type and module name will be generated. -* `--folder` or `-f`: Specifies the target folder in the target module's virtual folder system. +- `--template` or `-t`: Specifies the template name. This parameter doesn't have a default value and must be set. Available templates and their sub-options: + - `lib.class-library` + - `lib.domain-shared` + - `--add-localization`: Includes default localization configuration & language files. + - `lib.domain` + - `--add-settings`: Includes default settings configuration. + - `--add-db-properties`: Includes the default Database Properties class. + - `--add-domain-shared`: Includes an additional Domain Shared package. + - `lib.application-contracts` + - `lib.application` + - `--add-mapperly`: Adds Mapperly configuration. + - `--add-application-contracts`: Includes an additional contracts package. + - `lib.ef` + - `--include-migrations`: Allows migration operations on this package. + - `--connection-string-name`: Default value is the last part of the package's namespace (or package name simply). + - `--connection-string`: Connection string value. The default value is null. You can set it later. **Note:** When specifying the connection string, make sure to enclose it in double quotes, for example: `--connection-string "Server=localhost;Database=MyProjectName;Trusted_Connection=True"`. + - `lib.mongodb` + - `lib.http-api` + - `lib.http-api-client` + - `lib.mvc` + - `--add-mapperly`: Adds Mapperly configuration. + - `lib.blazor` + - `--add-mapperly`: Adds Mapperly configuration. + - `--add-menu-contributors`: Includes default menu contributors. + - `lib.blazor-wasm` + - `lib.blazor-server` + - `host.http-api` + - `--add-serilog`: Includes Serilog configuration. + - `--add-swagger`: Includes Swagger configuration. + - `host.mvc` + - `--add-serilog`: Includes Serilog configuration. + - `--add-swagger`: Includes Swagger configuration. + - `host.blazor-wasm` + - `--backend`: Name of the backend project in the module (not path). + - `host.blazor-server` + - `abp.console` + - `csharp.console` + - `csharp.library` +- `--module-file` or `-m`: If set, the new package will be added to the given module. Otherwise the new package will added to the closest module in the file system. If no module found, it will throw an error. +- `--name` or `-n`: Specifies the name of the package. If not set, a name based on the template type and module name will be generated. +- `--folder` or `-f`: Specifies the target folder in the target module's virtual folder system. ### update @@ -338,25 +447,25 @@ Updating all ABP related packages can be tedious since there are many packages o Usage: -````bash +```bash abp update [options] -```` +``` -* If you run in a directory with a .csproj file, it updates all ABP related packages of the project to the latest versions. -* If you run in a directory with a .sln file, it updates all ABP related packages of the all projects of the solution to the latest versions. -* If you run in a directory that contains multiple solutions in sub-folders, it can update all the solutions, including Angular projects. +- If you run in a directory with a .csproj file, it updates all ABP related packages of the project to the latest versions. +- If you run in a directory with a .sln file, it updates all ABP related packages of the all projects of the solution to the latest versions. +- If you run in a directory that contains multiple solutions in sub-folders, it can update all the solutions, including Angular projects. Note that this command can upgrade your solution from a previous version, and also can upgrade it from a preview release to the stable release of the same version. #### Options -* `--npm`: Only updates NPM packages. -* `--nuget`: Only updates NuGet packages. -* `--solution-path` or `-sp`: Specify the solution path. Use the current directory by default -* `--solution-name` or `-sn`: Specify the solution name. Search `*.sln` files in the directory by default. -* `--check-all`: Check the new version of each package separately. Default is `false`. -* `--version` or `-v`: Specifies the version to use for update. If not specified, latest version is used. -* `--leptonx-version` or `-lv`: Specifies the LeptonX version to use for update. If not specified, latest version or the version that is compatible with `--version` argument is used. +- `--npm`: Only updates NPM packages. +- `--nuget`: Only updates NuGet packages. +- `--solution-path` or `-sp`: Specify the solution path. Use the current directory by default +- `--solution-name` or `-sn`: Specify the solution name. Search `*.sln` files in the directory by default. +- `--check-all`: Check the new version of each package separately. Default is `false`. +- `--version` or `-v`: Specifies the version to use for update. If not specified, latest version is used. +- `--leptonx-version` or `-lv`: Specifies the LeptonX version to use for update. If not specified, latest version or the version that is compatible with `--version` argument is used. ### clean @@ -364,41 +473,41 @@ Deletes all `BIN` and `OBJ` folders in the current folder. Usage: -````bash +```bash abp clean -```` - +``` ### add-package Adds an ABP package to a project by, -* Adding related nuget package as a dependency to the project. -* Adding `[DependsOn(...)]` attribute to the module class in the project (see the [module development document](../framework/architecture/modularity/basics.md)). +- Adding related nuget package as a dependency to the project. +- Adding `[DependsOn(...)]` attribute to the module class in the project (see the [module development document](../framework/architecture/modularity/basics.md)). > Notice that the added module may require additional configuration which is generally indicated in the documentation of the related package. Basic usage: -````bash +```bash abp add-package [options] -```` +``` Examples: -````bash +```bash abp add-package Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic -```` +``` -* This example adds the `Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic` package to the project. +- This example adds the `Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic` package to the project. #### Options -* `--project` or `-p`: Specifies the project (.csproj) file path. If not specified, CLI tries to find a .csproj file in the current directory. -* `--with-source-code`: Downloads the source code of the package to your solution folder and uses local project references instead of NuGet/NPM packages. -* `--add-to-solution-file`: Adds the downloaded package to your solution file, so you will also see the package when you open the solution on a IDE. (only available when `--with-source-code` is True) +- `--project` or `-p`: Specifies the project (.csproj) file path. If not specified, CLI tries to find a .csproj file in the current directory. +- `--with-source-code`: Downloads the source code of the package to your solution folder and uses local project references instead of NuGet/NPM packages. +- `--add-to-solution-file`: Adds the downloaded package to your solution file, so you will also see the package when you open the solution on a IDE. (only available when `--with-source-code` is True) > Currently only the source code of the basic theme packages([MVC](../framework/ui/mvc-razor-pages/basic-theme.md) and [Blazor](../framework/ui/blazor/basic-theme.md)) can be downloaded. +> > - Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic > - Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme > - Volo.Abp.AspNetCore.Components.Web.BasicTheme @@ -408,61 +517,61 @@ abp add-package Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic Adds one or more package reference to target project, also adds ABP module dependency. Both reference and target projects must belong to same module. -````bash +```bash abp add-package-ref [options] -```` +``` Examples: -````bash +```bash abp add-package-ref Acme.BookStore.Domain abp add-package-ref "Acme.BookStore.Domain Acme.BookStore.Domain.Shared" -t Acme.BookStore.Web -```` +``` #### Options -* `--target-project` or `-t`: Name of the project that reference will be added. If not set, project in the current directory will be used. +- `--target-project` or `-t`: Name of the project that reference will be added. If not set, project in the current directory will be used. ### install-module Installs a module, that is published as nuget packages, to a local module. Project relations are created according the types of the projects. For Examples: a `lib.domain-shared` project is added to `lib.domain-shared` project -````bash +```bash abp install-module [options] -```` +``` Examples: -````bash +```bash abp install-module Volo.Blogging abp install-module Volo.Blogging -t "modules/crm/Acme.Crm.abpmdl" -```` +``` #### Options -* `--target-module` or `-t`: Path (or folder path) of the target module that the other module will be installed to. If not set, the closest module to the current directory will be used. -* `--version` or `-v`: Nuget version of the module to be installed. +- `--target-module` or `-t`: Path (or folder path) of the target module that the other module will be installed to. If not set, the closest module to the current directory will be used. +- `--version` or `-v`: Nuget version of the module to be installed. ### install-local-module Installs one module to another. Project relations are created according the types of the projects. For Examples: a `lib.domain-shared` project is added to `lib.domain-shared` project -````bash +```bash abp install-local-module [options] -```` +``` Examples: -````bash +```bash abp install-local-module Acme.OrderManagement abp install-local-module Acme.OrderManagement -t "modules/crm/Acme.Crm.abpmdl" -```` +``` #### Options -* `--target-module` or `-t`: Path (or folder path) of the target module that the other module will be installed to. If not set, the closest module to the current directory will be used. +- `--target-module` or `-t`: Path (or folder path) of the target module that the other module will be installed to. If not set, the closest module to the current directory will be used. ### list-modules @@ -470,9 +579,9 @@ Lists names of open-source application modules. Usage: -````bash +```bash abp list-modules [options] -```` +``` Examples: @@ -496,9 +605,9 @@ Downloads the source code of a module to your computer. Usage: -````bash +```bash abp get-source [options] -```` +``` Examples: @@ -510,118 +619,118 @@ abp get-source Volo.Blogging --local-framework-ref --abp-path D:\GitHub\abp #### Options -* `--output-folder` or `-o`: Specifies the directory that source code will be downloaded in. If not specified, current directory is used. -* `--version` or `-v`: Specifies the version of the source code that will be downloaded. If not specified, latest version is used. -* `--preview`: If no version option is specified, this option specifies if latest [preview version](../release-info/previews.md) will be used instead of latest stable version. -* `--local-framework-ref --abp-path`: Path of [ABP GitHub repository](https://github.com/abpframework/abp) in your computer. This will be used for converting project references to your local system. If this is not specified, project references will be converted to NuGet references. +- `--output-folder` or `-o`: Specifies the directory that source code will be downloaded in. If not specified, current directory is used. +- `--version` or `-v`: Specifies the version of the source code that will be downloaded. If not specified, latest version is used. +- `--preview`: If no version option is specified, this option specifies if latest [preview version](../release-info/previews.md) will be used instead of latest stable version. +- `--local-framework-ref --abp-path`: Path of [ABP GitHub repository](https://github.com/abpframework/abp) in your computer. This will be used for converting project references to your local system. If this is not specified, project references will be converted to NuGet references. ### add-source-code Downloads the source code of a module and replaces package references with project references. This command only works if your ABP Commercial License has source-code access, or if source-code of the target module is free to all type of ABP Commercial Licenses. -````bash +```bash abp add-source-code [options] -```` +``` Examples: -````bash +```bash abp add-source-code Volo.Chat --add-to-solution-file -```` +``` #### Options -* `--target-module` or `-t`: The module that will refer the downloaded source code. If not set, the module in the current directory will be used. -* `--add-to-solution-file`: Adds the downloaded source code to C# solution file and ABP Studio solution file. +- `--target-module` or `-t`: The module that will refer the downloaded source code. If not set, the module in the current directory will be used. +- `--add-to-solution-file`: Adds the downloaded source code to C# solution file and ABP Studio solution file. ### init-solution Creates necessary files for a solution to be readable by ABP Studio. If the solution is generated via ABP Studio, you don't need this command. But it is not generated by ABP Studio, you need this command to make it work with ABP Studio. -````bash +```bash abp init-solution [options] -```` +``` Examples: -````bash +```bash abp init-solution --name Acme.BookStore -```` +``` #### Options -* `--name` or `-n`: Name for the solution. If not set, it will be the same as the name of closest c# solution in the file system. +- `--name` or `-n`: Name for the solution. If not set, it will be the same as the name of closest c# solution in the file system. ### kube-connect Connects to Kubernetes cluster (*Available for* ***Business*** *or higher licenses*). Press `ctrl+c` to disconnect. -````bash +```bash abp kube-connect [options] -```` +``` Examples: -````bash +```bash abp kube-connect abp kube-connect -p Default.abpk8s.json abp kube-connect -c docker-desktop -ns mycrm-local -```` +``` #### Options -* `--profile` or `-p`: Kubernetes Profile path or name to be used. Path can be relative (to current directory) or full path, or you can simply give the name of profile if you run this command in same directory with the solution or profile. This parameter is not needed if you use `--namespace` and `--context` parameters. -* `--namespace` or `-ns`: The namespace that services running on. -* `--context` or `-c`: The context that services running in. -* `--wireguard-password` or `-wp`: Wireguard password for the profile. This is not needed if you already set it on the ABP Studio user interface. -* `--solution-path` or `-sp`: Path of the solution. If not set, the closest solution in file system will be used. +- `--profile` or `-p`: Kubernetes Profile path or name to be used. Path can be relative (to current directory) or full path, or you can simply give the name of profile if you run this command in same directory with the solution or profile. This parameter is not needed if you use `--namespace` and `--context` parameters. +- `--namespace` or `-ns`: The namespace that services running on. +- `--context` or `-c`: The context that services running in. +- `--wireguard-password` or `-wp`: Wireguard password for the profile. This is not needed if you already set it on the ABP Studio user interface. +- `--solution-path` or `-sp`: Path of the solution. If not set, the closest solution in file system will be used. ### kube-intercept Intercepts a service running in Kubernetes environment (*Available for* ***Business*** *or higher licenses*). Press `ctrl+c` to stop interception. -````bash +```bash abp kube-intercept [options] -```` +``` Examples: -````bash +```bash abp kube-intercept mycrm-product-service -ns mycrm-local abp kube-intercept mycrm-product-service -ns mycrm-local -a MyCrm.ProductService.HttpApi.Host.csproj abp kube-intercept mycrm-product-service -ns mycrm-local -a MyCrm.ProductService.HttpApi.Host.csproj -pm 8080:80,8081:443 -```` +``` #### Options -* `--application` or `-a`: Relative or full path of the project that will intercept the service. If not set, the project in the current directory will be used. -* `--namespace` or `-ns`: The namespace that service running on. -* `--context` or `-sc`: The context that service running in. Default value is `docker-desktop`. -* `--port-mappings` or `-pm`: Port mappings for the service. +- `--application` or `-a`: Relative or full path of the project that will intercept the service. If not set, the project in the current directory will be used. +- `--namespace` or `-ns`: The namespace that service running on. +- `--context` or `-sc`: The context that service running in. Default value is `docker-desktop`. +- `--port-mappings` or `-pm`: Port mappings for the service. ### list-module-sources With this command, you can see the list of remote module sources that you can use to install modules. It is similar to the NuGet feed list in Visual Studio. -````bash +```bash abp list-module-sources -```` +``` ### add-module-source Adds a remote module source to the list of sources that you can use to install modules. -````bash +```bash abp add-module-source [options] -```` +``` You can create your own module source and add it to the list. It accepts a name and a url or a path as parameter. If you provide a path, it should be a local path that contains the modules json file. If you provide a url, it should be a url that contains the modules json file. The json file should be in the following format: -````json +```json { "name": "ABP Open Source Modules", "modules" : { @@ -631,40 +740,40 @@ You can create your own module source and add it to the list. It accepts a name ... } } -```` +``` When you add a module source, you can install modules from that source using the `install-module` command. It attempts to find the package from NuGet, such as `Volo.Abp.Account.Installer`. You can configure a private NuGet feed and publish your modules to that feed. Each module has an installer package that is utilized to install the module into a solution. When you publish your module to a private feed, you should also publish the installer package to the same feed. Examples: -````bash +```bash abp add-module-source -n "Custom Source" -p "D:\packages\abp\modules.json" abp add-module-source -n "Custom Http Source" -p "https://raw.githubusercontent.com/x/abp-module-store/main/abp-module-store.json" -```` +``` #### Options -* `--name` or `-n`: The name of the module source. -* `--path` or `-p`: The path of the module source. It can be a local path or a url. +- `--name` or `-n`: The name of the module source. +- `--path` or `-p`: The path of the module source. It can be a local path or a url. ### delete-module-source Deletes a remote module source from the list of sources that you can use to install modules. -````bash +```bash abp delete-module-source [options] -```` +``` Examples: -````bash +```bash abp delete-module-source -n "Custom Source" -```` +``` #### Options -* `--name` or `-n`: The name of the module source. +- `--name` or `-n`: The name of the module source. ### generate-proxy @@ -672,39 +781,38 @@ Generates Angular, C# or JavaScript service proxies for your HTTP APIs to make e Usage: -````bash +```bash abp generate-proxy -t [options] -```` +``` Examples: -````bash +```bash abp generate-proxy -t ng -url https://localhost:44302/ abp generate-proxy -t js -url https://localhost:44302/ abp generate-proxy -t csharp -url https://localhost:44302/ -```` +``` #### Options -* `--type` or `-t`: The name of client type. Available clients: - * `csharp`: C#, work in the `*.HttpApi.Client` project directory. There are some additional options for this client: - * `--without-contracts`: Avoid generating the application service interface, class, enum and dto types. - * `--folder`: Folder name to place generated CSharp code in. Default value: `ClientProxies`. - * `ng`: Angular. There are some additional options for this client: - * `--api-name` or `-a`: The name of the API endpoint defined in the `/src/environments/environment.ts`. Default value: `default`. - * `--source` or `-s`: Specifies the Angular project name to resolve the root namespace & API definition URL from. Default value: `defaultProject`. - * `--target`: Specifies the Angular project name to place generated code in. Default value: `defaultProject`. - * `--module`: Backend module name. Default value: `app`. - * `--entry-point`: Targets the Angular project to place the generated code. - * `--url`: Specifies api definition url. Default value is API Name's url in environment file. - * `--prompt` or `-p`: Asks the options from the command line prompt (for the unspecified options). - - * `js`: JavaScript. work in the `*.Web` project directory. There are some additional options for this client: - * `--output` or `-o`: JavaScript file path or folder to place generated code in. -* `--module` or `-m`: Specifies the name of the backend module you wish to generate proxies for. Default value: `app`. -* `--working-directory` or `-wd`: Execution directory. For `csharp` and `js` client types. -* `--url` or `-u`: API definition URL from. -* `--service-type` or `-st`: Specifies the service type to generate. `application`, `integration` and `all`, Default value: `all` for C#, `application` for JavaScript / Angular. +- `--type` or `-t`: The name of client type. Available clients: + - `csharp`: C#, work in the `*.HttpApi.Client` project directory. There are some additional options for this client: + - `--without-contracts`: Avoid generating the application service interface, class, enum and dto types. + - `--folder`: Folder name to place generated CSharp code in. Default value: `ClientProxies`. + - `ng`: Angular. There are some additional options for this client: + - `--api-name` or `-a`: The name of the API endpoint defined in the `/src/environments/environment.ts`. Default value: `default`. + - `--source` or `-s`: Specifies the Angular project name to resolve the root namespace & API definition URL from. Default value: `defaultProject`. + - `--target`: Specifies the Angular project name to place generated code in. Default value: `defaultProject`. + - `--module`: Backend module name. Default value: `app`. + - `--entry-point`: Targets the Angular project to place the generated code. + - `--url`: Specifies api definition url. Default value is API Name's url in environment file. + - `--prompt` or `-p`: Asks the options from the command line prompt (for the unspecified options). + - `js`: JavaScript. work in the `*.Web` project directory. There are some additional options for this client: + - `--output` or `-o`: JavaScript file path or folder to place generated code in. +- `--module` or `-m`: Specifies the name of the backend module you wish to generate proxies for. Default value: `app`. +- `--working-directory` or `-wd`: Execution directory. For `csharp` and `js` client types. +- `--url` or `-u`: API definition URL from. +- `--service-type` or `-st`: Specifies the service type to generate. `application`, `integration` and `all`, Default value: `all` for C#, `application` for JavaScript / Angular. > See the [Angular Service Proxies document](../framework/ui/angular/service-proxies.md) for more. @@ -716,34 +824,34 @@ This can be especially useful when you generate proxies for multiple modules bef Usage: -````bash +```bash abp remove-proxy -t [options] -```` +``` Examples: -````bash +```bash abp remove-proxy -t ng abp remove-proxy -t js -m identity -o Pages/Identity/client-proxies.js abp remove-proxy -t csharp --folder MyProxies/InnerFolder -```` +``` #### Options -* `--type` or `-t`: The name of client type. Available clients: - * `csharp`: C#, work in the `*.HttpApi.Client` project directory. There are some additional options for this client: - * `--folder`: Folder name to place generated CSharp code in. Default value: `ClientProxies`. - * `ng`: Angular. There are some additional options for this client: - * `--api-name` or `-a`: The name of the API endpoint defined in the `/src/environments/environment.ts`. Default value: `default`. - * `--source` or `-s`: Specifies the Angular project name to resolve the root namespace & API definition URL from. Default value: `defaultProject`. - * `--target`: Specifies the Angular project name to place generated code in. Default value: `defaultProject`. - * `--url`: Specifies api definition url. Default value is API Name's url in environment file. - * `--prompt` or `-p`: Asks the options from the command line prompt (for the unspecified options). - * `js`: JavaScript. work in the `*.Web` project directory. There are some additional options for this client: - * `--output` or `-o`: JavaScript file path or folder to place generated code in. -* `--module` or `-m`: Specifies the name of the backend module you wish to generate proxies for. Default value: `app`. -* `--working-directory` or `-wd`: Execution directory. For `csharp` and `js` client types. -* `--url` or `-u`: API definition URL from. +- `--type` or `-t`: The name of client type. Available clients: + - `csharp`: C#, work in the `*.HttpApi.Client` project directory. There are some additional options for this client: + - `--folder`: Folder name to place generated CSharp code in. Default value: `ClientProxies`. + - `ng`: Angular. There are some additional options for this client: + - `--api-name` or `-a`: The name of the API endpoint defined in the `/src/environments/environment.ts`. Default value: `default`. + - `--source` or `-s`: Specifies the Angular project name to resolve the root namespace & API definition URL from. Default value: `defaultProject`. + - `--target`: Specifies the Angular project name to place generated code in. Default value: `defaultProject`. + - `--url`: Specifies api definition url. Default value is API Name's url in environment file. + - `--prompt` or `-p`: Asks the options from the command line prompt (for the unspecified options). + - `js`: JavaScript. work in the `*.Web` project directory. There are some additional options for this client: + - `--output` or `-o`: JavaScript file path or folder to place generated code in. +- `--module` or `-m`: Specifies the name of the backend module you wish to generate proxies for. Default value: `app`. +- `--working-directory` or `-wd`: Execution directory. For `csharp` and `js` client types. +- `--url` or `-u`: API definition URL from. > See the [Angular Service Proxies document](../framework/ui/angular/service-proxies.md) for more. @@ -753,14 +861,13 @@ You can use this command to switch your solution or project to latest preview ve Usage: -````bash +```bash abp switch-to-preview [options] -```` +``` #### Options -* `--directory` or `-d`: Specifies the directory. The solution or project should be in that directory or in any of its sub directories. If not specified, default is the current directory. - +- `--directory` or `-d`: Specifies the directory. The solution or project should be in that directory or in any of its sub directories. If not specified, default is the current directory. ### switch-to-nightly @@ -768,13 +875,13 @@ You can use this command to switch your solution or project to latest [nightly]( Usage: -````bash +```bash abp switch-to-nightly [options] -```` +``` #### Options -* `--directory` or `-d`: Specifies the directory. The solution or project should be in that directory or in any of its sub directories. If not specified, default is the current directory. +- `--directory` or `-d`: Specifies the directory. The solution or project should be in that directory or in any of its sub directories. If not specified, default is the current directory. ### switch-to-stable @@ -782,12 +889,13 @@ If you're using the ABP preview packages (including nightly previews), you can s Usage: -````bash +```bash abp switch-to-stable [options] -```` +``` + #### Options -* `--directory` or `-d`: Specifies the directory. The solution or project should be in that directory or in any of its sub directories. If not specified, default is the current directory. +- `--directory` or `-d`: Specifies the directory. The solution or project should be in that directory or in any of its sub directories. If not specified, default is the current directory. ### switch-to-local @@ -795,20 +903,20 @@ Changes all NuGet package references to local project references for all the .cs Usage: -````bash +```bash abp switch-to-local [options] -```` -#### Options +``` -* `--solution` or `-s`: Specifies the solution directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory. +#### Options -* `--paths` or `-p`: Specifies the local paths that the projects are inside. You can use `|` character to separate the paths. +- `--solution` or `-s`: Specifies the solution directory. The solution should be in that directory or in any of its sub directories. If not specified, default is the current directory. +- `--paths` or `-p`: Specifies the local paths that the projects are inside. You can use `|` character to separate the paths. Examples: -````bash +```bash abp switch-to-local --paths "D:\Github\abp|D:\Github\my-repo" -```` +``` ### upgrade @@ -817,35 +925,35 @@ This command is specially designed for users who already started their developme Usage: -````bash +```bash abp upgrade [-t ] [options] -```` +``` Examples: -````bash +```bash abp upgrade -t app abp upgrade -t app --language-management --gdpr --audit-logging-ui --text-template-management --openiddict-pro abp upgrade -t app-nolayers --audit-logging-ui abp upgrade -t app-nolayers -p D:\MyProjects\MyProject -```` +``` #### Options -* `--path` or `-p`: Specifies the module path. The module should be in that directory. If not specified, the default is the current directory. -* `--gdpr`: Installs GDPR module too. -* `--language-management`: Installs Language Management module too. -* `--audit-logging-ui`: Installs Audit Logging Pro (UI) module too. -* `--text-template-management`: Installs Text Template Management module too. -* `--openiddict-pro`: Installs OpenIddict Pro (UI) module too. +- `--path` or `-p`: Specifies the module path. The module should be in that directory. If not specified, the default is the current directory. +- `--gdpr`: Installs GDPR module too. +- `--language-management`: Installs Language Management module too. +- `--audit-logging-ui`: Installs Audit Logging Pro (UI) module too. +- `--text-template-management`: Installs Text Template Management module too. +- `--openiddict-pro`: Installs OpenIddict Pro (UI) module too. ### translate Simplifies to translate [localization](../framework/fundamentals/localization.md) files when you have multiple JSON [localization](../framework/fundamentals/localization.md) files in a source control repository. -* This command will create a unified json file based on the reference culture. -* It searches all the localization `JSON` files in the current directory and all subdirectories (recursively). Then creates a single file (named `abp-translation.json` by default) that includes all the entries need to be translated. -* Once you translate the entries in this file, you can then apply your changes to the original localization files using the `--apply` command. +- This command will create a unified json file based on the reference culture. +- It searches all the localization `JSON` files in the current directory and all subdirectories (recursively). Then creates a single file (named `abp-translation.json` by default) that includes all the entries need to be translated. +- Once you translate the entries in this file, you can then apply your changes to the original localization files using the `--apply` command. > The main purpose of this command is to translate ABP localization files (since the [abp repository](https://github.com/abpframework/abp) has tens of localization files to be translated in different directories). @@ -853,38 +961,38 @@ Simplifies to translate [localization](../framework/fundamentals/localization.md First step is to create the unified translation file: -````bash +```bash abp translate -c [options] -```` +``` Examples: -````bash +```bash abp translate -c de -```` +``` This command created the unified translation file for the `de` (German) culture. ##### Additional Options -* `--reference-culture` or `-r`: Default `en`. Specifies the reference culture. -* `--output` or `-o`: Output file name. Default `abp-translation.json`. -* `--all-values` or `-all`: Include all keys to translate. By default, the unified translation file only includes the missing texts for the target culture. Specify this parameter if you may need to revise the values already translated before. +- `--reference-culture` or `-r`: Default `en`. Specifies the reference culture. +- `--output` or `-o`: Output file name. Default `abp-translation.json`. +- `--all-values` or `-all`: Include all keys to translate. By default, the unified translation file only includes the missing texts for the target culture. Specify this parameter if you may need to revise the values already translated before. #### Applying Changes Once you translate the entries in the unified translation file, you can apply your changes to the original localization files using the `--apply` parameter: -````bash +```bash abp translate --apply # apply all changes abp translate -a # shortcut for --apply -```` +``` Then review changes on your source control system to be sure that it has changed the proper files and send a Pull Request if you've translated ABP resources. Thank you in advance for your contribution. ##### Additional Options -* `--file` or `-f`: Default: `abp-translation.json`. The translation file (use only if you've used the `--output` option before). +- `--file` or `-f`: Default: `abp-translation.json`. The translation file (use only if you've used the `--output` option before). #### Online DeepL translate @@ -892,9 +1000,9 @@ The `translate` command also supports online translation. You need to provide yo It will search all the `en.json(reference-culture)` files in the directory and sub-directory and then translate and generate the corresponding `zh-Hans.json(culture)` files. -````bash +```bash abp translate -c zh-Hans --online --deepl-auth-key -```` +``` ### login @@ -926,42 +1034,42 @@ abp logout ### bundle -This command generates script and style references for ABP Blazor WebAssembly and MAUI Blazor project and updates the **index.html** file. It helps developers to manage dependencies required by ABP modules easily. In order for ```bundle``` command to work, its **executing directory** or passed ```--working-directory``` parameter's directory must contain a Blazor or MAUI Blazor project file(*.csproj). +This command generates script and style references for ABP Blazor WebAssembly and MAUI Blazor project and updates the **index.html** file. It helps developers to manage dependencies required by ABP modules easily. In order for `bundle` command to work, its **executing directory** or passed `--working-directory` parameter's directory must contain a Blazor or MAUI Blazor project file(*.csproj). Usage: -````bash +```bash abp bundle [options] -```` +``` > This command is no longer needed if you are using Global Assets feature. See [Managing Global Scripts & Styles](../framework/ui/blazor/global-scripts-styles.md) for more information. #### Options -* ```--working-directory``` or ```-wd```: Specifies the working directory. This option is useful when executing directory doesn't contain a Blazor project file. -* ```--force``` or ```-f```: Forces to build project before generating references. -* ```--project-type``` or ```-t```: Specifies the project type. Default type is `webassembly`. Available types: - * `webassembly` - * `maui-blazor` -* `--version` or `-v`: Specifies the ABP Framework version that the project is using. This is helpful for those who use central package management. +- `--working-directory` or `-wd`: Specifies the working directory. This option is useful when executing directory doesn't contain a Blazor project file. +- `--force` or `-f`: Forces to build project before generating references. +- `--project-type` or `-t`: Specifies the project type. Default type is `webassembly`. Available types: + - `webassembly` + - `maui-blazor` +- `--version` or `-v`: Specifies the ABP Framework version that the project is using. This is helpful for those who use central package management. `bundle` command reads the `appsettings.json` file inside the Blazor and MAUI Blazor project for bundling options. For more details about managing style and script references in Blazor or MAUI Blazor apps, see [Managing Global Scripts & Styles](../framework/ui/blazor/global-scripts-styles.md) ### install-libs -This command install NPM Packages for MVC / Razor Pages and Blazor Server UI types. Its **executing directory** or passed ```--working-directory``` parameter's directory must contain a project file(*.csproj). +This command install NPM Packages for MVC / Razor Pages and Blazor Server UI types. Its **executing directory** or passed `--working-directory` parameter's directory must contain a project file(*.csproj). `install-libs` command reads the `abp.resourcemapping.js` file to manage package. For more details see [Client Side Package Management](../framework/ui/mvc-razor-pages/client-side-package-management.md). Usage: -````bash +```bash abp install-libs [options] -```` +``` #### Options -* ```--working-directory``` or ```-wd```: Specifies the working directory. This option is useful when executing directory doesn't contain a project file. +- `--working-directory` or `-wd`: Specifies the working directory. This option is useful when executing directory doesn't contain a project file. ### check-extensions @@ -969,9 +1077,9 @@ This command checks the installed ABP CLI extensions and updates them if necessa Usage: -````bash +```bash abp check-extensions -```` +``` ### install-old-cli @@ -999,7 +1107,7 @@ abp mcp-studio [options] Options: -* `--endpoint` or `-e`: Overrides ABP Studio MCP endpoint. Default value is `http://localhost:38280/mcp/`. +- `--endpoint` or `-e`: Overrides ABP Studio MCP endpoint. Default value is `http://localhost:38280/mcp/`. Example: @@ -1092,7 +1200,7 @@ body { console.log('MyPage.js loaded!'); ``` -5. Finally, run the `generate-razor-page` command under the `Views` folder: +1. Finally, run the `generate-razor-page` command under the `Views` folder: ```bash > abp generate-razor-page @@ -1122,11 +1230,11 @@ app.Use(async (httpContext, next) => }); ``` -![Razor Page](./../images/abp-generate-razor-page.png) +Razor Page #### Options -* ```--version``` or ```-v```: Specifies the version for ABP CLI to be installed. +- `--version` or `-v`: Specifies the version for ABP CLI to be installed. ### generate-jwks @@ -1134,10 +1242,12 @@ Generates an RSA key pair for use with OpenIddict `private_key_jwt` client authe The command produces two files: -| File | Description | -|---|---| -| `.json` | JWKS (JSON Web Key Set) containing the **public key**. Paste this into the **JSON Web Key Set** field of your OpenIddict application in the ABP management UI. | -| `-private.pem` | PKCS#8 PEM **private key**. Store this securely in your client application and use it to sign JWT client assertions. | + +| File | Description | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `.json` | JWKS (JSON Web Key Set) containing the **public key**. Paste this into the **JSON Web Key Set** field of your OpenIddict application in the ABP management UI. | +| `-private.pem` | PKCS#8 PEM **private key**. Store this securely in your client application and use it to sign JWT client assertions. | + > **Security notice:** Never commit the private key file to source control. Add it to `.gitignore`. Only the JWKS (public key) needs to be shared with the authorization server. @@ -1149,11 +1259,11 @@ abp generate-jwks [options] #### Options -* `--output` or `-o`: Output directory. Defaults to the current directory. -* `--key-size` or `-s`: RSA key size in bits. Supported values: `2048` (default), `4096`. -* `--alg`: Signing algorithm. Supported values: `RS256` (default), `RS384`, `RS512`, `PS256`, `PS384`, `PS512`. -* `--kid`: Custom Key ID. Auto-generated if not specified. -* `--file` or `-f`: Output file name prefix. Defaults to `jwks`. Generates `.json` and `-private.pem`. +- `--output` or `-o`: Output directory. Defaults to the current directory. +- `--key-size` or `-s`: RSA key size in bits. Supported values: `2048` (default), `4096`. +- `--alg`: Signing algorithm. Supported values: `RS256` (default), `RS384`, `RS512`, `PS256`, `PS384`, `PS512`. +- `--kid`: Custom Key ID. Auto-generated if not specified. +- `--file` or `-f`: Output file name prefix. Defaults to `jwks`. Generates `.json` and `-private.pem`. #### Examples @@ -1171,9 +1281,7 @@ abp generate-jwks -o ./keys -f myapp #### Workflow 1. Run `abp generate-jwks` to generate the key pair. - 2. Open the ABP OpenIddict application management UI, select your **Confidential** application, choose **JWKS (private_key_jwt)** as the authentication method, and paste the contents of `jwks.json` into the **JSON Web Key Set** field. - 3. In your client application, load the private key from the PEM file and sign JWT client assertions: ```csharp @@ -1223,5 +1331,6 @@ var tokenResponse = await httpClient.RequestClientCredentialsTokenAsync( ## See Also -* [Examples for the new command](./new-command-samples.md) -* [Video tutorial](https://abp.io/video-courses/essentials/abp-cli) +- [Examples for the new command](./new-command-samples.md) +- [Video tutorial](https://abp.io/video-courses/essentials/abp-cli) + diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index b0a76124b4..f82f29d551 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -1866,6 +1866,68 @@ } ] }, + { + "text": "React", + "items": [ + { + "text": "Overview", + "path": "framework/ui/react/index.md", + "isIndex": true + }, + { + "text": "Configuration and Development", + "items": [ + { + "text": "Environment Variables", + "path": "framework/ui/react/environment-variables.md" + }, + { + "text": "Unit Testing", + "path": "framework/ui/react/unit-testing.md" + } + ] + }, + { + "text": "Core Features", + "items": [ + { + "text": "Authorization", + "path": "framework/ui/react/authorization.md" + }, + { + "text": "Localization", + "path": "framework/ui/react/localization.md" + }, + { + "text": "Permission Management", + "path": "framework/ui/react/permission-management.md" + }, + { + "text": "HTTP Requests", + "path": "framework/ui/react/http-requests.md" + } + ] + }, + { + "text": "Customization and Components", + "items": [ + { + "text": "Customization", + "path": "framework/ui/react/customization.md" + }, + { + "text": "Components", + "path": "framework/ui/react/components/index.md", + "isIndex": true + } + ] + }, + { + "text": "Admin Console", + "path": "framework/ui/react/admin-console.md" + } + ] + }, { "text": "React Native", "items": [ @@ -1873,6 +1935,10 @@ "text": "Overview", "path": "framework/ui/react-native", "isIndex": true + }, + { + "text": "Styling with NativeWind", + "path": "framework/ui/react-native/styling-with-nativewind.md" } ] }, diff --git a/docs/en/docs-params.json b/docs/en/docs-params.json index a5665a1215..cd78818f8c 100644 --- a/docs/en/docs-params.json +++ b/docs/en/docs-params.json @@ -12,6 +12,17 @@ "NG": "Angular" } }, + { + "name": "BlazorUI", + "displayName": "Blazor UI Library", + "values": { + "Blazorise": "Blazorise", + "MudBlazor": "MudBlazor" + }, + "dependsOn": { + "UI": ["Blazor", "BlazorServer", "BlazorWebApp", "MAUIBlazor"] + } + }, { "name": "DB", "displayName": "Database", diff --git a/docs/en/framework/infrastructure/background-jobs/index.md b/docs/en/framework/infrastructure/background-jobs/index.md index 2f27055f46..cdc4a1b5fb 100644 --- a/docs/en/framework/infrastructure/background-jobs/index.md +++ b/docs/en/framework/infrastructure/background-jobs/index.md @@ -271,9 +271,9 @@ If multiple applications share the same storage for background jobs and workers Set `ApplicationName` property in `AbpBackgroundJobWorkerOptions` to your application's name: ````csharp -public override void PreConfigureServices(ServiceConfigurationContext context) +public override void ConfigureServices(ServiceConfigurationContext context) { - PreConfigure(options => + Configure(options => { options.ApplicationName = context.Services.GetApplicationName()!; }); diff --git a/docs/en/framework/infrastructure/background-workers/index.md b/docs/en/framework/infrastructure/background-workers/index.md index a8e558cea1..912827ce94 100644 --- a/docs/en/framework/infrastructure/background-workers/index.md +++ b/docs/en/framework/infrastructure/background-workers/index.md @@ -212,9 +212,9 @@ If multiple applications share the same storage for background jobs and workers Set `ApplicationName` property in `AbpBackgroundJobWorkerOptions` to your application's name: ````csharp -public override void PreConfigureServices(ServiceConfigurationContext context) +public override void ConfigureServices(ServiceConfigurationContext context) { - PreConfigure(options => + Configure(options => { options.ApplicationName = context.Services.GetApplicationName()!; }); diff --git a/docs/en/framework/infrastructure/image-manipulation.md b/docs/en/framework/infrastructure/image-manipulation.md index 49a2e74c46..587fab07c7 100644 --- a/docs/en/framework/infrastructure/image-manipulation.md +++ b/docs/en/framework/infrastructure/image-manipulation.md @@ -1,12 +1,12 @@ ```json //[doc-seo] { - "Description": "Learn how to efficiently compress and resize images in your applications using ABP Framework's extensible services powered by ImageSharp and Magick.NET." + "Description": "Learn how to efficiently compress and resize images in your applications using ABP Framework's extensible services powered by ImageSharp, Magick.NET and SkiaSharp." } ``` # Image Manipulation -ABP provides services to compress and resize images and implements these services with popular [ImageSharp](https://sixlabors.com/products/imagesharp/) and [Magick.NET](https://github.com/dlemstra/Magick.NET) libraries. You can use these services in your reusable modules, libraries and applications, so you don't depend on a specific imaging library. +ABP provides services to compress and resize images and implements these services with popular [ImageSharp](https://sixlabors.com/products/imagesharp/), [Magick.NET](https://github.com/dlemstra/Magick.NET) and [SkiaSharp](https://github.com/mono/SkiaSharp) libraries. You can use these services in your reusable modules, libraries and applications, so you don't depend on a specific imaging library. > The image resizer/compressor system is designed to be extensible. You can implement your own image resizer/compressor contributor and use it in your application. @@ -46,10 +46,11 @@ public class YourModule : AbpModule ## Providers -ABP provides two image resizer/compressor implementations out of the box: +ABP provides three image resizer/compressor implementations out of the box: * [Magick.NET](#magick-net-provider) * [ImageSharp](#imagesharp-provider) +* [SkiaSharp](#skiasharp-provider) You should install one of these provides to make it actually working. @@ -334,6 +335,67 @@ Configure(options => }); ``` +## SkiaSharp Provider + +`Volo.Abp.Imaging.SkiaSharp` NuGet package implements the image operations using the [SkiaSharp](https://github.com/mono/SkiaSharp) library. + +## Installation + +You can add this package to your application by either using the [ABP CLI](../../cli) or manually installing it. Using the [ABP CLI](../../cli) is the recommended approach. + +### Using the ABP CLI + +Open a command line terminal in the folder of your project (.csproj file) and type the following command: + +```bash +abp add-package Volo.Abp.Imaging.SkiaSharp +``` + +### Manual Installation + +If you want to manually install; + +1. Add the [Volo.Abp.Imaging.SkiaSharp](https://www.nuget.org/packages/Volo.Abp.Imaging.SkiaSharp) NuGet package to your project: + +``` +dotnet add package Volo.Abp.Imaging.SkiaSharp +``` + +2. Add `AbpImagingSkiaSharpModule` to your [module](../architecture/modularity/basics.md)'s dependency list: + +```csharp +[DependsOn(typeof(AbpImagingSkiaSharpModule))] +public class MyModule : AbpModule +{ + //... +} +``` + +### Configuration + +`SkiaSharpResizerOptions` is an [options object](../fundamentals/options.md) that is used to configure the SkiaSharp image resize system. It has the following properties: + +* `SKSamplingOptions`: The sampling options used by SkiaSharp when resizing. (Default: `SKSamplingOptions.Default`) +* `Quality`: The quality of the encoded image (0-100). (Default: `75`) + +`SkiaSharpCompressOptions` is an [options object](../fundamentals/options.md) that is used to configure the SkiaSharp image compression system. It has the following properties: + +* `Quality`: The quality of the encoded image (0-100). (Default: `75`) + +**Example usage:** + +```csharp +Configure(options => +{ + options.Quality = 80; +}); + +Configure(options => +{ + options.Quality = 60; +}); +``` + ## ASP.NET Core Integration `Volo.Abp.Imaging.AspNetCore` NuGet package defines attributes for controller actions that can automatically compress and/or resize uploaded files. diff --git a/docs/en/framework/infrastructure/text-templating/razor.md b/docs/en/framework/infrastructure/text-templating/razor.md index d2f690c72c..a8bc1c55a6 100644 --- a/docs/en/framework/infrastructure/text-templating/razor.md +++ b/docs/en/framework/infrastructure/text-templating/razor.md @@ -10,6 +10,8 @@ The Razor template is a standard C# class, so you can freely use the functions of C#, such as `dependency injection`, using `LINQ`, custom methods, and even using `Repository`. +> The Razor engine compiles template content into a fully-trusted .NET assembly via Roslyn and executes it in the host process, so editing a Razor template at runtime is functionally equivalent to executing arbitrary server-side code. `RazorTemplateRenderingEngine.IsSandboxed` is therefore `false`, and the [Text Template Management](../../../modules/text-template-management.md) module requires the `TextTemplateManagement.TextTemplates.EditNonSandboxedContents` permission (in addition to `EditContents`) before allowing such templates to be edited via its UI. Grant the related permission only to fully trusted developers/operators. If you need a sandboxed engine for content editors, consider [Scriban](scriban.md), which is configured to honor Scriban's [safe runtime boundaries](https://github.com/scriban/scriban/blob/master/site/docs/runtime/safe-runtime.md) by default. + ## Installation diff --git a/docs/en/framework/infrastructure/text-templating/scriban.md b/docs/en/framework/infrastructure/text-templating/scriban.md index bb72624294..d2bb8e4a26 100644 --- a/docs/en/framework/infrastructure/text-templating/scriban.md +++ b/docs/en/framework/infrastructure/text-templating/scriban.md @@ -7,6 +7,31 @@ # Scriban Integration +## Safe Runtime (Sandbox) + +Scriban's [safe runtime](https://github.com/scriban/scriban/blob/master/site/docs/runtime/safe-runtime.md) builds the practical sandbox out of four boundaries: which globals you expose through `ScriptObject`, which .NET members you allow through the member filter, whether you configure `TemplateContext.TemplateLoader` for `include`, and which `TemplateContext` execution limits you enable. ABP's `ScribanTemplateRenderingEngine` is configured to honor these boundaries by default: + +| Boundary | ABP default | +|----------|-------------| +| Globals exposed | Only the `globalContext` (`Dictionary`) entries, the `model` you pass to `RenderAsync`, and the `L` localization helper. | +| .NET member access | `TemplateContext.MemberFilter` is set to `IsMemberAllowed`, an allowlist that exposes public properties only. Methods, fields, events, and `object`-level members (`GetType`, `ToString`, ...) are not reachable, which closes reflection-based escape paths such as `{%{{{ model.GetType.Assembly.GetType "..." }}}%}`. | +| `TemplateLoader` | Not configured. `include` directives have no template loader and cannot read templates from disk or other sources unless you explicitly wire one up. | +| Execution limits | Scriban's defaults (`LoopLimit = 1000`, `RecursiveLimit = 100`, `LimitToString = 1 MB`, `RegexTimeOut = 10s`). Override `CreateScribanTemplateContext` to tighten these for your own scenarios. | + +The recommended way to expose data to a Scriban template is via `ScriptObject` or `IDictionary` — the keys you put there are exactly what the template can see. When you pass a .NET object as `model`, the `MemberFilter` ensures only properties are exposed, but the safest pattern is to pre-build a dictionary or `ScriptObject` so the surface is fully under your control: + +````csharp +await _templateRenderer.RenderAsync( + "MyTemplate", + model: new Dictionary + { + { "name", user.Name }, + { "email", user.Email } + }); +```` + +If you must pass a .NET object whose methods/fields the template needs to read, override `ScribanTemplateRenderingEngine.IsMemberAllowed` to relax the filter. Only do so when the model objects are trusted and do not carry secrets, since methods and reflection entry points become reachable to whoever can edit the template content. + ## Installation It is suggested to use the [ABP CLI](../../../cli) to install this package. diff --git a/docs/en/framework/ui/blazor/basic-theme.md b/docs/en/framework/ui/blazor/basic-theme.md index 33c0cb7175..f8cd0c7023 100644 --- a/docs/en/framework/ui/blazor/basic-theme.md +++ b/docs/en/framework/ui/blazor/basic-theme.md @@ -10,7 +10,8 @@ ````json //[doc-params] { - "UI": ["Blazor", "BlazorServer"] + "UI": ["Blazor", "BlazorServer"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -20,6 +21,18 @@ The Basic Theme is a theme implementation for the Blazor UI. It is a minimalist > See the [Theming document](theming.md) to learn about themes. +{{if BlazorUI == "MudBlazor"}} + +> **MudBlazor Variant** — When the `--blazor-ui-library mudblazor` option is used, the Basic Theme ships as a MudBlazor variant. Replace `BasicTheme` with `MudBlazorBasicTheme` everywhere in this document (package names, module type names and namespaces). The MudBlazor variant is **not** based on Bootstrap — it uses MudBlazor's Material Design layout components. +> +> Concrete package names you will see when using the MudBlazor variant: +> +> * `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorBasicTheme` +> * `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorBasicTheme.Bundling` +> * Module types: `Abp{...}MudBlazorBasicThemeModule`, `Abp{...}MudBlazorBasicThemeBundlingModule` + +{{end}} + ## Installation If you need to manually this theme, follow the steps below: diff --git a/docs/en/framework/ui/blazor/components/submit-button.md b/docs/en/framework/ui/blazor/components/submit-button.md index 832554c9b1..82dbeeeee9 100644 --- a/docs/en/framework/ui/blazor/components/submit-button.md +++ b/docs/en/framework/ui/blazor/components/submit-button.md @@ -1,12 +1,21 @@ +```json +//[doc-params] +{ + "BlazorUI": ["Blazorise", "MudBlazor"] +} +``` + ```json //[doc-seo] { - "Description": "Explore the `SubmitButton` component in Blazor UI, designed for easy form submissions with localization support and loading indicators." + "Description": "Explore the submit button component in Blazor UI, designed for easy form submissions with localization support and loading indicators." } ``` # Blazor UI: SubmitButton Component +{{if BlazorUI == "Blazorise"}} + `SubmitButton` is a simple wrapper around `Button` component. It is used to be placed inside of page Form or Modal dialogs where it can response to user actions and to be activated as a default button by pressing an ENTER key. Once clicked it will go into the `disabled` state and also it will show a small loading indicator until clicked event is finished. ## Quick Example @@ -29,4 +38,54 @@ Notice that we didn't specify any text, like `Save Changes`. This is because `Su @L["Save"] -``` \ No newline at end of file +``` + +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +The MudBlazor variant of ABP UI does not ship a dedicated `SubmitButton` wrapper. Use the standard `MudButton` together with the typical `Processing`/`Disabled` pattern to disable the button and show a progress indicator while the click handler is running. + +## Quick Example + +```razor + + @if (_processing) + { + + } + @L["Save"] + + +@code { + private bool _processing; + + private async Task SaveAsync() + { + _processing = true; + try + { + // ... your save operation + } + finally + { + _processing = false; + } + } +} +``` + +## Submit on Enter + +When the button is placed inside a `` or a ``, pressing ENTER inside an input control submits the form. To run validation before saving, call `_form.Validate()` first. See the [Forms & Validation](../forms-validation.md) page for details. + +## Use Inside `AbpMudCrudPageBase` + +The MudBlazor CRUD page base (`AbpMudCrudPageBase`) already wires up the standard create/update buttons inside its dialogs and shows a progress indicator while the application service call is running. In most cases you don't need to author a save button by hand; override `OnCreatingEntityAsync` / `OnUpdatingEntityAsync` instead. + +> Check the [MudBlazor button documentation](https://mudblazor.com/components/button) for all available options. + +{{end}} \ No newline at end of file diff --git a/docs/en/framework/ui/blazor/customization-overriding-components.md b/docs/en/framework/ui/blazor/customization-overriding-components.md index 8855ff89bd..c919ed084e 100644 --- a/docs/en/framework/ui/blazor/customization-overriding-components.md +++ b/docs/en/framework/ui/blazor/customization-overriding-components.md @@ -10,7 +10,8 @@ ````json //[doc-params] { - "UI": ["Blazor", "BlazorServer"] + "UI": ["Blazor", "BlazorServer"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -41,6 +42,8 @@ The next step is to create a razor component, like `MyBranding.razor`, in your a The content of the `MyBranding.razor` is shown below: +{{if BlazorUI == "Blazorise"}} + ````html @using Volo.Abp.DependencyInjection {{if UI == "BlazorServer"}} @@ -58,6 +61,33 @@ The content of the `MyBranding.razor` is shown below: ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +The MudBlazor variant uses the LeptonX-based MudBlazor theme by default. The component to override is the `Branding` component shipped by the active MudBlazor theme: + +````html +@using Volo.Abp.DependencyInjection +{{if UI == "BlazorServer"}} +@using Volo.Abp.AspNetCore.Components.Server.MudBlazorLeptonXTheme.Themes.MudBlazorLeptonX +{{end}} +{{if UI == "Blazor"}} +@using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorLeptonXTheme.Themes.MudBlazorLeptonX +{{end}} + +@inherits Branding +@attribute [ExposeServices(typeof(Branding))] +@attribute [Dependency(ReplaceServices = true)] + + + +```` + +> If you are using the MudBlazor BasicTheme or a different MudBlazor theme, replace the namespace with the namespace of that theme's `Themes/` folder. + +{{end}} + Let's explain the code: * `@inherits Branding` line inherits the Branding component defined by the [Basic Theme](basic-theme.md) (in the {{if UI == "BlazorServer"}}`Volo.Abp.AspNetCore.Components.Server.BasicTheme.Themes.Basic`{{end}} {{if UI == "Blazor"}}`Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme.Themes.Basic`{{end}} namespace). @@ -75,6 +105,8 @@ Now, you can run the application to see the result: If you prefer to use code-behind file for the C# code of your component, you can use the attributes in the C# side. +{{if BlazorUI == "Blazorise"}} + **MyBlazor.razor** ````html @@ -113,6 +145,50 @@ namespace MyProject.Blazor.Components } ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +**MyBlazor.razor** + +````html +{{if UI == "BlazorServer"}} +@using Volo.Abp.AspNetCore.Components.Server.MudBlazorLeptonXTheme.Themes.MudBlazorLeptonX +{{end}} +{{if UI == "Blazor"}} +@using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorLeptonXTheme.Themes.MudBlazorLeptonX +{{end}} +@inherits Branding + + + +```` + +**MyBlazor.razor.cs** + +````csharp +{{if UI == "BlazorServer"}} +using Volo.Abp.AspNetCore.Components.Server.MudBlazorLeptonXTheme.Themes.MudBlazorLeptonX; +{{end}} +{{if UI == "Blazor"}} +using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorLeptonXTheme.Themes.MudBlazorLeptonX; +{{end}} + +using Volo.Abp.DependencyInjection; + +namespace MyProject.Blazor.Components +{ + [ExposeServices(typeof(Branding))] + [Dependency(ReplaceServices = true)] + public partial class MyBranding + { + + } +} +```` + +{{end}} + ## Theming The [Theming](theming.md) system allows you to build your own theme. You can create your theme from scratch or get the [Basic Theme](basic-theme.md) and change however you like. diff --git a/docs/en/framework/ui/blazor/data-table-column-extensions.md b/docs/en/framework/ui/blazor/data-table-column-extensions.md index b89b825201..bb6bc6eeb0 100644 --- a/docs/en/framework/ui/blazor/data-table-column-extensions.md +++ b/docs/en/framework/ui/blazor/data-table-column-extensions.md @@ -1,3 +1,10 @@ +```json +//[doc-params] +{ + "BlazorUI": ["Blazorise", "MudBlazor"] +} +``` + ```json //[doc-seo] { @@ -102,6 +109,8 @@ public class CustomTableColumn Navigate to the razor file and paste the following code. +{{if BlazorUI == "Blazorise"}} + ```csharp @using System @using Volo.Abp.Identity @@ -116,6 +125,27 @@ else } ``` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +```razor +@using Volo.Abp.Identity + +@if (Data.As().EmailConfirmed) +{ + +} +else +{ + +} +``` + +> When using MudBlazor, the standard data grid in module pages is `AbpMudExtensibleDataGrid`. You can replace `Component = typeof(CustomTableColumn)` exactly the same way as in Blazorise; the column system is shared across both UI libraries. + +{{end}} + Navigate back to the `CustomizedUserManagement` class, and use `Component` property to specify the custom blazor component. ```csharp diff --git a/docs/en/framework/ui/blazor/entity-action-extensions.md b/docs/en/framework/ui/blazor/entity-action-extensions.md index ab3d4b3cba..78d16f7327 100644 --- a/docs/en/framework/ui/blazor/entity-action-extensions.md +++ b/docs/en/framework/ui/blazor/entity-action-extensions.md @@ -1,3 +1,10 @@ +```json +//[doc-params] +{ + "BlazorUI": ["Blazorise", "MudBlazor"] +} +``` + ```json //[doc-seo] { @@ -95,6 +102,8 @@ Here, the list of the properties that you use in the `EntityAction`. #### Example +{{if BlazorUI == "Blazorise"}} + ```csharp var clickMeAction = new EntityAction() { @@ -115,3 +124,33 @@ var clickMeAction = new EntityAction() } }; ``` + +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +```csharp +var clickMeAction = new EntityAction() +{ + Text = "Click Me!", + Clicked = (data) => + { + //TODO: Write your custom code + + return Task.CompletedTask; + }, + Color = MudBlazor.Color.Error, + Icon = MudBlazor.Icons.Material.Filled.PanTool, + ConfirmationMessage = (data) => "Are you sure you want to click to the action?", + Visible = (data) => + { + //TODO: Write your custom visibility action + //var selectedUser = data.As(); + return true; + } +}; +``` + +> The MudBlazor variant uses `MudBlazor.Color` enum values (e.g. `Color.Primary`, `Color.Error`, `Color.Success`) for `Color`, and Material Icon constants (e.g. `Icons.Material.Filled.Edit`) for `Icon`. The `EntityAction` model itself is shared with Blazorise; only the values you put inside it change. + +{{end}} diff --git a/docs/en/framework/ui/blazor/error-handling.md b/docs/en/framework/ui/blazor/error-handling.md index 2fae4ff38d..211c56f25e 100644 --- a/docs/en/framework/ui/blazor/error-handling.md +++ b/docs/en/framework/ui/blazor/error-handling.md @@ -10,7 +10,8 @@ ````json //[doc-params] { - "UI": ["Blazor", "BlazorServer"] + "UI": ["Blazor", "BlazorServer"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -36,7 +37,9 @@ There are different type of `Exception` classes handled differently by the ABP. **Example** -````csharp +{{if BlazorUI == "Blazorise"}} + +````razor @page "/" @using Volo.Abp @@ -60,11 +63,41 @@ There are different type of `Exception` classes handled differently by the ABP. {{end}} +{{if BlazorUI == "MudBlazor"}} + +````razor +@page "/" +@using Volo.Abp + +Throw test exception + +@code +{ + private async Task TestException() + { + try + { + throw new UserFriendlyException("A user friendly error message!"); + } + catch(UserFriendlyException ex) + { + await HandleErrorAsync(ex); + } + } +} +```` + +{{end}} + +{{end}} + {{if UI == "Blazor"}} **Example** -````csharp +{{if BlazorUI == "Blazorise"}} + +````razor @page "/" @using Volo.Abp @@ -78,6 +111,28 @@ There are different type of `Exception` classes handled differently by the ABP. } } ```` + +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````razor +@page "/" +@using Volo.Abp + +Throw test exception + +@code +{ + private void TestException() + { + throw new UserFriendlyException("A user friendly error message!"); + } +} +```` + +{{end}} + {{end}} ABP automatically handle the exception and show an error message to the user: diff --git a/docs/en/framework/ui/blazor/forms-validation.md b/docs/en/framework/ui/blazor/forms-validation.md index 7fb1423e77..c7f799c484 100644 --- a/docs/en/framework/ui/blazor/forms-validation.md +++ b/docs/en/framework/ui/blazor/forms-validation.md @@ -1,12 +1,21 @@ +```json +//[doc-params] +{ + "BlazorUI": ["Blazorise", "MudBlazor"] +} +``` + ```json //[doc-seo] { - "Description": "Learn how to implement form validation in ABP Blazor UI using Blazorise's validation infrastructure with practical examples." + "Description": "Learn how to implement form validation in ABP Blazor UI using Blazorise or MudBlazor with practical examples." } ``` # Blazor UI: Forms & Validation +{{if BlazorUI == "Blazorise"}} + ABP Blazor UI is based on the [Blazorise](https://blazorise.com/docs) and does not have a built-in form validation infrastructure. However, you can use the [Blazorise validation infrastructure](https://blazorise.com/docs/components/validation) to validate your forms. ## Sample @@ -44,4 +53,89 @@ _The example is provided by official Blazorise documentation._ } ``` -> Check the [Blazorise documentation](https://blazorise.com/docs/components/validation) for more information and examples. \ No newline at end of file +> Check the [Blazorise documentation](https://blazorise.com/docs/components/validation) for more information and examples. + +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +ABP Blazor UI built on top of [MudBlazor](https://mudblazor.com) uses MudBlazor's built-in form components and validation infrastructure. MudBlazor accepts a `ValidationAttribute` (e.g. `[Required]`, `[EmailAddress]` from ASP.NET Core's `DataAnnotations`) on the input's `Validation` parameter, plus custom `Func` / `Func>` delegates. FluentValidation can be plugged in the same way. + +## Sample + +The most common pattern is wrapping inputs in a `` and binding the form's validation state through `IsValid`: + +> Standard MudBlazor and ABP usings (`@using MudBlazor`, `@using Volo.Abp.MudBlazorUI`, etc.) come from the project's `_Imports.razor`. The example below only adds the additional usings needed for validation. + +```razor +@using System.ComponentModel.DataAnnotations + + + + + + + + + Submit + + + + +@code { + private MudForm _form; + private bool _isValid; + private SampleModel _model = new(); + + private async Task SubmitAsync() + { + await _form.Validate(); + if (_isValid) + { + // ... + } + } + + public class SampleModel + { + [Required] + public string Name { get; set; } + + [Required, EmailAddress] + public string Email { get; set; } + } +} +``` + +### Inputs Used in CRUD Pages + +ABP's MudBlazor CRUD pages (see `AbpMudCrudPageBase`) use a `` containing a `` and standard MudBlazor inputs: + +* `` / `` for text and multi-line text +* `` / `` for dropdowns +* `` for booleans +* `` / `` for date and time +* `` for numbers + +`AbpMudCrudPageBase.CreateEntityAsync` and `UpdateEntityAsync` validate the form for you (`CreateFormRef.Validate()` / `EditFormRef.Validate()`) and only call the corresponding hook when the form is valid. To inject custom logic before the application service call, override `OnCreatingEntityAsync` / `OnUpdatingEntityAsync` (do **not** re-validate inside the override): + +```csharp +protected override Task OnCreatingEntityAsync() +{ + // mutate NewEntity here if needed + return base.OnCreatingEntityAsync(); +} +``` + +> Check the [MudBlazor documentation](https://mudblazor.com/components/form) for the full list of validation modes and the [MudBlazor inputs reference](https://mudblazor.com/components/textfield). + +{{end}} \ No newline at end of file diff --git a/docs/en/framework/ui/blazor/overall.md b/docs/en/framework/ui/blazor/overall.md index 23077e307e..f5d5cf7b78 100644 --- a/docs/en/framework/ui/blazor/overall.md +++ b/docs/en/framework/ui/blazor/overall.md @@ -1,3 +1,10 @@ +```json +//[doc-params] +{ + "BlazorUI": ["Blazorise", "MudBlazor"] +} +``` + ```json //[doc-seo] { @@ -95,6 +102,8 @@ Currently, three themes are **officially provided**: There are a set of standard libraries that comes pre-installed and supported by all the themes: +{{if BlazorUI == "Blazorise"}} + * [Twitter Bootstrap](https://getbootstrap.com/) as the fundamental HTML/CSS framework. * [Blazorise](https://github.com/stsrki/Blazorise) as a component library that supports the Bootstrap and adds extra components like Data Grid and Tree. * [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library. @@ -106,6 +115,22 @@ These libraries are selected as the base libraries and available to the applicat > Beginning from June, 2021, the Blazorise library has dual licenses; open source & commercial. Based on your yearly revenue, you may need to buy a commercial license. See [this post](https://blazorise.com/news/announcing-2022-blazorise-plans-and-pricing-updates) to learn more. The Blazorise license is bundled with ABP and commercial customers doesn't need to buy an extra Blazorise license. +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +* [MudBlazor](https://mudblazor.com/) as the component library, providing a complete set of Material Design components built natively for Blazor (form controls, data grid, dialogs, snackbars, dates, etc.). +* [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library. +* [Flag Icon](https://github.com/lipis/flag-icons) as a library to show flags of countries. + +These libraries are selected as the base libraries and available to the applications and modules. + +The MudBlazor variant ships its own theming, dialog, snackbar and popover providers (see [Theming](theming.md)). The MudBlazor library is MIT-licensed and is bundled with ABP at no extra cost. + +> Bootstrap is **not** required when using MudBlazor; MudBlazor brings its own layout and component styles. + +{{end}} + ### The Layout The themes provide the layout. So, you have a responsive layout with the standard features already implemented. The screenshot below has taken from the layout of the [Basic Theme](basic-theme.md): diff --git a/docs/en/framework/ui/blazor/page-header.md b/docs/en/framework/ui/blazor/page-header.md index ed2e16903d..5c03af6aa4 100644 --- a/docs/en/framework/ui/blazor/page-header.md +++ b/docs/en/framework/ui/blazor/page-header.md @@ -1,3 +1,10 @@ +```json +//[doc-params] +{ + "BlazorUI": ["Blazorise", "MudBlazor"] +} +``` + ```json //[doc-seo] { @@ -30,6 +37,8 @@ Breadcrumbs can be added using the `BreadcrumbItems` property. **Example: Add Language Management to the breadcrumb items.** +{{if BlazorUI == "Blazorise"}} + Create a collection of `Volo.Abp.BlazoriseUI.BreadcrumbItem` objects and set the collection to the `BreadcrumbItems` parameter. ```csharp @@ -44,6 +53,31 @@ public partial class Index } ``` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +Create a collection of `MudBlazor.BreadcrumbItem` objects and set the collection to the `BreadcrumbItems` parameter. The MudBlazor `BreadcrumbItem` constructor takes `(string text, string href, bool disabled = false, string icon = null)`. + +```csharp +using MudBlazor; + +public partial class Index +{ + protected List BreadcrumbItems { get; } = new(); + + protected override void OnInitialized() + { + BreadcrumbItems.Add(new BreadcrumbItem( + text: "Language Management", + href: null, + disabled: true)); + } +} +``` + +{{end}} + Navigate back to the razor page. ```csharp @@ -57,19 +91,38 @@ The theme then renders the breadcrumb. An example render result can be: * The Home icon is rendered by default. Set `BreadcrumbShowHome` to `false` to hide it. * Breadcrumb items will be activated based on current navigation. Set `BreadcrumbShowCurrent` to `false` to disable it. -You can add as many items as you need. `BreadcrumbItem` constructor gets three parameters: +You can add as many items as you need. + +{{if BlazorUI == "Blazorise"}} + +The `Volo.Abp.BlazoriseUI.BreadcrumbItem` constructor gets three parameters: * `text`: The text to show for the breadcrumb item. * `url` (optional): A URL to navigate to, if the user clicks to the breadcrumb item. * `icon` (optional): An icon class (like `fas fa-user-tie` for Font-Awesome) to show with the `text`. +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +The `MudBlazor.BreadcrumbItem` constructor takes: + +* `text`: The text to show for the breadcrumb item. +* `href`: A URL to navigate to (use `null` for the current page). +* `disabled` (optional): When `true`, the item is rendered as the current/non-clickable item. +* `icon` (optional): A Material icon (e.g. `Icons.Material.Filled.Language`). + +{{end}} + ## Page Toolbar Page toolbar can be set using the `Toolbar` property. **Example: Add a "New Item" toolbar item to the page toolbar.** -Create a `PageToolbar` object and define toolbar items using the `AddButton` extension method. +Create a `PageToolbar` object and define toolbar items using the `AddButton` extension method. + +{{if BlazorUI == "Blazorise"}} ```csharp public partial class Index @@ -87,6 +140,28 @@ public partial class Index } ``` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +```csharp +public partial class Index +{ + protected PageToolbar Toolbar { get; } = new(); + + protected override void OnInitialized() + { + Toolbar.AddButton("New Item", () => + { + //Write your click action here + return Task.CompletedTask; + }, icon: MudBlazor.Icons.Material.Filled.Add); + } +} +``` + +{{end}} + Navigate back to the razor page and set the `Toolbar` parameter. ```csharp diff --git a/docs/en/framework/ui/blazor/page-layout.md b/docs/en/framework/ui/blazor/page-layout.md index 75666fd17a..abd32745d0 100644 --- a/docs/en/framework/ui/blazor/page-layout.md +++ b/docs/en/framework/ui/blazor/page-layout.md @@ -1,3 +1,10 @@ +```json +//[doc-params] +{ + "BlazorUI": ["Blazorise", "MudBlazor"] +} +``` + ```json //[doc-seo] { @@ -36,6 +43,8 @@ Indicates current selected menu item name. Menu item name should match a unique Menu item name can be set on runtime too. +{{if BlazorUI == "Blazorise"}} + ```html @inject PageLayout PageLayout @@ -49,6 +58,25 @@ Menu item name can be set on runtime too. } ``` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +```razor +@inject PageLayout PageLayout + +Change Menu + +@code{ + protected void SetCategoriesMenuAsSelected() + { + PageLayout.MenuItemName = "MyProjectName.Categories"; + } +} +``` + +{{end}} + ![leptonx selected menu item](../../../images/leptonx-selected-menu-item-example.gif) @@ -57,6 +85,9 @@ Menu item name can be set on runtime too. ## BreadCrumbs BreadCrumbItems are used to render breadcrumbs in the PageHeader. + +{{if BlazorUI == "Blazorise"}} + ```csharp @inject PageLayout PageLayout @@ -65,6 +96,21 @@ BreadCrumbItems are used to render breadcrumbs in the PageHeader. } ``` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +```razor +@using MudBlazor +@inject PageLayout PageLayout + +@{ + PageLayout.BreadcrumbItems.Add(new BreadcrumbItem("My Page", "/my-page")); +} +``` + +{{end}} + ## Toolbar ToolbarItems are used to render action toolbar items in the PageHeader. diff --git a/docs/en/framework/ui/blazor/page-toolbar-extensions.md b/docs/en/framework/ui/blazor/page-toolbar-extensions.md index 9c96bf90f7..96c48b000d 100644 --- a/docs/en/framework/ui/blazor/page-toolbar-extensions.md +++ b/docs/en/framework/ui/blazor/page-toolbar-extensions.md @@ -1,3 +1,10 @@ +```json +//[doc-params] +{ + "BlazorUI": ["Blazorise", "MudBlazor"] +} +``` + ```json //[doc-seo] { @@ -27,6 +34,8 @@ We will use the [component override system](customization-overriding-components. Here, the content of the overridden `SetToolbarItemsAsync` method. +{{if BlazorUI == "Blazorise"}} + ```csharp protected override async ValueTask SetToolbarItemsAsync() { @@ -38,10 +47,31 @@ protected override async ValueTask SetToolbarItemsAsync() }, "file-import", Blazorise.Color.Secondary); } ``` + +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +```csharp +protected override async ValueTask SetToolbarItemsAsync() +{ + await base.SetToolbarItemsAsync(); + Toolbar.AddButton("Import users from excel", () => + { + //TODO: Write your custom code + return Task.CompletedTask; + }, MudBlazor.Icons.Material.Filled.Upload, MudBlazor.Color.Secondary); +} +``` + +{{end}} + > In order to use the `AddButton` extension method, you need to add a using statement for the `Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars` namespace. Here, the entire content of the file. +{{if BlazorUI == "Blazorise"}} + ```csharp using System.Threading.Tasks; using Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars; @@ -67,6 +97,37 @@ namespace MyCompanyName.MyProjectName.Blazor.Pages.Identity } ``` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +```csharp +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Components.Web.Theming.PageToolbars; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Identity.Blazor.Pages.Identity; + +namespace MyCompanyName.MyProjectName.Blazor.Pages.Identity +{ + [ExposeServices(typeof(UserManagement))] + [Dependency(ReplaceServices = true)] + public class CustomizedUserManagement : UserManagement + { + protected override async ValueTask SetToolbarItemsAsync() + { + await base.SetToolbarItemsAsync(); + Toolbar.AddButton("Import users from excel", () => + { + //TODO: Write your custom code + return Task.CompletedTask; + }, MudBlazor.Icons.Material.Filled.Upload, MudBlazor.Color.Secondary); + } + } +} +``` + +{{end}} + When you run the application, you will see the button added next to the current button list. There are some other parameters of the `AddButton` method (for example, use `Order` to set the order of the button component relative to the other components). ## Advanced Use Cases @@ -83,9 +144,21 @@ For this example, we've created a `MyToolbarComponent` component under the `/Pag `MyToolbarComponent.razor` content: -````csharp +{{if BlazorUI == "Blazorise"}} + +````razor ```` + +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````razor +CLICK ME +```` + +{{end}} We will leave the `MyToolbarComponent.razor.cs` file empty. Then you can add the `MyToolbarComponent` to the user management page toolbar: diff --git a/docs/en/framework/ui/blazor/theming.md b/docs/en/framework/ui/blazor/theming.md index 7ded33608f..1fff0a057f 100644 --- a/docs/en/framework/ui/blazor/theming.md +++ b/docs/en/framework/ui/blazor/theming.md @@ -10,7 +10,8 @@ ````json //[doc-params] { - "UI": ["Blazor", "BlazorServer"] + "UI": ["Blazor", "BlazorServer"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -52,6 +53,8 @@ All the themes must depend on the [Volo.Abp.AspNetCore.Components.Server.Theming {{end}} +{{if BlazorUI == "Blazorise"}} + * [Twitter Bootstrap](https://getbootstrap.com/) as the fundamental HTML/CSS framework. * [Blazorise](https://github.com/stsrki/Blazorise) as a component library that supports the Bootstrap and adds extra components like Data Grid and Tree. * [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library. @@ -61,6 +64,31 @@ These libraries are selected as the base libraries and available to the applicat > Bootstrap's JavaScript part is not used since the Blazorise library already provides the necessary functionalities to the Bootstrap components in a native way. +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +* [MudBlazor](https://mudblazor.com/) as the component library, providing a Material Design component set built natively for Blazor (form controls, data grid, dialogs, snackbars, dates, etc.). +* [FontAwesome](https://fontawesome.com/) as the fundamental CSS font library. +* [Flag Icon](https://github.com/lipis/flag-icons) as a library to show flags of countries. + +These libraries are selected as the base libraries and available to the applications and modules. + +A theme using the MudBlazor variant must place the four MudBlazor providers in the layout root so dialogs, snackbars and popovers work everywhere: + +```razor + + + + + +@Body +``` + +The provided themes (`Volo.Abp.AspNetCore.Components.Server.MudBlazorLeptonXTheme`, `Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorLeptonXTheme`, etc.) ship these providers as part of their layout templates. + +{{end}} + ### The Layout All themes must define a layout for the application. The following image shows the user management page in the [Basic Theme](basic-theme.md) application layout: @@ -90,6 +118,8 @@ A theme is simply a Razor Class Library. The easiest way of creating a new theme is adding [Basic Theme Source Code](https://github.com/abpframework/abp/tree/dev/modules/basic-theme) module with source codes and customizing it. +{{if BlazorUI == "Blazorise"}} + {{if UI == "Blazor"}} ```bash abp add-package Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme --with-source-code --add-to-solution-file @@ -102,6 +132,24 @@ abp add-package Volo.Abp.AspNetCore.Components.Server.BasicTheme --with-source-c ``` {{end}} +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +{{if UI == "Blazor"}} +```bash +abp add-package Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme --with-source-code --add-to-solution-file +``` +{{end}} + +{{if UI == "BlazorServer"}} +```bash +abp add-package Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme --with-source-code --add-to-solution-file +``` +{{end}} + +{{end}} + ### Global Styles / Scripts A theme generally needs to add a global style to the page. ABP provides a system to manage the [Global Styles and Scripts](global-scripts-styles.md). A theme can implement the `IBundleContributor` to add global style or script files to the page. diff --git a/docs/en/framework/ui/index.md b/docs/en/framework/ui/index.md index d892c63c69..ca33ca9ff1 100644 --- a/docs/en/framework/ui/index.md +++ b/docs/en/framework/ui/index.md @@ -1,7 +1,7 @@ ```json //[doc-seo] { - "Description": "Explore ABP's UI options, including MVC, Blazor, Angular, React Native, and MAUI, to build dynamic applications effortlessly." + "Description": "Explore ABP's UI options, including React, MVC, Blazor, Angular, React Native, and MAUI, to build dynamic applications effortlessly." } ``` @@ -9,6 +9,7 @@ ABP provides several options for building the user interface (UI) in your applications. Here are some of the officially supported UI options you can use with ABP: +* [React](./react/index.md) *(modern template system only)* * [MVC / Razor Pages](./mvc-razor-pages/overall.md) * [Blazor](./blazor/overall.md) * [Angular](./angular/quick-start.md) diff --git a/docs/en/framework/ui/mvc-razor-pages/overall.md b/docs/en/framework/ui/mvc-razor-pages/overall.md index fc5b0b0ac0..425c94d2d9 100644 --- a/docs/en/framework/ui/mvc-razor-pages/overall.md +++ b/docs/en/framework/ui/mvc-razor-pages/overall.md @@ -17,8 +17,8 @@ ABP provides a convenient and comfortable way of creating web applications using ASP.NET Core provides two models for UI development: -* **[MVC (Model-View-Controller)](https://docs.microsoft.com/en-us/aspnet/core/mvc/)** is the classic way that exists from the version 1.0. This model can be used to create UI pages/components and HTTP APIs. -* **[Razor Pages](https://docs.microsoft.com/en-us/aspnet/core/razor-pages/)** was introduced with the ASP.NET Core 2.0 as a new way to create web pages. +- **[MVC (Model-View-Controller)](https://docs.microsoft.com/en-us/aspnet/core/mvc/)** is the classic way that exists from the version 1.0. This model can be used to create UI pages/components and HTTP APIs. +- **[Razor Pages](https://docs.microsoft.com/en-us/aspnet/core/razor-pages/)** was introduced with the ASP.NET Core 2.0 as a new way to create web pages. **ABP supports both** of the MVC and the Razor Pages models. However, it is suggested to create the **UI pages with Razor Pages** approach and use the **MVC model to build HTTP APIs**. So, all the pre-build modules, samples and the documentation is based on the Razor Pages for the UI development, while you can always apply the MVC pattern to create your own pages. @@ -32,18 +32,18 @@ The [application startup template](../../../solution-templates/application-modul ABP provides a complete [Theming](theming.md) system with the following goals: -* Reusable [application modules](../../../modules) are developed **theme-independent**, so they can work with any UI theme. -* UI theme is **decided by the final application**. -* The theme is distributed via NuGet/NPM packages, so it is **easily upgradable**. -* The final application can **customize** the selected theme. +- Reusable [application modules](../../../modules) are developed **theme-independent**, so they can work with any UI theme. +- UI theme is **decided by the final application**. +- The theme is distributed via NuGet/NPM packages, so it is **easily upgradable**. +- The final application can **customize** the selected theme. ### Current Themes Currently, three themes are **officially provided**: -* The [Basic Theme](Basic-Theme.md) is the minimalist theme with the plain Bootstrap style. It is **open source and free**. -* The [Lepton Theme](https://abp.io/themes) is a **commercial** theme developed by the core ABP team and is a part of the [ABP](https://abp.io/) license. -* The [LeptonX Theme](https://x.leptontheme.com/) is a theme that has both [commercial](../../../ui-themes/lepton-x/mvc.md) and [lite](../../../ui-themes/lepton-x-lite/asp-net-core.md) choices. +- The [Basic Theme](Basic-Theme.md) is the minimalist theme with the plain Bootstrap style. It is **open source and free**. +- The [Lepton Theme](https://abp.io/themes) is a **commercial** theme developed by the core ABP team and is a part of the [ABP](https://abp.io/) license. +- The [LeptonX Theme](https://x.leptontheme.com/) is a theme that has both [commercial](../../../ui-themes/lepton-x/mvc.md) and [lite](../../../ui-themes/lepton-x-lite/asp-net-core.md) choices. There are also some community-driven themes for the ABP (you can search on the web). @@ -72,7 +72,7 @@ You can use these libraries directly in your applications, without needing to ma The themes provide the standard layouts. So, you have responsive layouts with the standard features already implemented. The screenshot below has taken from the Application Layout of the [Basic Theme](basic-theme.md): -![basic-theme-application-layout](../../../images/basic-theme-account-layout.png) +basic-theme-application-layout See the [Theming](theming.md) document for more layout options and other details. @@ -90,13 +90,13 @@ Dynamic JavaScript API Client Proxy system allows you to consume your server sid **Example: Get a list of authors from the server** -````js +```js acme.bookStore.authors.author.getList({ maxResultCount: 10 }).then(function(result){ console.log(result.items); }); -```` +``` `acme.bookStore.authors.author.getList` is an auto-generated function that internally makes an AJAX call to the server. @@ -108,7 +108,7 @@ ABP makes it easier & type safe to write Bootstrap HTML. **Example: Render a Bootstrap modal** -````html +```html @@ -116,7 +116,7 @@ ABP makes it easier & type safe to write Bootstrap HTML. -```` +``` See the [Tag Helpers](tag-helpers) document for more. @@ -126,9 +126,9 @@ ABP provides `abp-dynamic-form` and `abp-input` tag helpers to dramatically simp **Example: Use `abp-dynamic-form` to create a complete form based on a model** -````html +```html -```` +``` See the [Forms & Validation](forms-validation.md) document for details. @@ -136,14 +136,14 @@ See the [Forms & Validation](forms-validation.md) document for details. ABP provides a flexible and modular Bundling & Minification system to create bundles and minify style/script files on runtime. -````html +```html -```` +``` Also, Client Side Package Management system offers a modular and consistent way of managing 3rd-party library dependencies. @@ -157,11 +157,11 @@ See the [Bundling & Minification](bundling-minification.md) and [Client Side Pac ABP provides a lot of built-in solutions to common application requirements; -* [Widget System](widgets.md) can be used to create reusable widgets & create dashboards. -* [Page Alerts](page-alerts.md) makes it easy to show alerts to the user. -* [Modal Manager](modals.md) provides a simple way to build and use modals. -* [Data Tables](data-tables.md) integration makes straightforward to create data grids. +- [Widget System](widgets.md) can be used to create reusable widgets & create dashboards. +- [Page Alerts](page-alerts.md) makes it easy to show alerts to the user. +- [Modal Manager](modals.md) provides a simple way to build and use modals. +- [Data Tables](data-tables.md) integration makes straightforward to create data grids. ## Customization -There are a lot of ways to customize the theme and the UIs of the pre-built modules. You can override components, pages, static resources, bundles and more. See the [User Interface Customization Guide](customization-user-interface.md). +There are a lot of ways to customize the theme and the UIs of the pre-built modules. You can override components, pages, static resources, bundles and more. See the [User Interface Customization Guide](customization-user-interface.md). \ No newline at end of file diff --git a/docs/en/framework/ui/react-native/index.md b/docs/en/framework/ui/react-native/index.md index 862d836ac4..c0957d5426 100644 --- a/docs/en/framework/ui/react-native/index.md +++ b/docs/en/framework/ui/react-native/index.md @@ -5,12 +5,12 @@ } ``` -````json +```json //[doc-params] { "Architecture": ["Monolith", "Tiered", "Microservice"] } -```` +``` # Getting Started with React Native @@ -18,7 +18,7 @@ The ABP platform provides a basic [React Native](https://reactnative.dev/) startup template to develop mobile applications **integrated with your ABP-based backends**. -![React Native gif](../../../images/react-native-introduction.gif) +> The startup template UI is built with **[NativeWind v4](https://www.nativewind.dev/)** (Tailwind CSS for React Native) on top of a shadcn-inspired neutral palette, with full **light/dark mode** support. See [Styling with NativeWind](styling-with-nativewind.md) for the styling system reference. ## How to Prepare Development Environment @@ -28,10 +28,9 @@ Please follow the steps below to prepare your development environment for React 2. **[Optional] Install Yarn:** You can install Yarn v1 (not v2) by following the instructions on [the installation page](https://classic.yarnpkg.com/en/docs/install). Yarn v1 provides a better developer experience compared to npm v6 and below. You can skip this step and use npm, which is built into Node.js. 3. **[Optional] Install VS Code:** [VS Code](https://code.visualstudio.com/) is a free, open-source IDE that works seamlessly with TypeScript. While you can use any IDE, including Visual Studio or Rider, VS Code typically provides the best developer experience for React Native projects. 4. **[Optional] Install an Emulator/Simulator:** If you want to test on Android emulators or iOS simulators (instead of using the Web View method), you'll need to install one of the following: - - **Android Studio & Emulator:** Install [Android Studio](https://developer.android.com/studio) and set up an Android Virtual Device (AVD) through the AVD Manager. You can follow the [Android Studio Emulator guide](https://docs.expo.dev/workflow/android-studio-emulator/) on expo.io documentation. - - **Xcode & iOS Simulator:** On macOS, install [Xcode](https://developer.apple.com/xcode/) from the App Store, which includes the iOS Simulator. You can follow the [iOS Simulator guide](https://docs.expo.dev/workflow/ios-simulator/) on expo.io documentation. - - > **Note:** The Web View method (recommended for quick testing) doesn't require an emulator or simulator. If you prefer a CLI-based approach for Android, you can check the [setting up android emulator without android studio](setting-up-android-emulator.md) guide as an alternative. + - **Android Studio & Emulator:** Install [Android Studio](https://developer.android.com/studio) and set up an Android Virtual Device (AVD) through the AVD Manager. You can follow the [Android Studio Emulator guide](https://docs.expo.dev/workflow/android-studio-emulator/) on expo.io documentation. + - **Xcode & iOS Simulator:** On macOS, install [Xcode](https://developer.apple.com/xcode/) from the App Store, which includes the iOS Simulator. You can follow the [iOS Simulator guide](https://docs.expo.dev/workflow/ios-simulator/) on expo.io documentation. + > **Note:** The Web View method (recommended for quick testing) doesn't require an emulator or simulator. If you prefer a CLI-based approach for Android, you can check the [setting up android emulator without android studio](setting-up-android-emulator.md) guide as an alternative. ## How to Start a New React Native Project @@ -41,7 +40,7 @@ You have multiple options to initiate a new React Native project that works with ABP Studio is the most convenient and flexible way to create a React Native application based on the ABP framework. Follow the [tool documentation](../../../studio) and select the option below: -![React Native option](../../../images/react-native-option.png) +React Native option ### 2. Using ABP CLI @@ -61,7 +60,6 @@ This command creates a solution containing an **Angular** or **MVC** project (de Before running the React Native application, install the dependencies by running `yarn install` or `npm install` in the `react-native` directory. - ### Web View (Recommended - Quickest Method) The quickest way to test the application is by using the web view. While testing on a physical device is also supported, we recommend using [local HTTPS development](https://docs.expo.dev/guides/local-https-development/) as it requires fewer backend modifications. @@ -69,28 +67,21 @@ The quickest way to test the application is by using the web view. While testing Follow these steps to set up the web view: 1. Navigate to the `react-native` directory and start the application by running: - ```bash + ```bash yarn web - ``` - + ``` 2. Generate SSL certificates by running the following command in a separate directory: - ```bash + ```bash mkcert localhost - ``` - + ``` 3. Set up the local proxy by running: - ```bash + ```bash yarn create:local-proxy - ``` + ``` The default port is `443`. To use a different port, specify the `SOURCE_PORT` environment variable: - ```bash - SOURCE_PORT=8443 yarn create:local-proxy - ``` - 4. If you changed the port in the previous step, update the `apiUrl` in `Environment.ts` accordingly. - 5. Update the mobile application settings in the database and re-run the migrations. If you specified a custom port, ensure the port is updated in the configuration as well: - ```json + ```json "OpenIddict": { "Applications": { "MyApplication_Mobile": { @@ -99,7 +90,7 @@ Follow these steps to set up the web view: } } } - ``` + ``` ### Running on Emulator/Simulator @@ -111,17 +102,17 @@ If you prefer to test on an Android emulator or iOS simulator, you'll need to co {{ if Architecture == "Monolith" }} -![react native monolith environment local IP](../../../images/react-native-monolith-environment-local-ip.png) +react native monolith environment local IP {{ else if Architecture == "Tiered" }} -![react native tiered environment local IP](../../../images/react-native-tiered-environment-local-ip.png) +react native tiered environment local IP > Make sure that `issuer` matches the running address of the `.AuthServer` project, `apiUrl` matches the running address of the `.HttpApi.Host` or `.Web` project. {{ else }} -![react native microservice environment local IP](../../../images/react-native-environment-local-ip.png) +react native microservice environment local IP > Make sure that `issuer` matches the running address of the `.AuthServer` project, `apiUrl` matches the running address of the `.AuthServer` project. @@ -131,25 +122,91 @@ If you prefer to test on an Android emulator or iOS simulator, you'll need to co > The React Native application was generated with [Expo](https://expo.io/). Expo is a set of tools built around React Native to help you quickly start an app, and it includes many features. -![expo-cli-options](../../../images/rn-options.png) +expo-cli-options In the image above, you can start the application on an Android emulator, an iOS simulator, or a physical phone by scanning the QR code with the [Expo Client](https://expo.io/tools#client) or by choosing the corresponding option. ### Expo -![React Native login screen on iPhone 16](../../../images/rn-login-iphone.png) +Press **i** to open the iOS simulator, or scan the QR code from the Expo CLI with your phone to run on a physical device. ### Android Studio 1. Start the emulator in **Android Studio** before running the `yarn start` or `npm start` command. 2. Press **a** to open in Android Studio. -![React Native login screen on Android Device](../../../images/rn-login-android-studio.png) +React Native login screen -Enter **admin** as the username and **1q2w3E\*** as the password to log in to the application. +Enter **admin** as the username and **1q2w3E** as the password to log in to the application. The application is up and running. You can continue to develop your application based on this startup template. +## Navigation + +The startup template ships with **two navigation styles**, switchable when the project is created: + +- **Bottom Tab** — *the default* — three tabs at the bottom of the screen: **Home**, **Settings** and **Account**. +- **Drawer** — a side menu (hamburger) with two items: **Home** and **Settings**. + +Bottom Tab vs Drawer navigation comparison + + +Every main tab or drawer item is wired to **its own** native stack (`@react-navigation/native-stack`). Pushing more screens stays on that branch: the Back stack belongs to that tab or drawer route and does not mix with others. Bottom Tab and Drawer use the **same screen components**; they differ in how those screens are grouped and opened from the outer shell (and where the sign‑in/sign‑up flow lives in Bottom Tab versus Drawer). + +> **How to choose:** The mode is selected in **ABP Studio** during the *Mobile Framework* step. Switching modes after the project is generated is not a one-line change — you would need to add the missing navigator (and its `@react-navigation/drawer` or `@react-navigation/bottom-tabs` dependency) manually, then update `src/AppContainer.tsx` and `src/navigators/types.ts` to match. Pick the mode upfront when possible. + +### Bottom Tab Navigation (default) + +The root navigator is `BottomTabNavigator` (`src/navigators/BottomTabNavigator.tsx`) with three stacks: + +- **HomeTab** → `HomeNavigator` → `HomeScreen` (hero greeting + feature cards). +- **SettingsTab** → `SettingsNavigator` → `SettingsScreen` (language, theme, profile/password shortcuts). +- **AccountTab** → `AccountNavigator` — *conditional stack* based on the authentication state read from Redux: + - **Authenticated:** `AccountScreen` → `ChangePasswordScreen`, `ProfilePictureScreen`. + - **Guest:** `LoginScreen` → `RegisterScreen`, `ForgotPasswordScreen`, `ResetPasswordScreen`. + +Tab bar colors (active/inactive tint, background, border) are sourced from the `useThemeColors` hook so the bar follows the active light/dark theme. + +#### The Account Screen + +`AccountScreen` (`src/screens/Account/AccountScreen.tsx`) is the home of the AccountTab when the user is signed in. Its layout follows an iOS-style grouped pattern: + +1. **Profile header** — circular avatar (profile picture or first-letter fallback), full name and email, centered at the top. +2. **Account actions card** — a single rounded card containing two rows with leading icon chips: + - **Profile Picture** → navigates to `ProfilePictureScreen`. + - **Change Password** → navigates to `ChangePasswordScreen`. +3. **Destructive logout button** — an outlined `destructive`-colored button that calls the `useLogout` hook. + +### Drawer Navigation (alternative) + +When the drawer mode is selected, `DrawerNavigator` (`src/navigators/DrawerNavigator.tsx`) replaces the bottom tabs. It exposes two drawer items: + +- **HomeStack** → `HomeNavigator` → `HomeScreen`, plus the auth flow (`LoginScreen`, `RegisterScreen`, `ForgotPasswordScreen`, `ResetPasswordScreen`). +- **SettingsStack** → `SettingsNavigator` → `SettingsScreen`, `ChangePasswordScreen`, `ProfilePictureScreen`. + +Note that there is **no `AccountTab` / `AccountScreen` in drawer mode** — auth lives in the Home stack and profile/password actions live in the Settings stack. The drawer side panel itself is fully custom. + +#### The Drawer Content + +`DrawerContent` (`src/components/DrawerContent/DrawerContent.tsx`) is the custom side panel rendered by `DrawerNavigator` via the `drawerContent` prop. From top to bottom: + +1. **User header** — circular avatar (image or first-letter fallback) + full name + email when authenticated. +2. **Divider**. +3. **Navigation items** — Home and Settings rows with leading Ionicons; tapping navigates and closes the drawer. +4. **Auth row** — when authenticated, a **Logout** row that calls `useLogout`; when guest, a **Login** row that navigates to the Login screen inside `HomeStack`. + +The whole panel uses NativeWind classes with `dark:` variants, so it follows the active theme automatically. + +### Adding a New Screen + +To add a screen to either navigation mode: + +1. Create the screen component under `src/screens//Screen.tsx` and export it from `src/screens/index.ts`. +2. Register it as a `Stack.Screen` inside the appropriate navigator (e.g. `HomeNavigator`, `SettingsNavigator`, or `AccountNavigator`). +3. Add the route to the matching `*ParamList` in `src/navigators/types.ts` so the screen props stay typed. + +If the new screen needs to appear at the *root* level (a new tab or drawer item rather than a child of an existing stack), edit `BottomTabNavigator.tsx` or `DrawerNavigator.tsx` and update the corresponding `BottomTabParamList` / `RootDrawerParamList` type. + ## How to Configure & Run the Backend (Required for Emulator/Simulator Testing) > React Native application does not trust the auto-generated .NET HTTPS certificate. You should use **HTTP** during the development. @@ -172,11 +229,10 @@ A React Native application running on an Android emulator or a physical phone ** {{ if Architecture == "Monolith" }} -![React Native monolith host project configuration](../../../images/react-native-monolith-be-config.png) +React Native monolith host project configuration - Open the `appsettings.json` file in the `.DbMigrator` folder. Replace the `localhost` address in the `RootUrl` property with your local IP address. Then, run the database migrator. - Open the `appsettings.Development.json` file in the `.HttpApi.Host` folder. Add this configuration to accept global requests for testing the React Native application in the development environment. - ```json { "Kestrel": { @@ -191,11 +247,10 @@ A React Native application running on an Android emulator or a physical phone ** {{ else if Architecture == "Tiered" }} -![React Native tiered project configuration](../../../images/react-native-tiered-be-config.png) +React Native tiered project configuration - Open the `appsettings.json` file in the `.DbMigrator` folder. Replace the `localhost` address in the `RootUrl` property with your local IP address. Then, run the database migrator. - Open the `appsettings.Development.json` file in the `.AuthServer` folder. Add this configuration to accept global requests for testing the React Native application in the development environment. - ```json { "Kestrel": { @@ -207,9 +262,7 @@ A React Native application running on an Android emulator or a physical phone ** } } ``` - - Open the `appsettings.Development.json` file in the `.HttpApi.Host` folder. Add this configuration to accept global requests. Additionally, you need to configure the authentication server as mentioned above. - ```json { "Kestrel": { @@ -230,10 +283,9 @@ A React Native application running on an Android emulator or a physical phone ** {{ else if Architecture == "Microservice" }} -![React Native microservice project configuration](../../../images/react-native-microservice-be-config.png) +React Native microservice project configuration - Open the `appsettings.Development.json` file in the `.AuthServer` folder. Add this configuration to accept global requests for testing the React Native application in the development environment. - ```json { "App": { @@ -248,9 +300,7 @@ A React Native application running on an Android emulator or a physical phone ** } } ``` - - Open the `appsettings.Development.json` file in the `.AdministrationService` folder. Add this configuration to accept global requests for testing the React Native application in the development environment. You should also provide the authentication server configuration. Additionally, you need to apply the same process for all services you will use in the React Native application. - ```json { "App": { @@ -271,9 +321,7 @@ A React Native application running on an Android emulator or a physical phone ** } } ``` - - Update the `appsettings.json` file in the `.IdentityService` folder. Replace the `localhost` configuration with your local IP address for the React Native application. - ```json { //... @@ -292,9 +340,7 @@ A React Native application running on an Android emulator or a physical phone ** } } ``` - - Finally, update the mobile gateway configurations as follows: - ```json //gateways/mobile/MyMicroserviceProject.MobileGateway/Properties/launchSettings.json { @@ -319,7 +365,6 @@ A React Native application running on an Android emulator or a physical phone ** } } ``` - ```json //gateways/mobile/MyMicroserviceProject.MobileGateway/appsettings.json { @@ -367,7 +412,6 @@ A React Native application running on an Android emulator or a physical phone ** } } ``` - {{ end }} -Run the backend application(s) as described in the [getting started document](../../../get-started). +Run the backend application(s) as described in the [getting started document](../../../get-started). \ No newline at end of file diff --git a/docs/en/framework/ui/react-native/styling-with-nativewind.md b/docs/en/framework/ui/react-native/styling-with-nativewind.md new file mode 100644 index 0000000000..2f2b8e75fa --- /dev/null +++ b/docs/en/framework/ui/react-native/styling-with-nativewind.md @@ -0,0 +1,159 @@ +```json +//[doc-seo] +{ + "Description": "Learn how the ABP React Native startup template is styled with NativeWind v4 (Tailwind CSS for React Native), including the semantic color tokens, dark mode and theme customization." +} +``` + +# Styling with NativeWind + +The ABP React Native startup template uses **[NativeWind v4](https://www.nativewind.dev/)** — Tailwind CSS for React Native. Most components are styled through utility `className` props that are compiled at build time, so there is almost no styling runtime cost. The design system is based on the **shadcn/ui** neutral palette and supports **light and dark themes** out of the box. + +> This page documents the styling system that ships with the template. For the general getting-started flow (installing tools, running the app, configuring the backend), see [Getting Started with React Native](index.md). + +HomeScreen built with NativeWind + +--- + +## 1. Project Files + +NativeWind is wired in through a small set of configuration files at the root of the React Native project: + +| File | Purpose | +|------|---------| +| `tailwind.config.js` | Defines the design tokens (colors, spacing, border radius), enables `darkMode: 'class'`, and registers the NativeWind preset. This is the source of truth for the theme. | +| `global.css` | Tailwind entry point with the three base directives (`@tailwind base; @tailwind components; @tailwind utilities;`). | +| `metro.config.js` | Wraps the default Expo Metro config with `withNativeWind(...)` and points it at `global.css`. | +| `babel.config.js` | Adds the `nativewind/babel` preset and sets `jsxImportSource: 'nativewind'` on `babel-preset-expo` so JSX understands the `className` prop. | +| `nativewind-env.d.ts` | TypeScript triple-slash reference (`/// `) that adds `className` typings to React Native components. | + +All of these files come pre-configured in the template. You normally only need to edit `tailwind.config.js` to customize the theme. + +--- + +## 2. Semantic Color Tokens + +Instead of raw color names like `bg-zinc-900`, the template exposes a small set of **semantic tokens** that follow the shadcn/ui convention. Each token has a default (light) value and a `dark` variant, and many also carry a paired `foreground` color for text/icons placed on top. + +| Token | Use it for | +|-------|-----------| +| `background` / `foreground` | Screen background and primary text. | +| `card` / `card-border` | Surface containers (settings rows, feature cards) and their 1px border. | +| `primary` / `primary-foreground` | Filled call-to-action buttons. | +| `secondary` / `secondary-foreground` | Subtle filled elements such as icon chips inside cards. | +| `muted` / `muted-foreground` | Backgrounds and text for secondary information (subtitles, helper text). | +| `accent` / `accent-foreground` | Highlighted/active state — also used as the active tint of the bottom tab bar. | +| `destructive` / `destructive-foreground` | Delete/danger actions and error states. | +| `border` | Generic separators and outline borders. | +| `input` | Border color for text inputs (Paper `TextInput` outline). | +| `ring` | Focus ring/outline color. | + +All token values live in `tailwind.config.js` under `theme.extend.colors`. Refer to that file for the exact hex values. + +The template also extends Tailwind's spacing and border-radius scales so paddings and corners stay consistent across screens: + +* **Spacing:** `xs` (4px), `sm` (8px), `md` (16px), `lg` (24px), `xl` (32px) — usable as `p-md`, `mt-lg`, `gap-sm`, etc. +* **Border radius:** `rounded-sm` (4px), `rounded-md` (8px), `rounded-lg` (12px), `rounded-xl` (16px), `rounded-2xl` (20px). + +--- + +## 3. Dark Mode + +Dark mode is enabled with `darkMode: 'class'` in `tailwind.config.js` and is driven by the device color scheme via NativeWind's `useColorScheme()` hook. Each color token has a sibling `*-dark` (and where applicable `*-dark-foreground`) variant, and components opt into it with the `dark:` prefix on their classes: + +```tsx + + + Hello + + +``` + +> **Pattern note.** The token shape used in this template is `{ DEFAULT, dark }` (and `{ foreground, 'dark-foreground' }` where applicable), so the dark-mode class is `dark:bg-background-dark` — not `dark:bg-background`. Follow this convention when you add new screens so the theme switch behaves consistently across the app. + +The user can also switch themes manually from the **Settings** screen (system / light / dark), and the choice is persisted via Redux. + +HomeScreen with light and dark theme + +--- + +## 4. The `useThemeColors` Hook + +A handful of APIs in the template do not accept a `className` — they need a *color value*. Examples: + +* `react-native-paper`'s `TextInput` (which the template still uses for outlined inputs and validation styling). +* React Navigation's `screenOptions` (header background, tab bar tint, etc.). +* The native status bar. + +For these cases the template ships a `useThemeColors` hook at `src/hooks/UseThemeColors.ts`. It returns theme-aware values that mirror the Tailwind tokens: + +```ts +const { + primaryContainer, // Paper TextInput surface + headerBg, // Navigator headers + headerText, + iconColor, // Inactive icon tint + accentColor, // Active icon tint / focused tab + destructiveColor, + inputBorderColor, +} = useThemeColors(); +``` + +**Rule of thumb:** prefer `className` with `dark:` variants whenever the component supports it; reach for `useThemeColors` only for the components listed above. + +--- + +## 5. React Native Paper + +`react-native-paper` is still in the template's `package.json`, but its usage has been narrowed down to a single component: **`TextInput`** in outlined mode (used for forms because of its strong validation/error UI). Buttons, lists, modals, drawers, and icons are all NativeWind + `@expo/vector-icons` (Ionicons) now. + +If you add new forms, follow the same split: + +* Use Paper's `TextInput` (with values from `useThemeColors`) for text fields. +* Use plain `View`/`Text`/`Pressable` with NativeWind classes for everything else. + +--- + +## 6. Customizing the Theme + +Most customization happens in `tailwind.config.js`. To introduce a brand color, extend the `colors` map and reuse the `{ DEFAULT, dark }` shape so the `dark:` variants keep working: + +```js +// tailwind.config.js +module.exports = { + // ... + theme: { + extend: { + colors: { + // ... + brand: { + DEFAULT: '#2563eb', + dark: '#3b82f6', + foreground: '#ffffff', + 'dark-foreground': '#ffffff', + }, + }, + }, + }, +}; +``` + +You can then use it from any component: + +```tsx + + + Get started + + +``` + +If the new color also needs to be reachable from non-`className` APIs (Paper, navigators, status bar), add a matching entry to `useThemeColors` so the rest of the template stays consistent. + +--- + +## 7. Going Further + +* [NativeWind documentation](https://www.nativewind.dev/) — class reference, advanced features (variants, animations). +* [Tailwind CSS documentation](https://tailwindcss.com/docs) — utility classes, responsive design, theming. +* [shadcn/ui](https://ui.shadcn.com/) — the design system the color tokens are inspired by. diff --git a/docs/en/framework/ui/react/admin-console.md b/docs/en/framework/ui/react/admin-console.md new file mode 100644 index 0000000000..47bc355b76 --- /dev/null +++ b/docs/en/framework/ui/react/admin-console.md @@ -0,0 +1,191 @@ +```json +//[doc-seo] +{ + "Description": "Learn how the ABP Admin Console works with React UI applications and how it is hosted under /admin-console." +} +``` + +# Admin Console + +The **ABP Admin Console** is the React-based administration UI for ABP applications. It provides management pages for ABP modules and is available in React UI solutions created with ABP Studio v3.0+ or `abp new --modern --ui-framework react`. + +The Admin Console is delivered as the `Volo.Abp.AdminConsole` NuGet package for layered and single-layer solutions. In microservice solutions, the template also includes a standalone `apps/react-admin-console/` React app. + +## What It Provides + +The Admin Console contains administration pages for the ABP modules included in the host application. Module pages are activated based on the backend services available in the host, so a solution only shows pages for modules it actually has. + +The built-in module areas include: + +| Module | Notes | +| --- | --- | +| Identity Pro | User, role, claim, and organization unit management when Identity services are available. | +| Account Pro | Account management pages and account-related flows. | +| OpenIddict | Application and scope management when OpenIddict services are available. | +| Audit Logging UI | Optional. Visible when Audit Logging services are available. | +| AI Management | Optional. Visible when AI Management services are available. | +| Text Template Management | Optional. Visible when Text Template Management services are available. | + +Other module pages, such as Setting Management, SaaS, GDPR, or customization pages, can also be available depending on the solution and installed modules. + +## Hosting Model + +The Admin Console is served under: + +```text +/admin-console/* +``` + +API endpoints used by the Admin Console are served under: + +```text +/admin-console/api/* +``` + +The `Volo.Abp.AdminConsole` package embeds the built React SPA under `wwwroot/admin-console/` and registers it with ABP's Virtual File System. `AdminConsoleSpaMiddleware` then serves static assets and falls back to `index.html` for client-side routes. + +The middleware deliberately lets `/admin-console/api/*` requests pass through to MVC controllers. + +## Layered and Single-Layer Templates + +For layered and single-layer modern templates: + +- The developer-owned React app is in the `react/` folder. +- The Admin Console UI is embedded in the backend through the `Volo.Abp.AdminConsole` NuGet package. +- There is no separate `react-admin-console/` source folder in the generated solution. +- The backend host serves Admin Console pages under `/admin-console/*`. + +Example URL: + +```text +https://localhost:44300/admin-console/ +``` + +The main React app links to the Admin Console through `getAdminConsoleUrl()`. + +## Microservice Template + +For the microservice modern template: + +- The main React app is in `apps/react/`. +- The Admin Console app is in `apps/react-admin-console/`. +- Both are served through the Web Gateway. +- The Admin Console has its own OpenIddict client, normally `_AdminConsole`. + +The main React app uses `adminConsoleUrl` from `dynamic-env.json` to open the Admin Console origin and `/admin-console` base path. + +## Module Discovery + +The Admin Console calls: + +```text +GET /admin-console/api/modules +``` + +The backend checks for module application service contracts and returns which module areas are available. The discovery keys include: + +| Key | Backend service check | +| --- | --- | +| `identity` | `Volo.Abp.Identity.IIdentityUserAppService` | +| `saas` | `Volo.Saas.Host.ITenantAppService` | +| `auditLogging` | `Volo.Abp.AuditLogging.IAuditLogsAppService` | +| `gdpr` | `Volo.Abp.Gdpr.IGdprRequestAppService` | +| `openIddict` | `Volo.Abp.OpenIddict.Applications.IApplicationAppService` | +| `textTemplateManagement` | `Volo.Abp.TextTemplateManagement.TextTemplates.ITemplateDefinitionAppService` | +| `aiManagement` | AI Management service contracts, with a legacy AI engine fallback. | + +`settingManagement` is always returned as available by the discovery endpoint, while access to pages is still controlled by permissions. + +## Configuration Endpoint + +The Admin Console also uses: + +```text +GET /admin-console/api/config +``` + +This endpoint provides Admin Console runtime settings such as authority, client ID, scopes, application name, customization options, and localization language configuration. + +Host applications can configure Admin Console options from the `AdminConsole` configuration section or by configuring `AbpAdminConsoleOptions`. + +## Configuring the Admin Console + +In layered and single-layer modern React templates, the embedded Admin Console is configured from the backend host application's `appsettings.json` file. The generated template includes an `AdminConsole` section similar to the following: + +```json +{ + "AdminConsole": { + "IsEnabled": true, + "RedirectRootToAdminConsole": true, + "Authority": "https://localhost:44300", + "ClientId": "Acme_BookStore_AdminConsole", + "Scope": "openid profile email offline_access Acme_BookStore", + "LocalizationLanguages": [ "en", "tr" ], + "ThemeOverrideCssPath": "/theme-override.css", + "InitialTheme": "system", + "CustomizationPermissionName": "AdminConsole.Customization" + } +} +``` + +You can also configure the same values in the module class with `AbpAdminConsoleOptions`: + +```csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + Configure(options => + { + options.IsEnabled = true; + options.RedirectRootToAdminConsole = true; + options.Authority = "https://localhost:44300"; + options.ClientId = "Acme_BookStore_AdminConsole"; + options.Scope = "openid profile email offline_access Acme_BookStore"; + options.LocalizationLanguages = new[] { "en", "tr" }; + options.ThemeOverrideCssPath = "/theme-override.css"; + options.InitialTheme = "system"; + options.CustomizationPermissionName = "AdminConsole.Customization"; + }); +} +``` + +The most commonly changed options are: + +| Option | Description | +| --- | --- | +| `IsEnabled` | Enables or disables the embedded Admin Console SPA middleware. | +| `RedirectRootToAdminConsole` | Redirects the backend root path (`/`) to `/admin-console`. | +| `Authority` | OpenID Connect authority URL. If it is `null`, the host origin is used. | +| `ClientId` | OpenIddict client ID used by the Admin Console SPA. | +| `Scope` | Space-separated OAuth scopes requested by the Admin Console. | +| `LocalizationLanguages` | UI language codes exposed to the Admin Console. If empty, the frontend falls back to `en`. | +| `ThemeOverrideCssPath` | Optional CSS path or absolute URL injected into the Admin Console HTML. | +| `InitialTheme` | Initial theme behavior: `light`, `dark`, `system`, or `both`. | +| `CustomizationPermissionName` | Permission required to show and use the Admin Console customization page. If not set, customization is disabled. | + +The `ApplicationName`, `LogoUrl`, `InitialTheme`, and `ThemeOverrideCssPath` values can also be changed from the Admin Console customization UI when `CustomizationPermissionName` is configured and the current user has that permission. Values saved from the customization UI are stored as settings and override the defaults from configuration. + +In microservice solutions, the Admin Console is a separate React app under `apps/react-admin-console/`. It still uses its own OpenIddict client (`_AdminConsole`) and runtime configuration, while the backend exposes the same `/admin-console/api/config` and `/admin-console/api/modules` endpoints. + +## Permissions + +Admin Console routes still require permissions. For example: + +- Identity pages use `AbpIdentity.*` permissions. +- OpenIddict pages use `OpenIddictPro.Application` and `OpenIddictPro.Scope`. +- Audit Logging uses `AuditLogging.AuditLogs`. +- Text Template Management uses `TextTemplateManagement.*`. +- AI Management uses `AIManagement.*`. + +The main React app's Admin Console menu item only requires authentication. The Admin Console performs detailed permission checks for its own pages. + +## Customization + +The developer-owned React app is intended for application-specific pages. The Admin Console is an ABP-managed administration surface and should normally be updated by updating ABP packages. + +For layered and single-layer hosts, the package supports host-side options such as application name, localization languages, and theme override CSS path. For larger UI changes, prefer building your own pages in the main React app or extending the backend modules through supported ABP extension points. + +## See Also + +- [React UI](./index.md) +- [Environment Variables](./environment-variables.md) +- [Permission Management](./permission-management.md) diff --git a/docs/en/framework/ui/react/authorization.md b/docs/en/framework/ui/react/authorization.md new file mode 100644 index 0000000000..4f6d673d87 --- /dev/null +++ b/docs/en/framework/ui/react/authorization.md @@ -0,0 +1,183 @@ +```json +//[doc-seo] +{ + "Description": "Learn how authentication and authorization are configured in ABP React UI applications." +} +``` + +# Authorization in React UI + +OAuth is preconfigured in ABP React UI templates. When you create a React solution with ABP Studio v3.0+ or `abp new --modern --ui-framework react`, the template includes OpenID Connect settings, an OpenIddict client, route guards, and authentication hooks. + +The React app authenticates against the ABP Auth Server using the **Authorization Code flow with PKCE**, which is the recommended flow for browser-based applications. + +## Packages + +The template uses these packages for authentication: + +| Package | Purpose | +| --- | --- | +| `@volo/abp-oidc-auth` | Framework-agnostic OIDC client helpers for ABP/OpenIddict backends. | +| `@volo/abp-react-oidc-auth` | React adapter for the ABP OIDC client. | +| `oidc-client-ts` | Underlying OIDC protocol implementation. | + +The package list also includes `@volo/abp-app-config` and `@volo/abp-react-app-config`, which are used to fetch application configuration and permissions after authentication. + +## OAuth Configuration + +The OIDC settings are resolved from runtime configuration first and fall back to `src/env.ts`. + +```ts +export function getOAuthConfig(): { + issuer: string + redirectUri: string + clientId: string + scope: string + responseType: 'code' +} { + return { + issuer: loadedConfig?.oAuthConfig?.issuer ?? env.oauth.issuer, + redirectUri: loadedConfig?.oAuthConfig?.redirectUri ?? env.oauth.redirectUri, + clientId: loadedConfig?.oAuthConfig?.clientId ?? env.oauth.clientId, + scope: loadedConfig?.oAuthConfig?.scope ?? env.oauth.scope, + responseType: 'code', + } +} +``` + +The important configuration values are: + +- `oAuthConfig.issuer`: Auth Server / OpenIddict authority URL. +- `oAuthConfig.redirectUri`: URL where the Auth Server redirects after login. +- `oAuthConfig.clientId`: OpenIddict client ID, normally `_App`. +- `oAuthConfig.scope`: Scopes requested by the React app. + +See [Environment Variables](./environment-variables.md) for the full runtime configuration model. + +## Initializing Authentication + +The app loads runtime configuration before initializing OIDC: + +```tsx +async function bootstrap() { + await loadRuntimeConfig() + initUserManager() + createRoot(document.getElementById('root')!).render( + + + + ) +} +``` + +`initUserManager()` creates the ABP React OIDC client: + +```ts +client = createAbpReactOidcAuth({ + authority: config.issuer, + clientId: config.clientId, + redirectUri: config.redirectUri, + postLogoutRedirectUri: config.redirectUri, + scope: config.scope, + responseType: config.responseType, + automaticSilentRenew: true, + userStoreType: 'localStorage', + userStorePrefix: `oidc.${config.clientId}`, + silentRedirectUri: `${window.location.origin}/silent-renew.html`, +}) +``` + +The template stores the OIDC user in local storage and enables silent renewal with `public/silent-renew.html`. + +## Auth Provider and Hook + +`AuthProvider` wraps the app and handles the OIDC callback: + +```tsx +export function AuthProvider({ children }: { children: ReactNode }) { + const authClient = getAuthClient() + + useEffect(() => { + const params = new URLSearchParams(window.location.search) + if (!params.has('code') || !params.has('state')) return + void authClient.handleSigninCallback().then(() => + window.history.replaceState({}, document.title, window.location.pathname) + ) + }, []) + + return {children} +} +``` + +Use `useAuth()` in components: + +```tsx +import { useAuth } from '@/lib/auth/AuthContext' + +export function LoginButton() { + const { isAuthenticated, isLoading, login, logout, user } = useAuth() + + if (isLoading) return null + + return isAuthenticated ? ( + + ) : ( + + ) +} +``` + +## Route Protection + +The React template uses TanStack Router. Protected routes use `beforeLoad` guards. + +```ts +const identityLayoutRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/identity', + component: IdentityLayout, + beforeLoad: authGuard, +}) +``` + +`authGuard` checks the current OIDC user and redirects unauthenticated users to the Auth Server: + +```ts +export async function authGuard({ location }: GuardContext) { + const user = await userManager.getUser() + if (!user || user.expired) { + await userManager.signinRedirect({ + state: { returnUrl: location.href }, + }) + throw new Error('Redirecting to login') + } +} +``` + +Routes that also require a permission use `createPermissionGuard`: + +```ts +const usersRoute = createRoute({ + getParentRoute: () => identityLayoutRoute, + path: 'users', + component: UsersPage, + beforeLoad: createPermissionGuard('AbpIdentity.Users'), +}) +``` + +Permission checks are explained in [Permission Management](./permission-management.md). + +## OpenIddict Clients + +The generated OpenIddict clients depend on the template: + +- Layered and single-layer modern templates use the main React client, normally `_App`. +- Microservice modern templates also include an Admin Console client, normally `_AdminConsole`, because the Admin Console is a separate React app. + +If you change URLs after generation, update both the runtime configuration and the corresponding OpenIddict client redirect URLs. + +## See Also + +- [Environment Variables](./environment-variables.md) +- [Permission Management](./permission-management.md) +- [Authorization](../../../framework/fundamentals/authorization/index.md) diff --git a/docs/en/framework/ui/react/components/index.md b/docs/en/framework/ui/react/components/index.md new file mode 100644 index 0000000000..856759acd5 --- /dev/null +++ b/docs/en/framework/ui/react/components/index.md @@ -0,0 +1,184 @@ +```json +//[doc-seo] +{ + "Description": "Learn about the component architecture and UI libraries used by ABP React UI applications." +} +``` + +# Components + +ABP React UI templates use a source-owned component architecture. The generated app includes shadcn/ui-style primitives, layout components, feature components, route pages, and shared infrastructure under `src/lib/`. + +The goal is to give you a working React application that you can customize without replacing framework-owned black boxes. + +## Component Structure + +The main React app is organized like this: + +```text +src/ +├── components/ +│ ├── layout/ +│ ├── ui/ +│ └── identity/ +├── lib/ +│ ├── api/ +│ ├── auth/ +│ ├── i18n/ +│ ├── routing/ +│ └── theme/ +├── locales/ +├── pages/ +└── routes/ +``` + +The exact folders can vary by selected template options and modules. + +## UI Stack + +The React template uses: + +| Library | Purpose | +| --- | --- | +| React | UI rendering. | +| Vite | Build tool and development server. | +| TanStack Router | Client-side routing. | +| TanStack Query | Server state, queries, mutations, and cache invalidation. | +| shadcn/ui-style components | Source-owned UI primitives built on Radix UI and Tailwind CSS. | +| Radix UI | Accessible low-level UI primitives. | +| Tailwind CSS | Utility-first styling and design tokens. | +| React Hook Form | Form state management. | +| Zod | Form and DTO validation schemas. | +| Axios | HTTP client. | +| i18next / react-i18next | Localization. | +| Zustand | Lightweight client state when needed. | +| Sonner | Toast notifications. | +| Lucide React | Icons. | + +## `components/ui` + +`src/components/ui/` contains reusable UI primitives. These components are copied into your project and can be edited directly. + +Common components include: + +- `Button` +- `Input` +- `Label` +- `Table` +- `Dialog` +- `DropdownMenu` +- `Select` +- `Card` +- `Tabs` +- `Badge` +- `DatePicker` +- `ConfirmDialog` + +Use these primitives to build application pages and feature components. + +```tsx +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' + +export function ReportCard() { + return ( + + + Reports + + + + + + ) +} +``` + +## Layout Components + +Layout components are under `src/components/layout/`. + +Important components include: + +- `RootLayout`: root shell used by TanStack Router. +- `Header`: top bar, login button, theme toggle, and user menu. +- `Sidebar`: route-config-driven navigation menu. +- `UserMenu`: account-related dropdown menu. + +The sidebar reads `src/lib/routing/route-config.ts`, checks authentication and permissions, and renders internal or external links. + +## Feature Components + +Feature-specific components should live near the feature that owns them. For example, Identity-specific layout components live under `src/components/identity/`, while Books-specific UI is implemented in `src/pages/books/BooksPage.tsx` in the sample template. + +As a rule: + +- Put generic, reusable primitives in `components/ui`. +- Put application layout in `components/layout`. +- Put feature-specific components under `components/` or next to the page when they are only used by one page. + +## Pages + +Route pages live under `src/pages/`. A page usually combines: + +- UI primitives from `components/ui`. +- API functions from `src/lib/api`. +- Server state from TanStack Query. +- Form state from React Hook Form. +- Validation schemas from Zod. +- Permissions from `usePermissions()`. +- Localized strings from `useTranslation()`. + +The Books page is the best full CRUD reference when the sample CRUD option is selected. + +## Forms + +Forms use React Hook Form and Zod: + +```tsx +const productSchema = z.object({ + name: z.string().min(1, 'Required'), + price: z.number().min(0), +}) + +type ProductFormData = z.infer + +const form = useForm({ + resolver: zodResolver(productSchema), + defaultValues: { + name: '', + price: 0, + }, +}) +``` + +This keeps runtime validation and TypeScript types close to each other. + +## Routing Components + +Routes are configured in `src/routes/router.tsx` with TanStack Router. Use: + +- `authGuard` for authenticated pages. +- `createPermissionGuard('Permission.Name')` for permission-protected pages. +- `RootLayout` and nested layouts for shared page structure. + +Menu entries are configured separately in `src/lib/routing/route-config.ts`, so route registration and navigation display can evolve independently. + +## API Components and Hooks + +API functions live under `src/lib/api/` and use the shared `api` Axios instance. Components normally consume these functions through TanStack Query: + +```tsx +const usersQuery = useQuery({ + queryKey: ['app', 'users', queryParams], + queryFn: () => getAppUsers(queryParams), +}) +``` + +This keeps HTTP details out of rendering components and gives you caching, loading states, refetching, and mutation invalidation. + +## See Also + +- [Customization](../customization.md) +- [HTTP Requests](../http-requests.md) +- [Unit Testing](../unit-testing.md) diff --git a/docs/en/framework/ui/react/customization.md b/docs/en/framework/ui/react/customization.md new file mode 100644 index 0000000000..3e64018dab --- /dev/null +++ b/docs/en/framework/ui/react/customization.md @@ -0,0 +1,208 @@ +```json +//[doc-seo] +{ + "Description": "Learn how to customize ABP React UI applications, including pages, themes, sidebar navigation, and the user menu." +} +``` + +# Customization + +The React app generated by ABP is fully owned by your solution. All source code is available, so you can change pages, components, routes, themes, menus, API calls, and layout behavior just like in any other React application. + +This page focuses on the main developer-owned React app. The same general approach applies to the public-web React app if your solution includes one. The Admin Console is an ABP-managed administration surface; see [Admin Console](./admin-console.md) for details. + +## General Customization + +Application pages live under `src/pages/`. The template includes practical references: + +- **Users page**: a simple page that lists users and links to the Admin Console for full user and role management. +- **Books page**: a full CRUD sample when the sample CRUD option is selected during solution creation. It demonstrates TanStack Query, forms, Zod validation, dialogs, tables, permissions, and toast notifications. + +Shared UI and infrastructure live under: + +```text +src/ +├── components/ +│ ├── layout/ +│ └── ui/ +├── lib/ +│ ├── api/ +│ ├── auth/ +│ ├── i18n/ +│ ├── routing/ +│ └── theme/ +└── pages/ +``` + +## Adding a Page + +Create a page under `src/pages/`: + +```tsx +export function ReportsPage() { + return ( +
+

Reports

+
+ ) +} +``` + +Register it with TanStack Router in `src/routes/router.tsx`: + +```tsx +const reportsRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/reports', + component: ReportsPage, + beforeLoad: createPermissionGuard('MyProjectName.Reports'), +}) + +const routeTree = rootRoute.addChildren([ + indexRoute, + reportsRoute, +]) +``` + +Use `authGuard` for pages that only require authentication and `createPermissionGuard` for pages that require a permission. + +## Theming + +The React template uses **shadcn/ui**-style components, Radix UI primitives, Tailwind CSS, and CSS variables. + +Theme tokens are defined in `src/styles/globals.css`: + +```css +:root { + --background: oklch(0.978 0.003 264); + --foreground: oklch(0.205 0.008 264); + --primary: oklch(0.48 0.10 278); + --radius: 0.5rem; +} + +.dark { + --background: oklch(0.16 0.004 264); + --foreground: oklch(0.92 0.005 264); + --primary: oklch(0.62 0.12 278); +} +``` + +ABP Studio's modern wizard can generate different shadcn theme color presets and light/dark/system theme behavior. + +## Changing Theme Colors + +To make a quick theme change, edit the CSS variables in `src/styles/globals.css`: + +```css +:root { + --primary: oklch(0.623 0.188 259.6); + --primary-foreground: oklch(1 0 0); +} +``` + +Because the generated shadcn/ui components consume these variables through Tailwind tokens, the change applies across buttons, links, active sidebar entries, focus rings, and other components that use the primary color. + +## Theme Mode Switcher + +Theme mode is handled by `src/lib/theme/ThemeProvider.tsx`. It supports: + +- `light` +- `dark` +- `system` + +The header cycles through the allowed modes: + +```tsx +const THEME_CYCLE: Theme[] = ['light', 'dark', 'system'] + +function ThemeToggle() { + const { theme, resolvedTheme, setTheme } = useTheme() + + function cycleTheme() { + const currentIndex = THEME_CYCLE.indexOf(theme) + const nextIndex = currentIndex < 0 ? 0 : (currentIndex + 1) % THEME_CYCLE.length + setTheme(THEME_CYCLE[nextIndex]) + } + + return +} +``` + +To remove the switcher or replace it with a dropdown, edit `src/components/layout/Header.tsx`. + +## Modifying the Sidebar Menu + +Sidebar navigation is defined in `src/lib/routing/route-config.ts`. + +Add a menu item: + +```ts +import { BarChart3 } from 'lucide-react' + +export const routeConfig: RouteConfigItem[] = [ + { + path: '/reports', + nameKey: 'Menu:Reports', + icon: BarChart3, + order: 10, + requiredPolicy: 'MyProjectName.Reports', + }, +] +``` + +Then add the localization key to `src/locales/en.json`: + +```json +{ + "Menu:Reports": "Reports" +} +``` + +Use these properties depending on the menu item: + +| Property | Use | +| --- | --- | +| `path` | Internal route path or logical path for an external item. | +| `nameKey` | Localization key shown in the sidebar. | +| `icon` | Optional Lucide icon. | +| `order` | Sorting order. | +| `requiredPolicy` | Hide the item unless the permission is granted. | +| `requiresAuth` | Hide the item unless the user is authenticated. | +| `externalHref` | Open an external URL or another app, such as the Admin Console. | +| `children` | Add nested sidebar items. | + +## Sidebar vs User Menu + +Use the **sidebar navigation** for application pages and module entry points. + +Use the **user menu** for account-specific actions, profile links, sessions, security logs, linked accounts, and logout. The user menu is implemented in `src/components/layout/UserMenu.tsx`. + +Example user menu item: + +```tsx + + + + {t('MyAccount::Preferences')} + + +``` + +## Customizing UI Components + +shadcn/ui components are copied into your project under `src/components/ui/`. They are not black-box components from a package. You can edit them directly. + +For example: + +- Change button variants in `src/components/ui/button.tsx`. +- Change dialog structure in `src/components/ui/dialog.tsx`. +- Add a new reusable component under `src/components/ui/`. +- Add feature-specific components under `src/components//`. + +Keep generic primitives in `components/ui` and business-specific components close to the feature or page that owns them. + +## See Also + +- [Components](./components/index.md) +- [Permission Management](./permission-management.md) +- [Admin Console](./admin-console.md) diff --git a/docs/en/framework/ui/react/environment-variables.md b/docs/en/framework/ui/react/environment-variables.md new file mode 100644 index 0000000000..2b84b0c4d0 --- /dev/null +++ b/docs/en/framework/ui/react/environment-variables.md @@ -0,0 +1,121 @@ +```json +//[doc-seo] +{ + "Description": "Learn how runtime configuration and environment variables work in ABP React UI applications." +} +``` + +# Environment Variables + +ABP React UI applications use a runtime configuration file and Vite environment variables together. The template is preconfigured by ABP Studio's modern wizard, available with ABP Studio **v3.0+**, so a newly created solution already contains working local values for the API, Auth Server, OpenIddict client, and Admin Console link. + +You usually change these values when moving the application to another environment such as staging or production. + +## Configuration Sources + +The React template reads configuration from these places: + +- `dynamic-env.json`: runtime configuration that can be changed without rebuilding the application. +- `public/dynamic-env.json`: the file served by the app. The Vite build copies the root `dynamic-env.json` into this location when it exists. +- `src/env.ts`: local fallback values used when runtime configuration is not loaded. +- `.env` files / shell variables: Vite variables such as `VITE_API_URL`, `VITE_AUTH_URL`, and `VITE_APP_URL`. + +For layered and single-layer modern templates, the React app is in the `react/` folder. For the microservice modern template, it is in `apps/react/`. + +## `dynamic-env.json` + +The runtime configuration file has the same purpose as Angular's dynamic environment configuration: it lets you deploy the same build artifact to different environments and change the API or authentication endpoints at runtime. + +```json +{ + "application": { + "baseUrl": "https://localhost:3000", + "name": "Acme.BookStore" + }, + "oAuthConfig": { + "issuer": "https://localhost:44301/", + "redirectUri": "https://localhost:3000", + "clientId": "Acme_BookStore_App", + "scope": "offline_access openid profile email phone AuthServer IdentityService AdministrationService" + }, + "apis": { + "default": { + "url": "https://localhost:44300", + "rootNamespace": "Acme.BookStore" + } + }, + "adminConsoleUrl": "https://localhost:44307" +} +``` + +The template loads `/dynamic-env.json` first and then tries `/getEnvConfig` for compatibility with environments that expose the file through that endpoint. + +## Available Values + +| Key | Description | +| --- | --- | +| `application.baseUrl` | Public URL of the React application. It is used as a fallback for OAuth redirect URLs. | +| `application.name` | Application name. | +| `application.logoUrl` | Optional logo URL for application branding. | +| `oAuthConfig.issuer` | Auth Server / OpenIddict authority URL. | +| `oAuthConfig.redirectUri` | Redirect URI registered for the React OpenIddict client. | +| `oAuthConfig.clientId` | OpenIddict client ID. The main React app uses `_App`. | +| `oAuthConfig.scope` | OAuth scopes requested by the SPA. | +| `apis.default.url` | Backend API base URL. In microservice solutions, this normally points to the Web Gateway. | +| `apis.default.rootNamespace` | Root namespace used by generated API code and module-specific clients. | +| `adminConsoleUrl` | Origin of the Admin Console app. The React template uses it to open `/admin-console`. | + +The `DynamicEnv` type also includes fields such as `production`, `oAuthConfig.requireHttps`, `oAuthConfig.responseType`, `oAuthConfig.strictDiscoveryDocumentValidation`, and `oAuthConfig.skipIssuerCheck`. The template's OIDC setup always uses the Authorization Code flow by setting `responseType` to `code`. + +## Vite Variables + +The React template uses Vite and reads environment variables with `loadEnv(mode, process.cwd(), '')`, so variables are not limited to the `VITE_` prefix inside `vite.config.ts`. + +The important variables for developers are: + +| Variable | Description | +| --- | --- | +| `VITE_API_URL` | Overrides the backend API or gateway URL used by the dev proxy and runtime fallback. | +| `VITE_AUTH_URL` | Overrides the Auth Server URL used by the dev proxy and runtime fallback. If omitted, the dev proxy can fall back to `VITE_API_URL`. | +| `VITE_APP_URL` | Overrides the React app URL used as the OAuth redirect URI fallback. | + +Example: + +```bash +VITE_API_URL=https://api.bookstore.example.com +VITE_AUTH_URL=https://auth.bookstore.example.com +VITE_APP_URL=https://bookstore.example.com +``` + +## What ABP Studio Preconfigures + +When a React solution is created with ABP Studio v3.0+ or `abp new --modern`, the template fills these values from the generated solution configuration: + +- Local launch ports for the React app, Web Gateway/API host, Auth Server, and Admin Console. +- The OpenIddict client ID, usually `_App`. +- OAuth scopes based on the selected modules, such as Identity, Administration, SaaS, Audit Logging, GDPR, File Management, AI Management, Language Management, or Chat. +- `adminConsoleUrl` when the template includes a separate Admin Console application. + +For local development, these generated values should work without manual changes. For production, update the API URL, Auth Server URL, redirect URI, client ID if you changed the seeded client, and any environment-specific scopes. + +## Development Proxy + +In development, `vite.config.ts` proxies these paths: + +- `/api` to `VITE_API_URL` or the generated API/gateway URL. +- `/connect` to `VITE_AUTH_URL`, `VITE_API_URL`, or the generated Auth Server URL. +- `/getEnvConfig` to `VITE_API_URL` or the generated API/gateway URL. + +This allows the React app to call same-origin paths during development while the backend services run on their own ports. + +## Deployment + +For deployment, prefer changing `dynamic-env.json` instead of rebuilding the React application for each environment. The file should be served with `application/json` content type and should not be rewritten to `index.html` by SPA fallback rules. + +If your server exposes `/getEnvConfig`, configure it to return the same JSON content as `dynamic-env.json`. + +## See Also + +- [React UI](./index.md) +- [Authorization](./authorization.md) +- [HTTP Requests](./http-requests.md) diff --git a/docs/en/framework/ui/react/http-requests.md b/docs/en/framework/ui/react/http-requests.md new file mode 100644 index 0000000000..2c01884635 --- /dev/null +++ b/docs/en/framework/ui/react/http-requests.md @@ -0,0 +1,213 @@ +```json +//[doc-seo] +{ + "Description": "Learn how HTTP requests are made in ABP React UI applications with Axios, runtime configuration, and ABP interceptors." +} +``` + +# HTTP Requests + +ABP React UI templates use [Axios](https://axios-http.com/) for HTTP requests. The generated app contains a shared Axios instance with ABP-specific request and response interceptors, plus typed API modules for backend endpoints. + +The shared client is defined in `src/lib/api/axios.ts` and exported as `api`. + +## Base URL + +The Axios base URL is resolved at request time from runtime configuration: + +```ts +export function getApiBaseUrl(): string { + const apiUrl = getApiUrl() + if (apiUrl.startsWith('http://') || apiUrl.startsWith('https://')) { + return apiUrl.replace(/\/$/, '') + '/api' + } + if (import.meta.env.DEV) { + return '/api' + } + return apiUrl.replace(/\/$/, '') + '/api' +} +``` + +The API URL comes from: + +1. `dynamic-env.json` -> `apis.default.url` +2. `VITE_API_URL` +3. `src/env.ts` generated fallback + +In microservice solutions, `apis.default.url` normally points to the Web Gateway. In layered and single-layer solutions, it normally points to the HTTP API host. + +## Shared Axios Instance + +The template creates one shared instance: + +```ts +export const api = axios.create({ + baseURL: '', + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'Content-Type': 'application/json', + }, +}) +``` + +Use this instance for application API modules instead of creating new Axios clients. It centralizes ABP headers, authentication, tenant handling, language handling, and redirects. + +## Request Interceptor + +Before each request, the template: + +- Sets `baseURL` from runtime configuration. +- Adds `Authorization: Bearer ` from the OIDC user. +- Adds `__tenant` when the user has selected a tenant. +- Adds `Accept-Language` from i18next. +- Keeps default AJAX headers such as `X-Requested-With`. + +```ts +api.interceptors.request.use(async (config) => { + config.baseURL = getApiBaseUrl() + + const user = await userManager.getUser() + if (user?.access_token) { + config.headers.Authorization = `Bearer ${user.access_token}` + } + + const tenantId = sessionStorage.getItem('abp_tenant_id') + if (tenantId && !config.headers.__tenant) { + config.headers.__tenant = tenantId + } + + if (i18n?.language) { + config.headers['Accept-Language'] = + config.headers['Accept-Language'] ?? i18n.language + } + + return config +}) +``` + +## Response Interceptor + +The response interceptor handles common authorization failures: + +- `401 Unauthorized`: redirects to login unless `skipAuthRedirect` is set. +- `403 Forbidden`: redirects to `/403` unless `skip403Redirect` is set. +- Other errors are rejected so the caller can handle them. + +```ts +api.interceptors.response.use( + (response) => response, + async (error) => { + const status = error.response?.status + + if (status === 401 && !error.config?.skipAuthRedirect) { + await userManager.signinRedirect() + return Promise.reject(new Error('Unauthorized - redirecting to login')) + } + + if (status === 403 && !error.config?.skip403Redirect) { + window.location.href = '/403' + return Promise.reject(new Error('Forbidden')) + } + + return Promise.reject(error) + } +) +``` + +Use `skipAuthRedirect` or `skip403Redirect` for calls where the component should handle the error itself. + +## Typed API Modules + +The template organizes backend calls under `src/lib/api/`. For example, the Books sample defines DTOs and functions in `books.ts`: + +```ts +import { api } from './axios' + +export interface PagedResultDto { + items: T[] + totalCount: number +} + +export interface BookDto { + id: string + name?: string + price: number +} + +export async function getBooks(): Promise> { + const { data } = await api.get>('/app/book', { + params: { + maxResultCount: 10, + skipCount: 0, + }, + }) + return data +} +``` + +Notice that the API module calls `/app/book`, not `/api/app/book`. The shared Axios base URL already includes the `/api` prefix when needed. + +## Using Requests from Components + +The template uses TanStack Query for server state: + +```tsx +const { data, isLoading } = useQuery({ + queryKey: ['books', skipCount], + queryFn: () => + getBooks({ + maxResultCount: 10, + skipCount, + sorting: 'creationTime desc', + }), +}) +``` + +Mutations use `useMutation` and invalidate related queries after success: + +```tsx +const createMutation = useMutation({ + mutationFn: createBook, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['books'] }) + toast.success(t('AbpUi::SavedSuccessfully')) + }, +}) +``` + +## Adding a New API Module + +Create a file under `src/lib/api/`: + +```ts +import { api } from './axios' + +export interface ProductDto { + id: string + name: string +} + +export async function getProducts(): Promise { + const { data } = await api.get('/app/product') + return data +} +``` + +Then consume it from a component with TanStack Query: + +```tsx +const productsQuery = useQuery({ + queryKey: ['products'], + queryFn: getProducts, +}) +``` + +## Development Proxy + +In development, Vite proxies `/api`, `/connect`, and `/getEnvConfig`. This lets the React app use same-origin paths while calls are forwarded to the backend, Auth Server, or gateway configured by `VITE_API_URL` and `VITE_AUTH_URL`. + +## See Also + +- [Environment Variables](./environment-variables.md) +- [Authorization](./authorization.md) +- [Permission Management](./permission-management.md) diff --git a/docs/en/framework/ui/react/index.md b/docs/en/framework/ui/react/index.md new file mode 100644 index 0000000000..4483e7c84f --- /dev/null +++ b/docs/en/framework/ui/react/index.md @@ -0,0 +1,142 @@ +```json +//[doc-seo] +{ + "Description": "Learn how to build modern web applications with ABP React UI, including runtime configuration, authentication, Admin Console, shadcn/ui components, and testing." +} +``` + +# React UI + +ABP provides a **React UI** option for building modern, client-side web applications. React UI is part of the **modern template system** and is available with **ABP Studio v3.0+** through the Modern Wizard or with `abp new --modern` using [ABP CLI](../../../cli/index.md). + +React UI is not available in classic, non-modern templates. Use ABP Studio's modern template flow or `Volo.Abp.Studio.Cli` to create a React-based solution. + +## Technology Stack + +The React UI template is built with: + +| Technology | Purpose | +| --- | --- | +| [Vite](https://vite.dev/) | Build tool and dev server | +| [React](https://react.dev/) | UI framework | +| [TanStack Router](https://tanstack.com/router) | Client-side routing | +| [TanStack Query](https://tanstack.com/query) | Server state and API request orchestration | +| [shadcn/ui](https://ui.shadcn.com/) | Source-owned component library built on Radix UI and Tailwind CSS | +| [Zod](https://zod.dev/) | Schema validation | +| [React Hook Form](https://react-hook-form.com/) | Form state management | +| [Axios](https://axios-http.com/) | HTTP client | +| [Vitest](https://vitest.dev/) | Unit testing | +| [OpenID Connect / OIDC](https://openid.net/connect/) | Authentication against the ABP Auth Server | + +The template also includes ABP-specific NPM packages: + +- [`@volo/abp-app-config`](https://github.com/volosoft/volo/tree/dev/abp/npm/packs/abp-app-config) +- [`@volo/abp-oidc-auth`](https://github.com/volosoft/volo/tree/dev/abp/npm/packs/abp-oidc-auth) +- [`@volo/abp-react-app-config`](https://github.com/volosoft/volo/tree/dev/abp/npm/packs/abp-react-app-config) +- [`@volo/abp-react-oidc-auth`](https://github.com/volosoft/volo/tree/dev/abp/npm/packs/abp-react-oidc-auth) + +## React App and Admin Console + +A modern React solution contains two UI surfaces: + +- **Your React application**: the developer-owned SPA where you build application-specific pages and features. +- **ABP Admin Console**: the React-based administration UI for ABP modules. + +The Admin Console is provided by the `Volo.Abp.AdminConsole` NuGet package in layered and single-layer templates. In microservice templates, it is also generated as a separate `apps/react-admin-console/` app and served through the Web Gateway. + +See [Admin Console](./admin-console.md) for hosting, module discovery, and permission details. + +## Solution Structure + +The React app location depends on the modern template type: + +- **Layered (`app --modern`) and single-layer (`app-nolayers --modern`)**: the React app lives in the `react/` folder at the solution root. +- **Microservice (`microservice --modern`)**: the React app lives at `apps/react/`. + +Typical structure: + +```text +react/ +├── dynamic-env.json +├── public/ +├── src/ +│ ├── components/ +│ ├── lib/ +│ ├── locales/ +│ ├── pages/ +│ ├── routes/ +│ └── main.tsx +├── package.json +├── vite.config.ts +└── vitest.config.ts +``` + +## Creating a Solution + +Install or update `Volo.Abp.Studio.Cli`, then create a modern solution: + +```bash +# Layered app with React UI +abp new Acme.BookStore --template app --modern --ui-framework react + +# Single-layer app with React UI +abp new Acme.BookStore --template app-nolayers --modern --ui-framework react + +# Microservice solution with React UI +abp new Acme.BookStore --template microservice --modern --ui-framework react +``` + +You can also use ABP Studio v3.0+ and select the modern template flow in the New Solution wizard. The wizard preconfigures local ports, runtime configuration, OIDC clients, theme options, and React/Admin Console wiring based on the selected template and modules. + +## Running the Application + +Start the backend from ABP Studio or by running the backend host projects, then start the React development server. + +For layered and single-layer templates: + +```bash +cd react +npm install +npm run dev +``` + +For microservice templates: + +```bash +cd apps/react +npm install +npm run dev +``` + +Run tests with: + +```bash +npm run test +``` + +Build for production with: + +```bash +npm run build +``` + +## Documentation Map + +Use these pages to learn each part of the React UI: + +- [Environment Variables](./environment-variables.md): runtime configuration, `dynamic-env.json`, Vite variables, and Studio-generated defaults. +- [Authorization](./authorization.md): OIDC, Authorization Code flow with PKCE, auth provider, hooks, and route guards. +- [Localization](./localization.md): i18next, local JSON resources, ABP localization keys, and request culture. +- [Permission Management](./permission-management.md): fetching granted policies, `usePermissions()`, route protection, and conditional UI. +- [HTTP Requests](./http-requests.md): Axios setup, interceptors, typed API modules, and TanStack Query usage. +- [Customization](./customization.md): changing pages, themes, sidebar items, user menu entries, and shadcn/ui components. +- [Components](./components/index.md): component architecture, UI primitives, layout components, forms, and routing. +- [Unit Testing](./unit-testing.md): Vitest, React Testing Library, examples, and test workflow. +- [Admin Console](./admin-console.md): the `Volo.Abp.AdminConsole` package, `/admin-console/*` hosting, module discovery, and optional modules. + +## See Also + +- [ABP Studio](../../../studio/index.md) +- [ABP CLI](../../../cli/index.md) +- [Authorization](../../../framework/fundamentals/authorization/index.md) +- [Localization](../../../framework/fundamentals/localization.md) diff --git a/docs/en/framework/ui/react/localization.md b/docs/en/framework/ui/react/localization.md new file mode 100644 index 0000000000..64298278f0 --- /dev/null +++ b/docs/en/framework/ui/react/localization.md @@ -0,0 +1,158 @@ +```json +//[doc-seo] +{ + "Description": "Learn how localization works in ABP React UI applications with i18next and ABP application configuration." +} +``` + +# Localization + +ABP React UI templates use [i18next](https://www.i18next.com/) with [react-i18next](https://react.i18next.com/). The generated app includes local JSON resources and integrates with ABP application configuration through the `@volo/abp-app-config` packages. + +## Localization Files + +The main React app stores client-side translations under `src/locales/`. + +```text +src/ +├── locales/ +│ └── en.json +└── lib/ + └── i18n/ + └── i18n.ts +``` + +The default `i18n.ts` imports the English resource and registers it: + +```ts +import i18n from 'i18next' +import { initReactI18next } from 'react-i18next' +import en from '@/locales/en.json' + +i18n.use(initReactI18next).init({ + resources: { + en: { translation: en }, + }, + lng: 'en', + fallbackLng: 'en', + keySeparator: false, + nsSeparator: false, + interpolation: { + escapeValue: false, + }, +}) +``` + +`keySeparator` and `nsSeparator` are disabled so ABP-style keys such as `AbpIdentity::Users` and `Menu:Home` can be used directly. + +## Using Localized Text + +Use `useTranslation()` from `react-i18next` in components: + +```tsx +import { useTranslation } from 'react-i18next' + +export function BooksTitle() { + const { t } = useTranslation() + + return

{t('Menu:Books')}

+} +``` + +ABP localization keys commonly use the `ResourceName::Key` format: + +```tsx +{t('AbpIdentity::Users')} +{t('AbpAccount::Login')} +{t('AbpUi::SavedSuccessfully')} +``` + +Application-specific menu keys may use names like `Menu:Home` or `Menu:Books`. + +## Adding a Translation + +Add the key to `src/locales/en.json`: + +```json +{ + "Menu:Reports": "Reports", + "Reports": "Reports" +} +``` + +Then use it from a component: + +```tsx +const { t } = useTranslation() + +return

{t('Reports')}

+``` + +## Adding a Language + +Create a new JSON file, for example `src/locales/tr.json`: + +```json +{ + "Menu:Reports": "Raporlar", + "Reports": "Raporlar" +} +``` + +Register it in `src/lib/i18n/i18n.ts`: + +```ts +import en from '@/locales/en.json' +import tr from '@/locales/tr.json' + +i18n.use(initReactI18next).init({ + resources: { + en: { translation: en }, + tr: { translation: tr }, + }, + lng: 'en', + fallbackLng: 'en', +}) +``` + +If you add a language selector, call `i18n.changeLanguage('tr')` when the user chooses Turkish. + +## Server-Side ABP Localization + +ABP's backend localization system is still the source of truth for server-defined resources, validation messages, exception messages, and module texts. The React app uses ABP application configuration through `@volo/abp-app-config` / `@volo/abp-react-app-config` for auth and configuration data, and these packages can include localization resources when configured to do so. + +The main template currently creates the app configuration client with: + +```ts +export const appConfig = createAbpReactAppConfig({ + baseUrl: () => getApiUrl(), + includeLocalizationResources: false, +}) +``` + +Because `includeLocalizationResources` is disabled in the main React template, UI text is normally loaded from `src/locales/*.json`. If you enable server-provided localization resources, make sure your UI initialization merges them into i18next before rendering localized components. + +## Request Culture + +The shared Axios client sends the active i18next language with each request: + +```ts +if (i18n?.language) { + config.headers['Accept-Language'] = + config.headers['Accept-Language'] ?? i18n.language +} +``` + +This lets backend responses, validation messages, and exception messages use the selected culture when the server supports it. + +## Admin Console Localization + +The Admin Console has its own React app and localization setup. In layered and single-layer templates, it is served from the `Volo.Abp.AdminConsole` package. In microservice templates, it is generated as `apps/react-admin-console/`. + +The Admin Console host can expose available languages through `AdminConsole:LocalizationLanguages`, and `/admin-console/api/config` returns the normalized language list. + +## See Also + +- [React UI](./index.md) +- [HTTP Requests](./http-requests.md) +- [Localization](../../../framework/fundamentals/localization.md) diff --git a/docs/en/framework/ui/react/permission-management.md b/docs/en/framework/ui/react/permission-management.md new file mode 100644 index 0000000000..fc009ce5a2 --- /dev/null +++ b/docs/en/framework/ui/react/permission-management.md @@ -0,0 +1,171 @@ +```json +//[doc-seo] +{ + "Description": "Learn how permissions are fetched, stored, checked, and applied in ABP React UI applications." +} +``` + +# Permission Management + +ABP permissions are defined on the server side and are exposed to the React app through ABP application configuration. The React template uses those permissions to protect routes, hide sidebar items, and conditionally render UI actions. + +For the server-side permission system, see [Authorization](../../../framework/fundamentals/authorization/index.md). + +## Packages + +The React template uses: + +| Package | Purpose | +| --- | --- | +| `@volo/abp-app-config` | Framework-agnostic ABP application configuration client. | +| `@volo/abp-react-app-config` | React hooks and adapters for application configuration. | + +The template creates a shared app configuration client in `src/lib/auth/permissions.ts`: + +```ts +export const appConfig = createAbpReactAppConfig({ + baseUrl: () => getApiUrl(), + includeLocalizationResources: false, +}) +``` + +## Fetching Permissions + +After the user logs in, `AuthProvider` fetches application configuration with the current access token: + +```ts +const user = await authClient.getUserManager().getUser() +if (user && !user.expired) { + await fetchAppConfig(user.access_token ?? null) +} +``` + +`fetchAppConfig` also sends the current tenant ID when one is selected: + +```ts +export async function fetchAppConfig(token: string | null): Promise { + const headers: Record = {} + const tenantId = sessionStorage.getItem('abp_tenant_id') + if (tenantId) headers.__tenant = tenantId + await appConfig.fetchConfig(token, { headers }) +} +``` + +The response includes the current user's granted policies. These are stored by the app configuration client and exposed to React components. + +## Checking Permissions in Components + +Use `usePermissions()` from `src/lib/auth/permissions.ts`: + +```tsx +import { usePermissions } from '@/lib/auth/permissions' + +export function BookActions() { + const { isGranted } = usePermissions() + + return ( + <> + {isGranted('MyProjectName.Books.Edit') && } + {isGranted('MyProjectName.Books.Delete') && } + + ) +} +``` + +The Books page uses this pattern for edit and delete actions: + +```ts +const { isGranted } = usePermissions() +const canEdit = isGranted('MyProjectName.Books.Edit') +const canDelete = isGranted('MyProjectName.Books.Delete') +``` + +## Route Guards + +Routes can require a permission by using `createPermissionGuard`: + +```ts +const booksRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/books', + component: BooksPage, + beforeLoad: createPermissionGuard('MyProjectName.Books'), +}) +``` + +`createPermissionGuard` runs the authentication guard first, fetches app configuration if needed, and redirects to `/403` when the required policy is not granted. + +```ts +export function createPermissionGuard(requiredPolicy: string) { + return async (context: GuardContext) => { + await authGuard(context) + + if (!appConfig.getSnapshot()?.initialized) { + const user = await userManager.getUser() + await fetchAppConfig(user?.access_token ?? null) + } + + if (!isPolicyGranted(requiredPolicy)) throw redirect({ to: '/403' }) + } +} +``` + +## Sidebar Visibility + +The sidebar reads `routeConfig` and hides items that require missing permissions: + +```ts +export const routeConfig: RouteConfigItem[] = [ + { + path: '/identity/users', + nameKey: 'AbpIdentity::Users', + requiredPolicy: 'AbpIdentity.Users', + }, +] +``` + +The sidebar checks each item: + +```ts +if (item.requiresAuth && !isAuthenticated) return false +if (!item.requiredPolicy) return true +if (!isAuthenticated) return false +return isGranted(item.requiredPolicy) +``` + +Use `requiresAuth` for menu items that only require login. Use `requiredPolicy` when the item should only be visible to users with a specific permission. + +## Compound Policies + +The template's `isPolicyGranted` helper supports simple compound expressions: + +- `PermissionA || PermissionB` +- `PermissionA && PermissionB` + +This is useful for menu entries that should be visible when the user has one of several related module permissions. + +## Where Permissions Are Applied + +The generated React app uses permissions in these places: + +- **Users page**: the `/identity/users` route and sidebar entry require `AbpIdentity.Users`. The page links to the Admin Console for full user and role management. +- **Books page**: the route requires `MyProjectName.Books`; edit and delete actions check `MyProjectName.Books.Edit` and `MyProjectName.Books.Delete`. +- **Admin Console link**: the sidebar entry uses `requiresAuth` because the Admin Console performs its own module and route permission checks. + +The Admin Console applies module-specific permissions for pages such as: + +- Identity users and roles: `AbpIdentity.*`. +- OpenIddict applications and scopes: `OpenIddictPro.Application` and `OpenIddictPro.Scope`. +- Audit Logging UI: `AuditLogging.AuditLogs`. +- Text Template Management: `TextTemplateManagement.*`. +- AI Management: `AIManagement.*`. + +## Multi-Tenancy + +When a tenant is selected, the template stores the tenant ID in `sessionStorage` as `abp_tenant_id`. Permission and API requests send it with the `__tenant` header. This ensures the backend returns permissions and data for the selected tenant context. + +## See Also + +- [Authorization](./authorization.md) +- [HTTP Requests](./http-requests.md) +- [Authorization](../../../framework/fundamentals/authorization/index.md) diff --git a/docs/en/framework/ui/react/unit-testing.md b/docs/en/framework/ui/react/unit-testing.md new file mode 100644 index 0000000000..46a3089eeb --- /dev/null +++ b/docs/en/framework/ui/react/unit-testing.md @@ -0,0 +1,150 @@ +```json +//[doc-seo] +{ + "Description": "Learn how to run and write unit tests in ABP React UI applications with Vitest and React Testing Library." +} +``` + +# Unit Testing React UI + +ABP React UI templates are preconfigured for unit testing. A solution created with ABP Studio v3.0+ or `abp new --modern --ui-framework react` includes Vitest, jsdom, React Testing Library, and jest-dom. + +You can add a test file and run the test command without adding extra test infrastructure. + +## Test Stack + +The React template uses: + +| Package | Purpose | +| --- | --- | +| `vitest` | Test runner and assertion library. | +| `jsdom` | Browser-like DOM environment for component tests. | +| `@testing-library/react` | Render React components and query the DOM like a user. | +| `@testing-library/jest-dom` | Extra DOM assertions such as `toBeInTheDocument`. | + +The template also includes `src/test/setup.ts`, which imports `@testing-library/jest-dom/vitest` and initializes the React i18n setup. + +## Configuration + +The test configuration is in `vitest.config.ts`: + +```ts +import { defineConfig } from 'vitest/config' +import react from '@vitejs/plugin-react' +import path from 'path' + +export default defineConfig({ + plugins: [react()], + test: { + environment: 'jsdom', + setupFiles: ['./src/test/setup.ts'], + include: ['src/**/*.{test,spec}.{ts,tsx}'], + globals: true, + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, +}) +``` + +Tests can import application files with the same `@/` alias used by the app. + +## Running Tests + +Install dependencies once: + +```bash +npm install +``` + +Run tests in watch mode: + +```bash +npm run test +``` + +Run tests once, which is useful for CI: + +```bash +npm run test:run +``` + +The template's `package.json` maps these commands to `vitest` and `vitest run`. + +## Example Test + +The template includes example tests under `src/`. For example, `src/pages/home/HomePage.test.tsx` renders the home page and mocks the authentication hook: + +```tsx +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { render, screen } from '@testing-library/react' +import { HomePage } from './HomePage' +import * as auth from '@/lib/auth/AuthContext' + +vi.mock('@/lib/auth/AuthContext', () => ({ + useAuth: vi.fn(), +})) + +describe('HomePage', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('renders login prompt when not authenticated', () => { + vi.mocked(auth.useAuth).mockReturnValue({ + isAuthenticated: false, + isLoading: false, + user: null, + login: vi.fn(), + logout: vi.fn(), + navigateToLogin: vi.fn(), + getAccessToken: vi.fn(), + } as unknown as ReturnType) + + render() + expect(screen.getByText('Welcome')).toBeInTheDocument() + expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument() + }) +}) +``` + +This style keeps the test focused on visible behavior. Dependencies that would require real authentication, network calls, or browser redirects are mocked. + +## Writing a Component Test + +Create a `*.test.tsx` file next to the component: + +```tsx +import { render, screen } from '@testing-library/react' +import { describe, expect, it } from 'vitest' +import { Button } from '@/components/ui/button' + +describe('Button', () => { + it('renders its content', () => { + render() + expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument() + }) +}) +``` + +Prefer queries such as `getByRole`, `getByLabelText`, and `getByText` because they describe what the user can see or do. + +## Writing a Service or Hook Test + +For non-component logic, use Vitest directly. The template includes tests for routing guards, permissions, authentication context, and Axios interceptors. + +When testing API code, mock the shared Axios instance or the lower-level dependency instead of calling a real backend. When testing permission behavior, mock the application configuration client or use the exported permission helpers. + +## Interpreting Output + +Vitest reports each test file, failed assertions, stack traces, and a summary of passed/failed tests. In watch mode, it reruns affected tests when files change. In `test:run` mode, Vitest exits with a non-zero status code if any test fails, which makes it suitable for CI pipelines. + +If a component test fails because an ABP service is not initialized, mock the hook or provider used by the component. For example, pages that call `useAuth()` or `usePermissions()` should provide a controlled mock for those hooks unless the test is specifically verifying the provider. + +## See Also + +- [Components](./components/index.md) +- [Authorization](./authorization.md) +- [Permission Management](./permission-management.md) diff --git a/docs/en/get-started/images/abp-studio-mobile-sample.gif b/docs/en/get-started/images/abp-studio-mobile-sample.gif index 4301c451b6..2a165b9900 100644 Binary files a/docs/en/get-started/images/abp-studio-mobile-sample.gif and b/docs/en/get-started/images/abp-studio-mobile-sample.gif differ diff --git a/docs/en/images/abp-studio-ai-agent.png b/docs/en/images/abp-studio-ai-agent.png new file mode 100644 index 0000000000..d74add4b2d Binary files /dev/null and b/docs/en/images/abp-studio-ai-agent.png differ diff --git a/docs/en/images/react-ui-and-admin-console.png b/docs/en/images/react-ui-and-admin-console.png new file mode 100644 index 0000000000..8b2dbea60b Binary files /dev/null and b/docs/en/images/react-ui-and-admin-console.png differ diff --git a/docs/en/images/rn-home-screen.png b/docs/en/images/rn-home-screen.png new file mode 100644 index 0000000000..cc01a2a6d2 Binary files /dev/null and b/docs/en/images/rn-home-screen.png differ diff --git a/docs/en/images/rn-login-iphone.png b/docs/en/images/rn-login-iphone.png index 576c260f5e..7bc468584e 100644 Binary files a/docs/en/images/rn-login-iphone.png and b/docs/en/images/rn-login-iphone.png differ diff --git a/docs/en/images/rn-nav-comparison.png b/docs/en/images/rn-nav-comparison.png new file mode 100644 index 0000000000..02fe01a6f7 Binary files /dev/null and b/docs/en/images/rn-nav-comparison.png differ diff --git a/docs/en/modules/account/shared-user-accounts.md b/docs/en/modules/account/shared-user-accounts.md index 4df18e2fdf..6f028238c6 100644 --- a/docs/en/modules/account/shared-user-accounts.md +++ b/docs/en/modules/account/shared-user-accounts.md @@ -142,6 +142,30 @@ Configure(options => ![new-user--join-strategy-inform](../../images/new-user-join-strategy-inform.png) +## Tenant Admin vs Host Admin Operations + +When the Shared strategy is enabled, a user is a **global resource** across host and all tenants. Their identity, activation state, lockout, password policy and two-factor settings live at the host level. Therefore some user management operations are restricted to host administrators and are not available to tenant administrators. + +### Host-only operations + +The following operations can only be performed by a host administrator when Shared is enabled. Both the Identity Pro UI (MVC + Blazor) and the `IdentityUserAppService` enforce this — a direct API call from a tenant context will be rejected with a `UserFriendlyException`: + +- Delete a user +- Activate / deactivate a user (`IsActive`) +- Lock / unlock a user +- Enable or disable two-factor authentication +- Change `LockoutEnabled` or `ShouldChangePasswordOnNextLogin` + +### What tenant admins can do + +- Invite users to the tenant (see the Invitation flow above) +- Manage role and organization-unit assignments within the tenant +- View audit / security logs scoped to the tenant + +### What a user can do for themselves + +Users can leave a tenant from their own account menu (`Switch Tenant` → `Leave`). Leaving marks the tenant membership as `Leaved = true` and preserves the user's host identity, so they can be re-invited later with the same `UserId`. + ## Migration Guide If you plan to migrate an existing multi-tenant application from an isolated strategy to Shared User Accounts, keep the following in mind: diff --git a/docs/en/modules/elsa-pro.md b/docs/en/modules/elsa-pro.md index 6b93e42368..63e95bdb59 100644 --- a/docs/en/modules/elsa-pro.md +++ b/docs/en/modules/elsa-pro.md @@ -46,7 +46,7 @@ After adding the package references, open the module class of the project (e.g.: ### `AbpElsaAspNetCoreModule` and `AbpElsaIdentityModule` -These two modules generally will be added to your authentication project. Please add `Volo.Elsa.Abp.AspNetCore` and `Volo.Elsa.Abp.Identity` packages to your project and add the `AbpElsaAspNetCoreModule` and `AbpElsaIdentityModule` to the `DependsOn` attribute of your module class based on your project structure: +These two modules generally will be added to your authentication project. Please add `Volo.Abp.Elsa.AspNetCore` and `Volo.Abp.Elsa.Identity` packages to your project and add the `AbpElsaAspNetCoreModule` and `AbpElsaIdentityModule` to the `DependsOn` attribute of your module class based on your project structure: ```xml @@ -65,20 +65,20 @@ These two modules generally will be added to your authentication project. Please The Elsa Workflows has its own database provider, and also has a Tenant/Role/User system. They are under active development, so the ABP Elsa module is not yet fully integrated. Below is the current status of each module in the ABP's Elsa Module: -- `AbpElsaAspNetCoreModule(Volo.Elsa.Abp.AspNetCore)` module is used to integrate Elsa authentication. -- `AbpElsaIdentityModule(Volo.Elsa.Abp.Identity)` module is used to integrate ABP Identity authentication. -- `AbpElsaApplicationModule(Volo.Elsa.Abp.Application)` and `AbpElsaApplicationContractsModule(Volo.Elsa.Abp.Application.Contracts)` modules are used to define the Elsa permissions. +- `AbpElsaAspNetCoreModule(Volo.Abp.Elsa.AspNetCore)` module is used to integrate Elsa authentication. +- `AbpElsaIdentityModule(Volo.Abp.Elsa.Identity)` module is used to integrate ABP Identity authentication. +- `AbpElsaApplicationModule(Volo.Abp.Elsa.Application)` and `AbpElsaApplicationContractsModule(Volo.Abp.Elsa.Application.Contracts)` modules are used to define the Elsa permissions. The rest of the projects/modules are basically empty and will be implemented in the future based on the Elsa features: -- `AbpElsaDomainModule(Volo.Elsa.Abp.Domain)` -- `AbpElsaEntityFrameworkCoreModule(Volo.Elsa.Abp.EntityFrameworkCore)` -- `AbpElsaHttpApiModule(Volo.Elsa.Abp.HttpApi)` -- `AbpElsaHttpApiClientModule(Volo.Elsa.Abp.HttpApi.Client)` -- `AbpElsaBlazorModule(Volo.Elsa.Abp.Blazor)` -- `AbpElsaBlazorServerModule(Volo.Elsa.Abp.Blazor.Server)` -- `AbpElsaBlazorWebAssemblyModule(Volo.Elsa.Abp.Blazor.WebAssembly)` -- `AbpElsaWebModule(Volo.Elsa.Abp.Web)` +- `AbpElsaDomainModule(Volo.Abp.Elsa.Domain)` +- `AbpElsaEntityFrameworkCoreModule(Volo.Abp.Elsa.EntityFrameworkCore)` +- `AbpElsaHttpApiModule(Volo.Abp.Elsa.HttpApi)` +- `AbpElsaHttpApiClientModule(Volo.Abp.Elsa.HttpApi.Client)` +- `AbpElsaBlazorModule(Volo.Abp.Elsa.Blazor)` +- `AbpElsaBlazorServerModule(Volo.Abp.Elsa.Blazor.Server)` +- `AbpElsaBlazorWebAssemblyModule(Volo.Abp.Elsa.Blazor.WebAssembly)` +- `AbpElsaWebModule(Volo.Abp.Elsa.Web)` ## Configure the Elsa Server @@ -129,7 +129,7 @@ The Elsa Workflow API endpoints check permissions. Also, it has a `*` wildcard p The ABP Elsa module defines all permissions that are used in the Elsa workflow. You can use ABP Permission Management module to manage the permissions. -`AbpElsaAspNetCoreModule(Volo.Elsa.Abp.AspNetCore)` module will check and add these permissions to the current user's claims: +`AbpElsaAspNetCoreModule(Volo.Abp.Elsa.AspNetCore)` module will check and add these permissions to the current user's claims: ![Elsa Permissions](../images/elsa-permissions.png) @@ -158,7 +158,7 @@ Elsa Studio requires authentication and there are two ways to authenticate Elsa ##### Elsa Studio - Password Flow Authentication -The `AbpElsaIdentityModule(Volo.Elsa.Abp.Identity)` module is used to integrate with [ABP Identity module](./identity-pro.md) to check Elsa Studio *username* and *password* against ABP Identity. +The `AbpElsaIdentityModule(Volo.Abp.Elsa.Identity)` module is used to integrate with [ABP Identity module](./identity-pro.md) to check Elsa Studio *username* and *password* against ABP Identity. You need to replace `UseIdentity` with `UseAbpIdentity` when configuring Elsa in your Elsa server project as follows: diff --git a/docs/en/modules/identity/two-factor-authentication.md b/docs/en/modules/identity/two-factor-authentication.md index 1ac4b5dd75..c94962ce99 100644 --- a/docs/en/modules/identity/two-factor-authentication.md +++ b/docs/en/modules/identity/two-factor-authentication.md @@ -121,3 +121,61 @@ Configure(IdentityConstants.TwoFactorRememberMeSche options.Cookie.Name = "MyRememberMeCookieName"; //override the cookie name }); ``` + +## How the Verification Code Is Generated + +The codes delivered by the **Email** and **SMS** verification providers are produced by ABP's built-in single-use token providers, registered in `AbpIdentityAspNetCoreModule`: + +- `AbpEmailTwoFactorTokenProvider` is registered under `TokenOptions.DefaultEmailProvider` and replaces ASP.NET Core Identity's TOTP-based `EmailTokenProvider`. +- `AbpPhoneNumberTwoFactorTokenProvider` is registered under `TokenOptions.DefaultPhoneProvider` and replaces ASP.NET Core Identity's TOTP-based `PhoneNumberTokenProvider`. + +Both derive from the abstract `AbpTwoFactorTokenProvider`. The `Authenticator` provider is unaffected: it is overridden by `AbpAuthenticatorTokenProvider`, which still relies on TOTP ([RFC 6238](https://datatracker.ietf.org/doc/html/rfc6238)) because authenticator apps require it. + +On generation, the provider produces a cryptographically-random numeric code (default 6 digits), encrypts it together with an absolute UTC expiration via `IDataProtector`, and persists the resulting blob in the user tokens table. The plaintext code is sent to the user via email/SMS and is never stored. Validation reloads the persisted entry, verifies it has not expired, decrypts and compares constant-time against the submitted input, and — on success — removes the entry so it cannot be replayed. + +This persisted, single-use design has a few properties worth being explicit about: + +1. **A generated code is single-use.** Successful verification removes the stored entry. Re-submitting the same code from a concurrent session fails. +2. **Generating a new code invalidates the previous one.** `SetToken` overwrites the same `(provider, name)` row, so at most one code is valid at any time. Re-issuing a code (e.g. when the user requests a new one) replaces the stored entry and the previously delivered code stops working. +3. **The validity window is exactly the configured lifespan (3 minutes by default).** Expiration is captured as an absolute Unix-seconds value at generation time and is not extended at validation — in contrast to TOTP-based providers, which accept the previous timestep as well and effectively give a 3–6 minute window. +4. **Failed verification keeps the stored entry in place** so the user can retry until expiration. Rate-limiting incorrect attempts is delegated to ASP.NET Core Identity's lockout settings. +5. **Concurrent successful verification returns `false` instead of throwing.** Two requests racing to consume the same code go through the user row's `ConcurrencyStamp`; the loser surfaces as a normal validation failure rather than a 500. +6. **Expired or undecryptable entries are cleaned up on next access.** A stale entry encountered during validation is removed before returning `false`, so the next `GenerateAsync` starts from a clean slate. + +## Configuring the Default Providers + +Both built-in providers expose options classes (`AbpEmailTwoFactorTokenProviderOptions` and `AbpPhoneNumberTwoFactorTokenProviderOptions`) inheriting from `AbpTwoFactorTokenProviderOptions`: + +| Option | Default | Notes | +| --- | --- | --- | +| `TokenLifespan` | `TimeSpan.FromMinutes(3)` | Absolute lifetime of an issued code. | +| `CodeLength` | `6` | Number of digits in the generated code. Valid range: `1`–`9`. | + +Configure them in your module's `ConfigureServices`: + +```csharp +Configure(options => +{ + options.TokenLifespan = TimeSpan.FromMinutes(5); + options.CodeLength = 8; +}); + +Configure(options => +{ + options.TokenLifespan = TimeSpan.FromMinutes(2); +}); +``` + +## Replacing the Verification Code Provider + +If the built-in single-use behavior does not match your requirements (e.g. you need alphanumeric codes, a different storage backend or a custom delivery policy), you can replace either provider by registering your own `IUserTwoFactorTokenProvider` under the same key. `AddTokenProvider` with an existing key replaces the previous descriptor in `TokenOptions.ProviderMap`: + +```csharp +PreConfigure(builder => +{ + builder.AddTokenProvider(TokenOptions.DefaultEmailProvider); + builder.AddTokenProvider(TokenOptions.DefaultPhoneProvider); +}); +``` + +The most ergonomic starting point is to subclass `AbpTwoFactorTokenProvider` and override only the parts you want to change (for example `GenerateNumericCode`, `CreateProtector`, or the storage helpers). `AccountAppService.SendTwoFactorCodeAsync` and `SignInManager.TwoFactorSignInAsync` call through `UserManager.GenerateTwoFactorTokenAsync` and `UserManager.VerifyTwoFactorTokenAsync` respectively, so a registered replacement is invoked without any further wiring. diff --git a/docs/en/modules/openiddict.md b/docs/en/modules/openiddict.md index c1ed40ff9f..910bb2c738 100644 --- a/docs/en/modules/openiddict.md +++ b/docs/en/modules/openiddict.md @@ -303,6 +303,18 @@ PreConfigure(options => - `UpdateAbpClaimTypes(default: true)`: Updates `AbpClaimTypes` to be compatible with the Openiddict claims. - `AddDevelopmentEncryptionAndSigningCertificate(default: true)`: Registers (and generates if necessary) a user-specific development encryption/development signing certificate. This is a certificate used for signing and encrypting the tokens and for **development environment only**. You must set it to **false** for non-development environments. +- `UseDefaultScopesForClientCredentials(default: false)`: When set to `true`, the access token issued for the `client_credentials` grant automatically grants the scopes configured on the client application (permissions prefixed with `oi_scp:`) when the client does not explicitly request any scope. +- `UseDefaultScopesForPassword(default: false)`: When set to `true`, the token response for the `password` grant automatically grants the scopes configured on the client application when the client does not explicitly request any scope. If the configured scopes include `openid`/`profile`/`email`/`roles`, the corresponding `id_token` and claim destinations are affected as well. +- `UseDefaultScopesForTokenExchange(default: false)`: When set to `true`, the token response for the `urn:ietf:params:oauth:grant-type:token-exchange` grant automatically grants the scopes configured on the client application when the client does not explicitly request any scope. If the configured scopes include `openid`/`profile`/`email`/`roles`, the corresponding `id_token` and claim destinations are affected as well. + +Example to enable the default-scope fallback for the `client_credentials` grant: + +```csharp +PreConfigure(options => +{ + options.UseDefaultScopesForClientCredentials = true; +}); +``` > `AddDevelopmentEncryptionAndSigningCertificate` cannot be used in applications deployed on IIS or Azure App Service: trying to use them on IIS or Azure App Service will result in an exception being thrown at runtime (unless the application pool is configured to load a user profile). To avoid that, consider creating self-signed certificates and storing them in the X.509 certificates store of the host machine(s). Please refer to: https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html#registering-a-development-certificate diff --git a/docs/en/modules/text-template-management.md b/docs/en/modules/text-template-management.md index fa62ee7470..32b7cc31a9 100644 --- a/docs/en/modules/text-template-management.md +++ b/docs/en/modules/text-template-management.md @@ -164,6 +164,22 @@ See the [connection strings](../framework/fundamentals/connection-strings.md) do See the `TextTemplateManagementPermissions` class members for all permissions defined for this module. +The module exposes two edit-time permissions with different risk levels: + +| Permission | Required to edit | Default grant | +|------------|------------------|---------------| +| `TextTemplateManagement.TextTemplates.EditContents` | Templates rendered by a sandboxed engine (e.g. Scriban). Editing such templates is safe for content editors because the engine cannot execute arbitrary .NET code. | Granted to roles that need to edit template text. | +| `TextTemplateManagement.TextTemplates.EditNonSandboxedContents` | Templates rendered by a **non-sandboxed** engine (e.g. Razor). Editing such templates is functionally equivalent to granting server-side code execution because the engine compiles the content into a .NET assembly that runs with the host process's privileges. | **Not granted to any role by default**, including `admin`. Must be granted explicitly. | + +Whether a template is sandboxed is determined by `ITemplateRenderingEngine.IsSandboxed` on the engine that renders it. Editing a non-sandboxed template requires **both** `EditContents` and `EditNonSandboxedContents`. + +The Text Template Management UI surfaces this distinction: + +- A warning banner is rendered above the editor for non-sandboxed templates. +- The save and restore buttons are disabled when the current user lacks `EditNonSandboxedContents` for a non-sandboxed template. + +> Treat `EditNonSandboxedContents` as equivalent to granting shell access to the application server. Only assign it to fully trusted developers or operators. + ### Angular UI diff --git a/docs/en/others/free-licenses-vs-pro-licenses.md b/docs/en/others/free-licenses-vs-pro-licenses.md index 96f06eecb1..c6383a6d20 100644 --- a/docs/en/others/free-licenses-vs-pro-licenses.md +++ b/docs/en/others/free-licenses-vs-pro-licenses.md @@ -29,6 +29,8 @@ The following table explains the major differences between the open-source ABP a | [PRO application modules](https://abp.io/modules) | | | | [PRO UI themes](https://abp.io/themes) | | | | [ABP Suite](https://abp.io/tools/suite) | | | +| [React UI](../framework/ui/react/index.md) | | | +| [ABP Studio AI Agent](https://abp.io/studio/ai-agent) | | | | [Premium support](https://abp.io/support) | | | | | [Download](https://abp.io/get-started) | [Pricing](https://abp.io/pricing) | @@ -62,21 +64,22 @@ ABP has many **additional modules** compared to the open-source ABP project. Als | Account | [Basic](https://docs.abp.io/en/abp/latest/Modules/Account) | [PRO](https://abp.io/modules/Volo.Account.Pro) | | Multi-Tenancy | [Basic](https://docs.abp.io/en/abp/latest/Modules/Tenant-Management) *(only tenant management)* | [PRO](https://abp.io/modules/Volo.Saas) (SaaS) | | CMS Kit | [Basic](https://docs.abp.io/en/abp/latest/Modules/Cms-Kit) | [PRO](https://abp.io/modules/Volo.CmsKit.Pro) | -| [Blogging](https://docs.abp.io/en/abp/latest/Modules/Blogging) | | | -| [Docs](https://docs.abp.io/en/abp/latest/Modules/Docs) | | | -| [Identity Server Integration](https://docs.abp.io/en/abp/latest/Modules/IdentityServer) | | | +| [Blogging](https://abp.io/modules/blogging) | | | +| [Docs](../modules/docs.md) | | | +| [Identity Server Integration](../modules/identity-server.md) | | | | [Identity Server Management UI](https://abp.io/modules/Volo.Identityserver.Ui) | | | -| [OpenIddict Integration](https://docs.abp.io/en/abp/latest/Modules/OpenIddict) | | | +| [OpenIddict Integration](../modules/openiddict.md) | | | | [OpenIddictManagement UI](https://abp.io/modules/Volo.OpenIddict.Pro) | | | -| [Audit Log Reporting UI](https://abp.io/modules/Volo.AuditLogging.Ui) | | | -| [Dynamic Language Management](https://abp.io/modules/Volo.LanguageManagement) | | | -| [Payment](https://abp.io/modules/Volo.Payment) | | | -| [Text Template Management](https://abp.io/modules/Volo.TextTemplateManagement) | | | -| [Chat](https://abp.io/modules/Volo.Chat) | | | -| [File Management](https://abp.io/modules/Volo.FileManagement) | | | -| [Forms](https://abp.io/modules/Volo.Forms) | | | -| [Twilio SMS](https://abp.io/modules/Volo.Abp.Sms.Twilio) | | | -| [GDPR](https://docs.abp.io/en/commercial/latest/modules/gdpr) | | | +| [Audit Log Reporting UI](../modules/audit-logging-pro.md) | | | +| [Dynamic Language Management](../modules/language-management.md) | | | +| [Payment](../modules/payment.md) | | | +| [Text Template Management](../modules/text-template-management.md) | | | +| [Chat](../modules/chat.md) | | | +| [File Management](../modules/file-management.md) | | | +| [Forms](../modules/forms.md) | | | +| [Twilio SMS](../modules/twilio-sms.md) | | | +| [GDPR](../modules/gdpr.md) | | | +| [AI Management](../modules/ai-management/index.md) | | | Some modules have "Basic" (open-source) and "PRO" (commercial) versions. The next sections show the differences between the basic and the PRO versions. @@ -153,6 +156,23 @@ A screenshot from the CRUD Page Generator: ABP Suite is a part of the ABP Platform that generates full source code from the backend to the client. ABP Suite is not a free tool for everyone. It is free for only the active ABP license holders. +## React UI and ABP Studio AI Agent + +ABP 10.4 introduces two important license-holder capabilities that are designed for modern, AI-assisted application development: + +| Feature | Open Source ABP Project | ABP | +| ------- | ----------------------- | --- | +| [React UI](../framework/ui/react/index.md) | | | +| [ABP Studio AI Agent](https://abp.io/studio/ai-agent) | | | + +- **[React UI](https://abp.io/community/announcements/react-ui-for-abp-framework-is-finally-here-7rfmgb2v) is available with the modern template system for ABP license holders. You can create a React-based solution with ABP Studio's Modern Wizard or with `abp new --modern --ui-framework react`. A modern React solution contains your own React application under `react/` or `apps/react/`, while ABP's standard administration pages are provided by the [ABP Admin Console](../framework/ui/react/admin-console.md). The Admin Console is delivered by the `Volo.Abp.AdminConsole` NuGet package in layered and single-layer templates, and as a separate `apps/react-admin-console/` application in microservice templates.** + +![React UI & Admin Console](../images/react-ui-and-admin-console.png) + +- **[ABP Studio AI Agent](https://abp.io/community/announcements/introducing-abp-studio-ai-agent-o1ni0toc) is also available for ABP license holders. It works inside ABP Studio to understand ABP solution structure, plan changes, edit code, build projects, run migrations, diagnose runtime errors and keep changes Git-safe. AI Agent usage is based on AI credits: existing customers have predefined AI credits, while new customers receive new AI credits according to the current commercial offering. See the [ABP Studio AI Agent](https://abp.io/studio/ai-agent) page for the latest feature and credit details** + +![ABP Studio AI Agent](../images/abp-studio-ai-agent.png) + ## LeptonX Lite (Basic Theme) vs LeptonX (PRO Theme) ABP provides a theming system that has the following goals: diff --git a/docs/en/package-version-changes.md b/docs/en/package-version-changes.md index 5b4722e976..84a10fafe9 100644 --- a/docs/en/package-version-changes.md +++ b/docs/en/package-version-changes.md @@ -7,10 +7,91 @@ # Package Version Changes +## 10.4.0-rc.2 + +| Package | Old Version | New Version | PR | +|---------|-------------|-------------|-----| +| Magick.NET-Q16-AnyCPU | 14.9.1 | 14.13.0 | #25427 | +| MongoDB.Driver | 3.8.0 | 3.8.1 | #25404 | + +## 10.4.0-rc.1 + +| Package | Old Version | New Version | PR | +|---------|-------------|-------------|-----| +| Microsoft.AspNetCore.Authentication.JwtBearer | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.AspNetCore.Authentication.OpenIdConnect | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.AspNetCore.Authorization | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.AspNetCore.Components | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.AspNetCore.Components.Authorization | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.AspNetCore.Components.Web | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.AspNetCore.Components.WebAssembly | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.AspNetCore.Components.WebAssembly.Authentication | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.AspNetCore.Components.WebAssembly.DevServer | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.AspNetCore.Components.WebAssembly.Server | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.AspNetCore.Components.WebView.Maui | 10.0.20 | 10.0.51 | #25313 | +| Microsoft.AspNetCore.DataProtection.StackExchangeRedis | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.AspNetCore.Mvc.NewtonsoftJson | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.AspNetCore.Mvc.Testing | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.AspNetCore.TestHost | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.AspNetCore.WebUtilities | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Bcl.AsyncInterfaces | 10.0.4 | 10.0.7 | #25313 | +| Microsoft.Data.Sqlite | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.EntityFrameworkCore | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.EntityFrameworkCore.Design | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.EntityFrameworkCore.InMemory | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.EntityFrameworkCore.Proxies | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.EntityFrameworkCore.Relational | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.EntityFrameworkCore.SqlServer | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.EntityFrameworkCore.Sqlite | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.EntityFrameworkCore.Tools | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.Caching.Memory | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.Caching.StackExchangeRedis | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.Configuration.Binder | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.Configuration.CommandLine | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.Configuration.EnvironmentVariables | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.Configuration.UserSecrets | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.DependencyInjection | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.DependencyInjection.Abstractions | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.FileProviders.Composite | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.FileProviders.Embedded | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.FileProviders.Physical | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.FileSystemGlobbing | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.Hosting | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.Hosting.Abstractions | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.Http | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.Identity.Core | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.Localization | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.Logging | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.Logging.Abstractions | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.Logging.Console | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.Options | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Extensions.Options.ConfigurationExtensions | 10.0.2 | 10.0.7 | #25313 | +| Microsoft.Maui.Controls | 10.0.20 | 10.0.51 | #25313 | +| OpenIddict.Abstractions | 7.3.0 | 7.5.0 | #25306 | +| OpenIddict.Core | 7.3.0 | 7.5.0 | #25306 | +| OpenIddict.Server.AspNetCore | 7.3.0 | 7.5.0 | #25306 | +| OpenIddict.Validation.AspNetCore | 7.3.0 | 7.5.0 | #25306 | +| OpenIddict.Validation.ServerIntegration | 7.3.0 | 7.5.0 | #25306 | +| System.Collections.Immutable | 10.0.2 | 10.0.7 | #25313 | +| System.Management | 10.0.2 | 10.0.7 | #25313 | +| System.Security.Cryptography.Xml | 10.0.6 | 10.0.7 | #25313 | +| System.Security.Permissions | 10.0.2 | 10.0.7 | #25313 | +| System.Text.Encoding.CodePages | 10.0.2 | 10.0.7 | #25313 | +| System.Text.Encodings.Web | 10.0.2 | 10.0.7 | #25313 | +| System.Text.Json | 10.0.2 | 10.0.7 | #25313 | + +**Added:** + +| Package | Version | PR | +|---------|---------|-----| +| MudBlazor | 8.0.0 | #25235 | + ## 10.3.1 | Package | Old Version | New Version | PR | |---------|-------------|-------------|-----| +| MongoDB.Driver | 3.7.1 | 3.8.0 | #25302 | | System.Security.Cryptography.Xml | 10.0.2 | 10.0.6 | #25279 | ## 10.3.0-rc.1 diff --git a/docs/en/release-info/migration-guides/abp-10-0.md b/docs/en/release-info/migration-guides/abp-10-0.md index 334adb2f4f..f3afb68d46 100644 --- a/docs/en/release-info/migration-guides/abp-10-0.md +++ b/docs/en/release-info/migration-guides/abp-10-0.md @@ -133,4 +133,28 @@ See the [Add failure retry policy to InboxProcessor](https://github.com/abpframe Starting from **ABP 10.0**, the [`HideErrors`](../../framework/fundamentals/caching#Available-Options) option of `AbpDistributedCacheOptions` is **disabled by default in the development environment**. By default, ABP hides and logs cache server errors to keep the application running even when the cache is unavailable. -However, in the **development environment**, errors are no longer hidden so that developers can immediately detect and fix **any cache server issues** (such as connection, configuration, or runtime errors). +However, in the **development environment**, errors are no longer hidden so that developers can immediately detect and fix **any cache server issues** (such as connection, configuration, or runtime errors). + +### Angular `LOGO_APP_NAME_TOKEN` May Need Explicit Provider in Module-based Apps + +In ABP v10, the logo/app-name binding moved to the `provideLogo(withEnvironmentOptions(...))` API from `@abp/ng.theme.shared`. + +For standalone Angular applications, configuring `provideLogo(withEnvironmentOptions(environment))` in `app.config.ts` is enough. + +For NgModule-based applications, some setups may still show the literal `ProjectName` text (the default value of `LOGO_APP_NAME_TOKEN`) instead of `environment.application.name`. In that case, provide the token explicitly in your module providers: + +```ts +import { LOGO_APP_NAME_TOKEN } from '@abp/ng.theme.shared'; +import { environment } from '../environments/environment'; + +@NgModule({ + // ... + providers: [ + // ... + { provide: LOGO_APP_NAME_TOKEN, useValue: environment.application.name }, + ], +}) +export class AppModule {} +``` + +For logo customization/replacement details, see the [Component Replacement](../../framework/ui/angular/component-replacement.md#how-to-replace-logocomponent) documentation. diff --git a/docs/en/release-info/migration-guides/abp-10-1.md b/docs/en/release-info/migration-guides/abp-10-1.md index ad0db21a37..2c973c28bb 100644 --- a/docs/en/release-info/migration-guides/abp-10-1.md +++ b/docs/en/release-info/migration-guides/abp-10-1.md @@ -60,6 +60,20 @@ ABP now targets Angular v21 (up from v20). For existing Angular projects, apply providers: [provideZoneChangeDetection(), ...appConfig.providers], }).catch(err => console.error(err)); ``` + + If you are using a module-based structure instead of a standalone one, update the providers in `app.module.ts`: + ```ts + @NgModule({ + declarations: [AppComponent], + providers: [ + //... + provideZoneChangeDetection(), + ], + bootstrap: [AppComponent], + }) + export class AppModule {} + ``` + - **tsconfig.json:** Align with the new property formats to avoid build issues: ```json /* angular/tsconfig.json */ diff --git a/docs/en/release-info/migration-guides/abp-10-4.md b/docs/en/release-info/migration-guides/abp-10-4.md new file mode 100644 index 0000000000..c66f09c96a --- /dev/null +++ b/docs/en/release-info/migration-guides/abp-10-4.md @@ -0,0 +1,310 @@ +```json +//[doc-seo] +{ + "Description": "Upgrade your ABP solutions from v10.3 to v10.4 with this migration guide covering important behavior and integration changes." +} +``` + +# ABP Version 10.4 Migration Guide + +This document is a guide for upgrading ABP v10.3 solutions to ABP v10.4. There are no explicitly marked breaking changes in this release scope, but there are some important changes that may require action in specific application scenarios. + +> **Package Version Changes:** Before upgrading, review the [Package Version Changes](../../package-version-changes.md) document to see version changes on dependent NuGet packages and align your project with ABP's internal package versions. + +## Open-Source (Framework) + +This version contains the following changes on the open-source side: + +### URL-Based Localization + +**Who is affected** + +- Applications that enable the new URL-based localization option. +- Blazor applications with custom routable pages. +- Angular applications that need culture-prefixed URLs. + +**What changed** + +- ABP now supports embedding the culture in the URL path, such as `/en/products` or `/tr/identity/users`. +- The feature is opt-in and disabled by default. +- MVC and Razor Pages are handled automatically when enabled. +- ABP built-in Blazor module pages include culture-aware routes, but your own Blazor pages need manual route variants. +- Angular applications should wrap routes and culture-aware links with the new Angular helpers. + +**What to do** + +If you do not enable URL-based localization, no action is required. + +If you enable it, configure `AbpRequestLocalizationOptions`: + +```csharp +Configure(options => +{ + options.UseRouteBasedCulture = true; +}); +``` + +For custom Blazor pages, add culture route variants: + +```razor +@page "/Products" +@page "/{culture}/Products" +``` + +For Angular applications, wrap your route tree with `withOptionalRouteCulturePrefix` from `@abp/ng.core`, and use the route-culture URL helpers/pipes for menu links, breadcrumbs, and language switching. + +> See the [URL-Based Localization](../../framework/fundamentals/url-based-localization.md) document and [#25174](https://github.com/abpframework/abp/pull/25174) for details. + +### Localization JSON Files Can Be Split by Culture + +**Who is affected** + +- Applications/modules that want to split a large localization resource into multiple JSON files for the same culture. +- Applications that already have duplicate culture JSON files under the same localization resource path. + +**What changed** + +- ABP now allows multiple JSON files with the same `culture` value under the same localization resource. +- Files are merged in ordinal name order. +- If the same localization key exists in multiple files, the value from the last file wins. + +**What to do** + +No action is required for existing applications. + +If you split localization files, choose deterministic file names and avoid unintended duplicate keys: + +```text +Localization/ ++-- MyResource/ + +-- en.json + +-- en_Authors.json + +-- en_Books.json +``` + +> See [#25227](https://github.com/abpframework/abp/pull/25227) for details. + +### Email/SMS 2FA Codes Are Single-Use + +**Who is affected** + +- Applications using the Identity module's email or phone 2FA providers. +- Applications with custom flows around phone-number change tokens. +- Applications that replace or customize ASP.NET Core Identity token providers. + +**What changed** + +- ABP replaces the default email and phone 2FA providers with DataProtector-backed single-use providers. +- Generated codes are encrypted, persisted with an absolute expiration, and removed after successful validation. +- Generating a new code invalidates the previous one. +- `UserManager.GenerateChangePhoneNumberTokenAsync` / `VerifyChangePhoneNumberTokenAsync` also inherit the new stored-token semantics because ASP.NET Core Identity uses the default phone provider for phone-number changes. +- Phone-change tokens issued before upgrading may stop working after the upgrade. + +**What to do** + +- Re-test login, email 2FA, SMS 2FA, and phone-number change flows. +- If your application needs a different code lifetime or length, configure the new provider options: + +```csharp +Configure(options => +{ + options.TokenLifespan = TimeSpan.FromMinutes(5); + options.CodeLength = 8; +}); + +Configure(options => +{ + options.TokenLifespan = TimeSpan.FromMinutes(2); +}); +``` + +- If your application requires custom token behavior, replace the token provider by registering your own provider under `TokenOptions.DefaultEmailProvider` and/or `TokenOptions.DefaultPhoneProvider`. + +> See the [Two Factor Authentication](../../modules/identity/two-factor-authentication.md) document and [#25316](https://github.com/abpframework/abp/pull/25316) for details. + +### MudBlazor Support for Blazor UI + +**Who is affected** + +- Blazor applications that want to try the new MudBlazor-based ABP UI support. +- Module authors who build Blazor UI packages and want to support MudBlazor. + +**What changed** + +- ABP v10.4 introduces the initial MudBlazor integration packages, templates, bundling infrastructure, and module/theme support. +- Existing Blazorise-based applications are not automatically migrated to MudBlazor. + +**What to do** + +- No action is required for existing Blazorise-based applications. +- If you want to try MudBlazor support, create a new v10.4 RC solution/template or add the related MudBlazor packages according to the target UI type and test the UI carefully before production usage. + +> See [#25235](https://github.com/abpframework/abp/pull/25235) for details. + +### Text Template Rendering Engine — `IsSandboxed` Marker + +**Who is affected** + +- Custom rendering engines that implement `Volo.Abp.TextTemplating.ITemplateRenderingEngine` directly (not deriving from `TemplateRenderingEngineBase`). +- Modules and applications that surface template editing to non-developer users (e.g. the Text Template Management module). + +**What changed** + +- `ITemplateRenderingEngine` exposes a new required property: + + ```csharp + bool IsSandboxed { get; } + ``` + + Sandboxed engines (e.g. Scriban) interpret templates as a restricted DSL without .NET interop. Non-sandboxed engines (e.g. Razor) compile templates into fully-trusted .NET code that runs with the same privileges as the host process. +- `TemplateRenderingEngineBase` provides a virtual default of `false` (secure-by-default): engines that derive from the base class and don't override the property are treated as non-sandboxed. +- `RazorTemplateRenderingEngine` declares `IsSandboxed => false` (compiles to .NET assembly via Roslyn). +- `ScribanTemplateRenderingEngine` declares `IsSandboxed => true` and now sets Scriban's `TemplateContext.MemberFilter` so only public properties on imported objects are exposed; methods, fields, events and reflection entry points (`GetType`, `Assembly`, ...) are no longer reachable from Scriban templates. + +**What to do** + +- If your application registers a custom engine by implementing `ITemplateRenderingEngine` directly (without deriving from `TemplateRenderingEngineBase`), add the property: + + ```csharp + public bool IsSandboxed => false; // or true if your engine cannot execute host code + ``` + +- If your engine derives from `TemplateRenderingEngineBase`, no action is required for compilation; however, override `IsSandboxed => true` if your engine is genuinely sandboxed so callers (such as the Text Template Management module) treat its templates as safe to edit by non-developer users. +- Scriban templates that invoke methods on the model (e.g. `{%{{{ model.SomeMethod }}}%}`) or read fields stop working because the engine now whitelists public properties only. Templates that access only properties (the typical Scriban usage) are unaffected. To restore the previous behavior in custom hosts, derive from `ScribanTemplateRenderingEngine` and override `IsMemberAllowed` to allow methods or fields: + + ```csharp + protected override bool IsMemberAllowed(MemberInfo member) => true; + ``` + + Only do this when the model objects are trusted and do not carry secrets, since the previous behavior exposed reflection entry points (`GetType`, `Assembly`, ...) on imported .NET objects. + +> See [#25399](https://github.com/abpframework/abp/pull/25399) for details. + +### Text Template Management — `EditContents` Permission Split (security) + +**Who is affected** + +- Applications using the Text Template Management module that have granted the `TextTemplateManagement.TextTemplates.EditContents` permission to roles that are not fully trusted server administrators/developers. +- In particular, applications that use Razor templates (default for solutions referencing `Volo.Abp.TextTemplating.Razor`) and have any non-developer role with `EditContents`. + +**What changed** + +- A new permission `TextTemplateManagement.TextTemplates.EditNonSandboxedContents` has been added. +- Editing a template whose rendering engine is **non-sandboxed** (i.e. `ITemplateRenderingEngine.IsSandboxed == false`, e.g. Razor) now requires **both** permissions: `EditContents` and `EditNonSandboxedContents`. +- The new permission is **not granted to any role by default**, including the `admin` role. The previously implicit assumption — that `EditContents` was enough to edit Razor templates — has been corrected: editing such templates is functionally equivalent to granting server-side code execution and is now gated by an explicit permission whose name communicates that risk. +- The Text Template Management UI (MVC, Blazor) renders a security warning banner when the current template's engine is non-sandboxed, and disables the save/restore buttons when the current user lacks the new permission. + +**What to do** + +After upgrading, audit which roles currently hold `EditContents` and decide which of them should also be granted `EditNonSandboxedContents`: + +1. Roles that should only edit sandboxed templates (e.g. Scriban, Liquid, plain HTML) need no further action — they keep editing those templates. +2. Roles that need to continue editing Razor templates must be granted `EditNonSandboxedContents` explicitly via the Permission Management page. +3. If — and only if — you have reviewed your role assignments and confirmed that every role currently holding `EditContents` is trusted to execute server-side code through Razor templates, you may seed the new permission for those roles via your `IDataSeedContributor`: + + ```csharp + var grants = await _permissionGrantRepository.GetListAsync( + TextTemplateManagementPermissions.TextTemplates.EditContents); + + foreach (var grant in grants) + { + await _permissionManager.SetAsync( + TextTemplateManagementPermissions.TextTemplates.EditNonSandboxedContents, + grant.ProviderName, + grant.ProviderKey, + isGranted: true); + } + ``` + +> Do not automate this seeding for arbitrary tenants/roles without first reviewing the current grants — auto-restoring the previously-implicit elevated trust would defeat the security improvement. The recommended path is to grant the new permission only to specific developer/operator roles via the Permission Management UI. + +If your application's data seeder grants all permissions in the `TextTemplateManagement` group to a role (e.g. the `admin` role), that role will automatically receive `EditNonSandboxedContents` on first run after upgrade. Audit your seeder if you want stricter defaults. + +> See the [Razor Integration](../../framework/infrastructure/text-templating/razor.md) document and [#25399](https://github.com/abpframework/abp/pull/25399) for details. + +### Dependency Updates + +**Who is affected** + +- Applications that pin ABP transitive dependencies directly. +- Applications that use OpenIddict, MongoDB, or Microsoft/System packages directly with fixed versions. + +**What changed** + +- OpenIddict was upgraded to **7.5.0**. +- MongoDB.Driver was upgraded to **3.8.0**. +- Several Microsoft/System packages were upgraded to **10.0.7** for CVE-2026-40372. +- `System.Security.Cryptography.Xml` was upgraded to **10.0.6**. + +**What to do** + +- Review your direct package references and align them with ABP's package versions where needed. +- Rebuild and run integration tests if you directly use upgraded packages. + +## Pro + +There are no explicitly marked breaking changes on the PRO side in this release scope. However, check the following if they apply to your application. + +### Account Pro Passwordless Email Login + +**Who is affected** + +- Applications using the Account Pro module that want to enable passwordless email login. + +**What changed** + +- Account Pro now supports email login by one-time link, one-time password (OTP), or both. +- The feature is disabled unless configured. +- Tokens are single-use, expire according to the configured lifetime, and respect tenant-scoped settings. + +**What to do** + +- No action is required if you do not enable passwordless email login. +- If you enable it, review account settings, email templates, token lifetime, rate limiting, and `PreventEmailEnumeration` behavior. +- Re-test login flows for host and tenant users. + +### QA Module AI Suggest + +**Who is affected** + +- Applications using the QA module and enabling AI-assisted answer suggestions. + +**What changed** + +- Authorized users can generate AI-suggested answers based on question context and existing answers. +- The feature is controlled by settings and permissions. + +**What to do** + +- No action is required unless you enable the feature. +- If enabled, configure the QA AI Suggest settings, prompt, character limits, and permissions. + +### AI Management MCP Server Enhancements + +**Who is affected** + +- Applications using the AI Management module's MCP server integration. + +**What changed** + +- MCP server configuration was enhanced for `stdio` transport scenarios and workspace relationships. + +**What to do** + +- Re-test MCP server connection, tool listing, and workspace chat flows after upgrading. + +### LeptonX URL-Based Localization Support + +**Who is affected** + +- Applications using LeptonX with URL-based localization. + +**What changed** + +- LeptonX has been updated for culture-aware navigation and language switching, including Angular support. +- Several menu, select, sidebar, and PathBase-related fixes were included. + +**What to do** + +- If you enable URL-based localization, re-test language switching, menu links, breadcrumbs, and PathBase deployments for your UI type. + diff --git a/docs/en/release-info/migration-guides/index.md b/docs/en/release-info/migration-guides/index.md index 580640bdb7..c64250ccc1 100644 --- a/docs/en/release-info/migration-guides/index.md +++ b/docs/en/release-info/migration-guides/index.md @@ -9,6 +9,7 @@ The following documents explain how to migrate your existing ABP applications. We write migration documents only if you need to take an action while upgrading your solution. Otherwise, you can easily upgrade your solution using the [abp update command](../upgrading.md). +- [10.3 to 10.4](abp-10-4.md) - [10.x to 10.3](abp-10-3.md) - [10.x to 10.2](abp-10-2.md) - [10.0 to 10.1](abp-10-1.md) @@ -34,3 +35,4 @@ The following documents explain how to migrate your existing ABP applications. W - [4.x to 4.2](abp-4-2.md) - [3.3.x to 4.0](abp-4-0.md) - [2.9.x to 3.0](../../framework/ui/angular/migration-guide-v3.md) + diff --git a/docs/en/release-info/release-notes.md b/docs/en/release-info/release-notes.md index d98a17e2a8..3802fe21e3 100644 --- a/docs/en/release-info/release-notes.md +++ b/docs/en/release-info/release-notes.md @@ -11,53 +11,66 @@ This document contains **brief release notes** for each release. Release notes o Also see the following notes about ABP releases: -* [ABP Studio release notes](../studio/release-notes.md) -* [Change logs for ABP pro packages](https://abp.io/pro-releases) +- [ABP Studio release notes](../studio/release-notes.md) +- [Change logs for ABP pro packages](https://abp.io/pro-releases) -## 10.3 (2026-04-01) +## 10.4 (2026-04-29) -> This is currently an RC (release-candidate) and you can see the detailed **[blog post / announcement](https://abp.io/community/announcements/announcing-abp-10-3-release-candidate-hgnpr9jq)** for the v10.3 release. +ABP v10.4 is currently in the release candidate stage; the stable version has not been released yet. See the detailed **[blog post / announcement](https://abp.io/community/announcements/announcing-abp-10-4-release-candidate-7ukyudm0)** for the v10.4 RC release. -* OpenIddict: `private_key_jwt` Client Authentication + `abp generate-jwks` -* Event Bus: String-Based Event Publishing with Dynamic Payload -* Background Jobs/Workers: String-Based Publishing with Dynamic Payload -* API Definition Endpoint: Descriptions and Documentation Support -* Entity Cache: New Batch APIs (`FindMany*` / `GetMany*`) -* Angular: User/Tenant Sharing and Tenant Switch Experience -* Angular: Upgrade to 21.2 + TypeScript 5.9 -* Introducing the `Volo.Abp.LuckyPenny.AutoMapper` Provider -* Security Improvements (Account Pro Module) +- URL-Based Localization +- Localization File Splitting +- Blazor UI: MudBlazor Support +- Identity: Single-Use Email/SMS 2FA Token Providers +- Account Pro: Passwordless Email Login +- AI Management: MCP Server Enhancements +- LeptonX: URL-Based Localization and Theme Improvements +- OpenIddict 7.5.0, MongoDB.Driver 3.8.0, and security-focused dependency updates + +## 10.3 (2026-04-15) + +See the detailed **[blog post / announcement](https://abp.io/community/announcements/announcing-abp-10-3-release-candidate-hgnpr9jq)** for the v10.3 release. + +- OpenIddict: `private_key_jwt` Client Authentication + `abp generate-jwks` +- Event Bus: String-Based Event Publishing with Dynamic Payload +- Background Jobs/Workers: String-Based Publishing with Dynamic Payload +- API Definition Endpoint: Descriptions and Documentation Support +- Entity Cache: New Batch APIs (`FindMany`* / `GetMany*`) +- Angular: User/Tenant Sharing and Tenant Switch Experience +- Angular: Upgrade to 21.2 + TypeScript 5.9 +- Introducing the `Volo.Abp.LuckyPenny.AutoMapper` Provider +- Security Improvements (Account Pro Module) ## 10.2 (2026-02-24) See the detailed **[blog post / announcement](https://abp.io/community/announcements/announcing-abp-10-2-stable-release-x47ytfww)** for the v10.2 release. -* Multi-Tenant Account Usage: Shared User Accounts -* Prevent Privilege Escalation: Assignment Restrictions for Roles and Permissions -* `ClientResourcePermissionValueProvider` for OAuth/OpenIddict -* Angular: Hybrid Localization Support -* Angular: Extensible Table Row Detail -* Angular: CMS Kit Module Features -* Blazor: Upgrade to Blazorise 2.0 -* Identity: Single Active Token Providers -* TickerQ Package Upgrade to 10.1.1 -* [AI Management Module](../modules/ai-management/index.md): MCP (Model Context Protocol) Support -* [AI Management Module](../modules/ai-management/index.md): RAG with File Upload -* [AI Management Module](../modules/ai-management/index.md): OpenAI-Compatible Chat Endpoint -* [File Management Module](../modules/file-management.md): Resource-Based Authorization +- Multi-Tenant Account Usage: Shared User Accounts +- Prevent Privilege Escalation: Assignment Restrictions for Roles and Permissions +- `ClientResourcePermissionValueProvider` for OAuth/OpenIddict +- Angular: Hybrid Localization Support +- Angular: Extensible Table Row Detail +- Angular: CMS Kit Module Features +- Blazor: Upgrade to Blazorise 2.0 +- Identity: Single Active Token Providers +- TickerQ Package Upgrade to 10.1.1 +- [AI Management Module](../modules/ai-management/index.md): MCP (Model Context Protocol) Support +- [AI Management Module](../modules/ai-management/index.md): RAG with File Upload +- [AI Management Module](../modules/ai-management/index.md): OpenAI-Compatible Chat Endpoint +- [File Management Module](../modules/file-management.md): Resource-Based Authorization ## 10.1 (2026-01-06) See the detailed **[blog post / announcement](https://abp.io/community/announcements/announcing-abp-10-1-stable-release-z4xfn1me)** for the v10.1 release. -* Resource-Based Authorization -* Introducing the [TickerQ Background Worker Provider](../framework/infrastructure/background-workers/tickerq.md) -* Angular UI: Version Upgrade to **v21** -* [File Management Module](../modules/file-management.md): Public File Sharing Support -* [Payment Module](../modules/payment.md): Public Page Implementation for Blazor & Angular UIs -* [AI Management Module](../modules/ai-management/index.md) for Blazor & Angular UIs -* [Identity PRO Module](../modules/identity-pro.md): Password History Support -* [Account PRO Module](../modules/account-pro.md): Introducing WebAuthn Passkeys +- Resource-Based Authorization +- Introducing the [TickerQ Background Worker Provider](../framework/infrastructure/background-workers/tickerq.md) +- Angular UI: Version Upgrade to **v21** +- [File Management Module](../modules/file-management.md): Public File Sharing Support +- [Payment Module](../modules/payment.md): Public Page Implementation for Blazor & Angular UIs +- [AI Management Module](../modules/ai-management/index.md) for Blazor & Angular UIs +- [Identity PRO Module](../modules/identity-pro.md): Password History Support +- [Account PRO Module](../modules/account-pro.md): Introducing WebAuthn Passkeys ## 10.0 (2025-11-18) @@ -65,45 +78,45 @@ See the detailed **[blog post / announcement](https://abp.io/community/announcem See the detailed **[blog post / announcement](https://abp.io/community/announcements/abp.io-platform-10.0-final-has-been-released-spknn925)** for the v10.0 release. -* Upgraded to .NET 10.0 -* Upgraded to `Blazorise` **v1.8.6** -* New PRO Module: [Elsa Workflows](../modules/elsa-pro.md) -* New Object Mapper: **Mapperly** -* Localization: Nested Object Support in JSON Files -* EF Core Shared Entity Types on Repositories -* Angular SSR Support +- Upgraded to .NET 10.0 +- Upgraded to `Blazorise` **v1.8.6** +- New PRO Module: [Elsa Workflows](../modules/elsa-pro.md) +- New Object Mapper: **Mapperly** +- Localization: Nested Object Support in JSON Files +- EF Core Shared Entity Types on Repositories +- Angular SSR Support ## 9.3 (2025-06-17) See the detailed **[blog post / announcement](https://abp.io/community/announcements/announcing-abp-9-3-stable-release-fw4n9sng)** for the v9.3 release. -* Cron Expression Support for Background Workers -* Docs Module: PDF Export -* Angular UI: Standalone Package Structure -* Upgraded to `Blazorise` **v1.7.7** -* Audit Logging Module: Excel Export -* Angular UI: Version Upgrade to **v20** +- Cron Expression Support for Background Workers +- Docs Module: PDF Export +- Angular UI: Standalone Package Structure +- Upgraded to `Blazorise` **v1.7.7** +- Audit Logging Module: Excel Export +- Angular UI: Version Upgrade to **v20** ## 9.2 (2025-06-02) See the detailed **[blog post / announcement](https://abp.io/community/articles/announcing-abp-9-2-stable-release-061qmtzb)** for the v9.2 release. -* Added `ApplicationName` Property to Isolate Background Jobs & Background Workers -* Docs Module: Added "Alternative Words" to Filter Items -* Introducing the [Bunny BLOB Storage Provider](../framework/infrastructure/blob-storing/bunny.md) -* Upgraded `MongoDB.Driver` to **v3.1.0** -* Identity Pro Module: Require Email Verification to Register -* Switching users during OAuth login +- Added `ApplicationName` Property to Isolate Background Jobs & Background Workers +- Docs Module: Added "Alternative Words" to Filter Items +- Introducing the [Bunny BLOB Storage Provider](../framework/infrastructure/blob-storing/bunny.md) +- Upgraded `MongoDB.Driver` to **v3.1.0** +- Identity Pro Module: Require Email Verification to Register +- Switching users during OAuth login ## 9.1 (2025-03-05) See the detailed **[blog post / announcement](https://abp.io/community/articles/abp.io-platform-9.1-final-has-been-released-h96a56qa)** for the v9.1 release. -* Upgraded to Angular 19 -* Upgraded to OpenIddict 6.0 -* New Blazor WASM Bundling System -* Idle Session Warning -* Lazy Expandable Feature for Documentation System +- Upgraded to Angular 19 +- Upgraded to OpenIddict 6.0 +- New Blazor WASM Bundling System +- Idle Session Warning +- Lazy Expandable Feature for Documentation System ## 9.0 (2024-11-19) @@ -111,341 +124,342 @@ See the detailed **[blog post / announcement](https://abp.io/blog/abp-9-0-stable > **Note**: ABP has upgraded to .NET 9.0, so if you plan to use ABP 9.0, you’ll need to migrate your solutions to .NET 9.0. You can refer to the [Migrate from ASP.NET Core 8.0 to 9.0](https://learn.microsoft.com/en-us/aspnet/core/migration/80-90) documentation for guidance. However, ABP’s NuGet packages are compatible with both .NET 8 and .NET 9, allowing developers to continue using .NET 8 while still enjoying the latest features and improvements of the ABP Framework without upgrading their SDK. -* Upgraded to .NET 9.0 -* Introducing the `Extension Property Policy` -* Google Cloud Storage BLOB Provider -* Removed React Native mobile option from free templates -* ABP Suite: Better naming for multiple navigation properties to the same entity -* CMS Kit Pro: Feedback feature improvements +- Upgraded to .NET 9.0 +- Introducing the `Extension Property Policy` +- Google Cloud Storage BLOB Provider +- Removed React Native mobile option from free templates +- ABP Suite: Better naming for multiple navigation properties to the same entity +- CMS Kit Pro: Feedback feature improvements ## 8.3 (2024-09-05) See the detailed **[blog post / announcement](https://abp.io/blog/announcing-abp-8-3-stable-release)** for the v8.3 release. -* CMS Kit: Marked Items & Approvement System for Commenting Feature -* Enhancements on the Docs Module (Google Translation support & new single project mode) -* Using DbFunction for generating more precise SQL commands for Global Query Filters -* CMS Kit (Pro): FAQ System +- CMS Kit: Marked Items & Approvement System for Commenting Feature +- Enhancements on the Docs Module (Google Translation support & new single project mode) +- Using DbFunction for generating more precise SQL commands for Global Query Filters +- CMS Kit (Pro): FAQ System ## 8.2 (2024-06-26) See the detailed **[blog post / announcement](https://abp.io/blog/announcing-abp-8-2-stable-release)** for the v8.2 release. -* Blazor Full-Stack Web App UI. -* Introducing the `IBlockUiService` for Blazor UI (disables/blocks the page or a part of the page). -* Session Management (prevent concurrent login, view & manage users' sessions). -* ABP Suite: File/Image property. -* ABP Suite: `DateOnly` & `TimeOnly` types. -* Periodic Log Deletion for Audit Logs. +- Blazor Full-Stack Web App UI. +- Introducing the `IBlockUiService` for Blazor UI (disables/blocks the page or a part of the page). +- Session Management (prevent concurrent login, view & manage users' sessions). +- ABP Suite: File/Image property. +- ABP Suite: `DateOnly` & `TimeOnly` types. +- Periodic Log Deletion for Audit Logs. ## 8.1 (2024-04-04) See the detailed **[blog post / announcement](https://abp.io/blog/announcing-abp-8-1-stable-release)** for the v8.1 release. -* Introducing the `ExposeKeyedServiceAttribute` & `DisableAbpFeaturesAttribute`. -* Custom menu component support for MVC UI. -* ABP Suite: Bulk delete. -* ABP Suite: Filterable properties. -* ABP Suite: Customizable page titles. -* ABP Suite: Establishing relationships with installed ABP modules' entities. -* ABP Suite: Support `BasicAggregateRoot` base class. -* ABP Studio v0.6.5. +- Introducing the `ExposeKeyedServiceAttribute` & `DisableAbpFeaturesAttribute`. +- Custom menu component support for MVC UI. +- ABP Suite: Bulk delete. +- ABP Suite: Filterable properties. +- ABP Suite: Customizable page titles. +- ABP Suite: Establishing relationships with installed ABP modules' entities. +- ABP Suite: Support `BasicAggregateRoot` base class. +- ABP Studio v0.6.5. ## 8.0 (2023-12-19) See the detailed **[blog post / announcement](https://abp.io/blog/abp-8-0-stable-release-with-dotnet-8-0)** for the v8.0 release. -* Upgraded to **.NET 8.0** & **Angular 17**. -* Dynamic Claims (allows to get the latest user claims for the current user). -* CDN support for Bundling & Minification System. -* Read-only repositories -* ABP Suite: Generating Master/Detail Relationship -* Getting profile picture from social/external logins. -* Switch Ocelot to YARP for the API Gateway for Microservice Solution Template. -* Password complexity indicators for MVC & Blazor UIs. -* Readonly view & export/import support for Identity/Users page. +- Upgraded to **.NET 8.0** & **Angular 17**. +- Dynamic Claims (allows to get the latest user claims for the current user). +- CDN support for Bundling & Minification System. +- Read-only repositories +- ABP Suite: Generating Master/Detail Relationship +- Getting profile picture from social/external logins. +- Switch Ocelot to YARP for the API Gateway for Microservice Solution Template. +- Password complexity indicators for MVC & Blazor UIs. +- Readonly view & export/import support for Identity/Users page. ## 7.4 (2023-08-16) See the detailed **[blog post / announcement](https://abp.io/blog/announcing-abp-7-4-stable-release)** for the v7.4 release. -* Dynamic Setting Store (collects and gets all setting definitions from a single point). -* Introducing the `AdditionalAssemblyAttribute`. -* `CorrelationId` support on distributed events. -* Database migration system for EF Core. -* Preserving customizations on code re-generation with ABP Suite. -* Support custom text-templates in distributed scenarios. -* MAUI & React Native mobile applications are re-designed and revised for functionality. -* A new CMS Kit feature to collect feedback from users about the site's contents. +- Dynamic Setting Store (collects and gets all setting definitions from a single point). +- Introducing the `AdditionalAssemblyAttribute`. +- `CorrelationId` support on distributed events. +- Database migration system for EF Core. +- Preserving customizations on code re-generation with ABP Suite. +- Support custom text-templates in distributed scenarios. +- MAUI & React Native mobile applications are re-designed and revised for functionality. +- A new CMS Kit feature to collect feedback from users about the site's contents. ## 7.3 (2023-06-12) See the detailed **[blog post / announcement](https://abp.io/blog/ABP.IO-Platform-7-3-Final-Has-Been-Released)** for the v7.3 release. -* Introducing the `Volo.Abp.Imaging` packages, which allows you to compress and resize images. -* ABP CLI: `switch-to-local` command. -* Monitoring distributed events. -* Allow ordering of the local event handlers. -* [Account Module](../modules/account.md): Using Authenticator App for Two-Factor Authentication. -* Support for the [Module Entity Extensions](../framework/architecture/modularity/extending/module-entity-extensions.md) in the [CMS Kit Pro Module](../modules/cms-kit/index.md). -* New Account Layout Design for [LeptonX Theme](../ui-themes/lepton-x/index.md). -* Many enhancements and fixes for the 7.3 version. +- Introducing the `Volo.Abp.Imaging` packages, which allows you to compress and resize images. +- ABP CLI: `switch-to-local` command. +- Monitoring distributed events. +- Allow ordering of the local event handlers. +- [Account Module](../modules/account.md): Using Authenticator App for Two-Factor Authentication. +- Support for the [Module Entity Extensions](../framework/architecture/modularity/extending/module-entity-extensions.md) in the [CMS Kit Pro Module](../modules/cms-kit/index.md). +- New Account Layout Design for [LeptonX Theme](../ui-themes/lepton-x/index.md). +- Many enhancements and fixes for the 7.3 version. ## 7.2 (2023-05-03) See the detailed **[blog post / announcement](https://abp.io/blog/ABP.IO-Platform-7-2-Final-Has-Been-Released)** for the v7.2 release. -* Grouping of the navigation menu items. -* New Components for Angular UI. -* **[LeptonX Theme](../ui-themes/lepton-x)** - Navigation Menu Item Grouping. -* Support for the **Authority Delegation** in the **[Account Module](../modules/account.md)**. -* Forcing Password Change at Next Logon. -* Periodic Password Changes / Password Aging. -* ABP Suite: Show/Hide Properties on Create/Update/List Pages -* **CMS Kit Comments**: Disallowing External URLs. +- Grouping of the navigation menu items. +- New Components for Angular UI. +- **[LeptonX Theme](../ui-themes/lepton-x)** - Navigation Menu Item Grouping. +- Support for the **Authority Delegation** in the **[Account Module](../modules/account.md)**. +- Forcing Password Change at Next Logon. +- Periodic Password Changes / Password Aging. +- ABP Suite: Show/Hide Properties on Create/Update/List Pages +- **CMS Kit Comments**: Disallowing External URLs. ## 7.1 (2023-03-22) See the detailed **[blog post / announcement](https://abp.io/blog/ABP-IO-Platform-7-1-Final-Has-Been-Released)** for the v7.1 release. -* New Blazor WASM option for Application Single Layer Startup template. -* Introducing the `IHasEntityVersion` & `EntitySynchronizer` services. -* Introducing the `DeleteDirectAsync` method for the `IRepository` interface. -* **Blazor WebAssembly** option for the single-layer startup template. -* **ABP Suite** code generation for **MAUI Blazor Hybrid** solutions. -* Allow to **impersonate** an arbitrary **user** in the SaaS module. -* Many enhancements and fixes for the 7.1 version. +- New Blazor WASM option for Application Single Layer Startup template. +- Introducing the `IHasEntityVersion` & `EntitySynchronizer` services. +- Introducing the `DeleteDirectAsync` method for the `IRepository` interface. +- **Blazor WebAssembly** option for the single-layer startup template. +- **ABP Suite** code generation for **MAUI Blazor Hybrid** solutions. +- Allow to **impersonate** an arbitrary **user** in the SaaS module. +- Many enhancements and fixes for the 7.1 version. ## 7.0 (2023-01-05) See the detailed **[blog post / announcement](https://abp.io/blog/ABP.IO-Platform-7.0-Final-Has-Been-Released)** for the v7.0 release. -* Upgraded to **.NET 7.0**. -* Upgraded to **OpenIddict 4.0**. -* **Dapr** Integration. -* Introducing the **Integration Services**. -* New **MAUI Blazor Hybrid** UI. -* Implemented **external localization**, **dynamic feature** and **dynamic permission** systems to allow more advanced microservice scenarios. All they are applied to the **microservice startup template**. -* **WeChat** and **Alipay** integrations for the **Payment** module. -* Allow host users to **change the password** of a user of a tenant. -* Allow host users to **test connection string** of a tenant database on the UI. -* Introduce **permission** for **searching other users** in the chat module. +- Upgraded to **.NET 7.0**. +- Upgraded to **OpenIddict 4.0**. +- **Dapr** Integration. +- Introducing the **Integration Services**. +- New **MAUI Blazor Hybrid** UI. +- Implemented **external localization**, **dynamic feature** and **dynamic permission** systems to allow more advanced microservice scenarios. All they are applied to the **microservice startup template**. +- **WeChat** and **Alipay** integrations for the **Payment** module. +- Allow host users to **change the password** of a user of a tenant. +- Allow host users to **test connection string** of a tenant database on the UI. +- Introduce **permission** for **searching other users** in the chat module. ## 6.0 (2022-10-05) See the detailed **[blog post / announcement](https://abp.io/blog/ABP.IO-Platform-6.0-Final-Has-Been-Released)** for the v6.0 release. -* New **OpenIddict** integration module (replacing the IdentityServer integration module). -* The **[LeptonX theme](https://x.leptontheme.com/)** is the default theme now, allowing to use the [old Lepton](https://leptontheme.com/) theme too. -* New **.NET MAUI mobile application**. -* **Blazor UI** for the **Chat** module. -* **Blazor admin UI** for the **CMS Kit** module. -* Allow to add **poll widgets** in blog/page contents in the **CMS Kit** module. -* **Cookie consent** feature for the **GDPR** module. -* Optional **PWA** support. -* Exporting to **excel** for **ABP Suite** code generation. +- New **OpenIddict** integration module (replacing the IdentityServer integration module). +- The **[LeptonX theme](https://x.leptontheme.com/)** is the default theme now, allowing to use the [old Lepton](https://leptontheme.com/) theme too. +- New **.NET MAUI mobile application**. +- **Blazor UI** for the **Chat** module. +- **Blazor admin UI** for the **CMS Kit** module. +- Allow to add **poll widgets** in blog/page contents in the **CMS Kit** module. +- **Cookie consent** feature for the **GDPR** module. +- Optional **PWA** support. +- Exporting to **excel** for **ABP Suite** code generation. ## 5.3 (2022-06-14) See the detailed **[blog post / announcement](https://abp.io/blog/ABP.IO-Platform-5.3-Final-Has-Been-Released)** for the v5.3 release. -* New module: **GDPR** (currently, allows to download/delete user's personal data). -* **Polling** feature for the [CMS Kit module](../modules/cms-kit/index.md). -* OAuth as **external login provider** for the [Identity module](../modules/identity.md). -* **ABP Suite**: Support for the no-layers startup template, concurrency stamp support on code generation, downloading Suite logs, using ABP CLI to trigger code generation. -* **Docker-compose** configuration for the no-layers startup template. -* **PWA** support for Blazor WASM and Angular UI. +- New module: **GDPR** (currently, allows to download/delete user's personal data). +- **Polling** feature for the [CMS Kit module](../modules/cms-kit/index.md). +- OAuth as **external login provider** for the [Identity module](../modules/identity.md). +- **ABP Suite**: Support for the no-layers startup template, concurrency stamp support on code generation, downloading Suite logs, using ABP CLI to trigger code generation. +- **Docker-compose** configuration for the no-layers startup template. +- **PWA** support for Blazor WASM and Angular UI. ## 5.2 (2022-04-05) See the detailed **[blog post / announcement](https://abp.io/blog/ABP-IO-Platform-5-2-Final-Has-Been-Released)** for the v5.2 release. -* Code generation with **many to many relation** support for the [ABP Suite](../suite/index.md). -* The new **single-layer**, simpler startup solution template. -* Migrated to **Blazorise 1.0** for the Blazor UI. -* Improvements on the microservice startup solution, pre-built application modules and other existing features. -* API Versioning. -* Allowing to hide default ABP endpoints from the Swagger UI. +- Code generation with **many to many relation** support for the [ABP Suite](../suite/index.md). +- The new **single-layer**, simpler startup solution template. +- Migrated to **Blazorise 1.0** for the Blazor UI. +- Improvements on the microservice startup solution, pre-built application modules and other existing features. +- API Versioning. +- Allowing to hide default ABP endpoints from the Swagger UI. ## 5.1 (2022-01-12) See the detailed **[blog post / announcement](https://abp.io/blog/ABP.IO-Platform-v5-1-Has-Been-Released)** for the v5.1 release. -* Upgraded to **Angular 13**. -* Changed the application startup solution to use the new ASP.NET Core **minimal hosting model**. -* New **URL Forwarding** feature for the CKS Kit Pro module. -* Improvements and fixes for the features shipped with the 5.0 release. +- Upgraded to **Angular 13**. +- Changed the application startup solution to use the new ASP.NET Core **minimal hosting model**. +- New **URL Forwarding** feature for the CKS Kit Pro module. +- Improvements and fixes for the features shipped with the 5.0 release. ## 5.0 (2021-12-14) See the detailed **[blog post / announcement](https://abp.io/blog/ABP-IO-Platform-5.0-RC-1-Has-Been-Released)** for the v5.0. -* Upgraded to **.NET 6.0**. -* Upgraded to **Bootstrap 5.1**. -* Transactional Outbox & Inbox for the distributed event bus. -* **User impersonation** (passwordless login with another user's account). -* **Tenant impersonation** (passwordless login as a tenant). -* Added **Helm charts** to the microservice startup template to deploy to **Kubernetes**. -* Added host and tenant **dashboards** to the microservice startup template. -* **Generate entities** and CRUD pages from **database tables** with ABP Suite. -* Pre-configured **social logins** for the microservice startup template. -* Switched to **static C# and JavaScript proxies** for all the modules. -* **Removed NGXS** and states from the Angular UI. -* Many improvements on existing modules and ABP Suite. +- Upgraded to **.NET 6.0**. +- Upgraded to **Bootstrap 5.1**. +- Transactional Outbox & Inbox for the distributed event bus. +- **User impersonation** (passwordless login with another user's account). +- **Tenant impersonation** (passwordless login as a tenant). +- Added **Helm charts** to the microservice startup template to deploy to **Kubernetes**. +- Added host and tenant **dashboards** to the microservice startup template. +- **Generate entities** and CRUD pages from **database tables** with ABP Suite. +- Pre-configured **social logins** for the microservice startup template. +- Switched to **static C# and JavaScript proxies** for all the modules. +- **Removed NGXS** and states from the Angular UI. +- Many improvements on existing modules and ABP Suite. ## 4.4 (2021-08-02) See the detailed **[blog post / announcement](https://abp.io/blog/ABP.IO-Platform-4.4-Final-Has-Been-Released!)** for the v4.4. -* Removed the `EntityFrameworkCore.DbMigrations` project for simplicity. -* Dynamic menu management for the [CMS Kit module](../modules/cms-kit/index.md). -* New ABP CLI commands (`install-libs`, `prompt` and `batch`). -* **Subscription** system & **payment** integration for the [SaaS module](../modules/saas.md). -* SaaS module: Allow to make a **tenant active/passive** and **limit user count**. -* [ABP Suite](../suite/index.md) **code generation** for the [microservice solution](../solution-templates/microservice/index.md). -* Allow to set **multiple connection strings** for each tenant, to separate a tenant's database per module/microservice. -* Angular UI: **Two-factor** authentication for resource owner password flow. -* **New localizations**: Hindi, Italian, Arabic, Finnish, French. -* A lot of small improvements and fixes for the current modules, themes and the tooling. +- Removed the `EntityFrameworkCore.DbMigrations` project for simplicity. +- Dynamic menu management for the [CMS Kit module](../modules/cms-kit/index.md). +- New ABP CLI commands (`install-libs`, `prompt` and `batch`). +- **Subscription** system & **payment** integration for the [SaaS module](../modules/saas.md). +- SaaS module: Allow to make a **tenant active/passive** and **limit user count**. +- [ABP Suite](../suite/index.md) **code generation** for the [microservice solution](../solution-templates/microservice/index.md). +- Allow to set **multiple connection strings** for each tenant, to separate a tenant's database per module/microservice. +- Angular UI: **Two-factor** authentication for resource owner password flow. +- **New localizations**: Hindi, Italian, Arabic, Finnish, French. +- A lot of small improvements and fixes for the current modules, themes and the tooling. ## 4.3 (2021-04-23) See the detailed **[blog post / announcement](https://abp.io/blog/ABP.IO-Platform-v4-3-Has-Been-Released)** for the v4.3. -* New module: **CMS Kit (pro)** -* New module: **Forms** -* **Blazor Server Side** support. -* **Extensibility** system for the Blazor UI. -* A lot of improvements done to ripen the **Microservice Startup Template**, including "new service" template, automatic database migrations, solution structure improvements, Tye, Prometheus, Grafana integrations, and more... -* Allow to use a **separate database schema** for tenants to not include host-related empty tables in tenant databases. -* Creating & **migrating tenant databases on the fly**. -* **Enabling/disabling modules** per edition/tenant. -* **Email settings** page. -* **Required** navigation properties on Suite code generation. +- New module: **CMS Kit (pro)** +- New module: **Forms** +- **Blazor Server Side** support. +- **Extensibility** system for the Blazor UI. +- A lot of improvements done to ripen the **Microservice Startup Template**, including "new service" template, automatic database migrations, solution structure improvements, Tye, Prometheus, Grafana integrations, and more... +- Allow to use a **separate database schema** for tenants to not include host-related empty tables in tenant databases. +- Creating & **migrating tenant databases on the fly**. +- **Enabling/disabling modules** per edition/tenant. +- **Email settings** page. +- **Required** navigation properties on Suite code generation. ## 4.2 (2021-01-28) See the detailed **[blog post / announcement](https://abp.io/blog/ABP.IO-Platform-4-2-Final-Has-Been-Released)** for the v4.2. -* Introducing the `IRepository.GetQueryableAsync()` method. -* Bulk operations for EF Core (`InsertManyAsync`, `UpdateManyAsync` and `DeleteManyAsync`). -* **Microservice startup template** (initial) to create microservice solutions. -* **Public website** application in the application startup template. -* **Blazor UI** for the Easy CRM sample application. -* Added login / **authorization** to the **Swagger UI** to test authorized APIs. -* **DBMS selection** on new application creation. -* Infrastructure for **Angular Unit Testing**. -* Iyzico integration for the **Payment** module. -* **Performance** optimization and other enhancements. +- Introducing the `IRepository.GetQueryableAsync()` method. +- Bulk operations for EF Core (`InsertManyAsync`, `UpdateManyAsync` and `DeleteManyAsync`). +- **Microservice startup template** (initial) to create microservice solutions. +- **Public website** application in the application startup template. +- **Blazor UI** for the Easy CRM sample application. +- Added login / **authorization** to the **Swagger UI** to test authorized APIs. +- **DBMS selection** on new application creation. +- Infrastructure for **Angular Unit Testing**. +- Iyzico integration for the **Payment** module. +- **Performance** optimization and other enhancements. ## 4.1 (2021-01-06) See the detailed **[blog post / announcement](https://abp.io/blog/ABP-IO-Platform-v4-1-Final-Has-Been-Released)** for the v4.1. -* Introducing the **module entity extensions** system. -* Bundling & Minification System for Blazor UI. -* **Organization Unit** Management for the Blazor UI. -* **Identity Server** Management for the Blazor UI. -* ABP Suite: **Navigation Property Selection** with Typeahead (supported by all UI types). -* **Spanish** language translation. +- Introducing the **module entity extensions** system. +- Bundling & Minification System for Blazor UI. +- **Organization Unit** Management for the Blazor UI. +- **Identity Server** Management for the Blazor UI. +- ABP Suite: **Navigation Property Selection** with Typeahead (supported by all UI types). +- **Spanish** language translation. ## 4.0 (2020-12-03) See the detailed **[blog post / announcement](https://abp.io/blog/ABP.IO-Platform-4.0-with-.NET-5.0-in-the-4th-Year)** for the v4.0. -* Upgraded to **.NET 5.0**. -* The **Blazor UI** option is now stable and officially supported. -* Completed the Blazor UI for the **file management** module. -* Upgraded to the **Identity Server 4.1.1** and revised the management UI. -* ABP Suite: Blazor UI **code generation**. -* ABP Suite: **Navigation property selection** supports dropdowns with auto-complete & lazy load. -* ABP Suite: **Generate new modules** inside an application solution. -* ABP Suite: Made the **backend code generation optional** to allow re-generate the UI with a different UI framework. +- Upgraded to **.NET 5.0**. +- The **Blazor UI** option is now stable and officially supported. +- Completed the Blazor UI for the **file management** module. +- Upgraded to the **Identity Server 4.1.1** and revised the management UI. +- ABP Suite: Blazor UI **code generation**. +- ABP Suite: **Navigation property selection** supports dropdowns with auto-complete & lazy load. +- ABP Suite: **Generate new modules** inside an application solution. +- ABP Suite: Made the **backend code generation optional** to allow re-generate the UI with a different UI framework. ## 3.3 (2020-10-27) See the detailed **[blog post / announcement](https://abp.io/blog/ABP-Framework-ABP-Commercial-3.3-Final-Have-Been-Released)** for the v3.3. -* Completed fundamental features, modules and the theme integration for the **Blazor UI**. -* Automatic **AntiForgery Token Validation** for HTTP APIs. -* New async LINQ extension methods for repositories. -* Stream support for the [Application Service](../framework/architecture/domain-driven-design/application-services.md) methods. -* Multi-Tenant **social/external logins** with options configurable on runtime. -* **Linked Accounts** system to link multiple accounts and switch between them easily. -* **Paypal** & **Stripe** integrations for the Payment Module. -* **reCAPTCHA** option for login & register forms. -* **ABP Suite** improvements. +- Completed fundamental features, modules and the theme integration for the **Blazor UI**. +- Automatic **AntiForgery Token Validation** for HTTP APIs. +- New async LINQ extension methods for repositories. +- Stream support for the [Application Service](../framework/architecture/domain-driven-design/application-services.md) methods. +- Multi-Tenant **social/external logins** with options configurable on runtime. +- **Linked Accounts** system to link multiple accounts and switch between them easily. +- **Paypal** & **Stripe** integrations for the Payment Module. +- **reCAPTCHA** option for login & register forms. +- **ABP Suite** improvements. ## 3.2 (2020-10-01) See the detailed **[blog post / announcement](https://abp.io/blog/ABP-Framework-ABP-Commercial-3.2-RC-With-The-New-Blazor-UI)** for the v3.2. -* Released the preview (experimental) **Blazor UI** option. -* **Angular** UI for the [file management](https://abp.io/modules/Volo.FileManagement) module. -* Managing the **application features** for the **host** side. -* User **profile picture** for the account module. -* Options to enable, disable or force **two factor authentication** for tenants and users. +- Released the preview (experimental) **Blazor UI** option. +- **Angular** UI for the [file management](https://abp.io/modules/Volo.FileManagement) module. +- Managing the **application features** for the **host** side. +- User **profile picture** for the account module. +- Options to enable, disable or force **two factor authentication** for tenants and users. ## 3.1 (2020-09-03) See the detailed **[blog post / announcement](https://abp.io/blog/ABP-Framework-v3.1-Final-Has-Been-Released)** for the v3.1 release. -* Completely re-written the ABP Suite **Angular UI code generation**, using the Angular Schematics system. -* Implemented **Authorization Code Authentication Flow** for the Angular UI. -* Revised and documented **social/external logins** for the account module and tested with major providers. -* Introduced the new external login system supporting to login via **LDAP / Active Directory**. Also, added a setting page to configure the LDAP options. -* Created a new **security log system** and the user interface to save and report all the authentication related operations (login, logout, change password...) for users. -* Implemented **email & phone number verification**. -* Implementing **locking a user** for a given period of time (locked users can not login to the application). -* Added breadcrumb and file icons for the file management module. +- Completely re-written the ABP Suite **Angular UI code generation**, using the Angular Schematics system. +- Implemented **Authorization Code Authentication Flow** for the Angular UI. +- Revised and documented **social/external logins** for the account module and tested with major providers. +- Introduced the new external login system supporting to login via **LDAP / Active Directory**. Also, added a setting page to configure the LDAP options. +- Created a new **security log system** and the user interface to save and report all the authentication related operations (login, logout, change password...) for users. +- Implemented **email & phone number verification**. +- Implementing **locking a user** for a given period of time (locked users can not login to the application). +- Added breadcrumb and file icons for the file management module. ## 3.0 (2020-07-01) See the detailed **[blog post / announcement](https://abp.io/blog/ABP-Framework-v3.0-Has-Been-Released)** for the v3.0 release. -* Introducing the **Azure BLOB Storage Provider**. -* Created the Oracle Integration Package for EF Core. -* New **File Management Module** that is used to store and manage files in your application. -* Migrated the Angular UI to the **Angular 10**. -* Published an **[API documentation](https://docs.abp.io/api-docs/commercial/2.9/api/index.html)** web site to explore the classes of the ABP. +- Introducing the **Azure BLOB Storage Provider**. +- Created the Oracle Integration Package for EF Core. +- New **File Management Module** that is used to store and manage files in your application. +- Migrated the Angular UI to the **Angular 10**. +- Published an **[API documentation](https://docs.abp.io/api-docs/commercial/2.9/api/index.html)** web site to explore the classes of the ABP. ## 2.9 (2020-06-04) See the detailed **[blog post / announcement](https://abp.io/blog/ABP-Framework-v2.9.0-Has-Been-Released)** for the v2.9 release. -* Performance improvements (pre-compiling razor pages). -* New **Organization Unit** Management UI for the [Identity Module](https://abp.io/modules/Volo.Identity.Pro) to create hierarchical organization units and manage their members and roles. -* Created **Angular UI** for the [Chat Module](https://abp.io/modules/Volo.Chat). -* Implemented **Angular UI** for the [Easy CRM](../samples/easy-crm.md) application. -* [ABP Suite](https://abp.io/tools/suite) code generation support for **module development**. -* New [leptontheme.com](http://leptontheme.com/) web site to show the **[Lepton Theme](https://abp.io/themes) components**. +- Performance improvements (pre-compiling razor pages). +- New **Organization Unit** Management UI for the [Identity Module](https://abp.io/modules/Volo.Identity.Pro) to create hierarchical organization units and manage their members and roles. +- Created **Angular UI** for the [Chat Module](https://abp.io/modules/Volo.Chat). +- Implemented **Angular UI** for the [Easy CRM](../samples/easy-crm.md) application. +- [ABP Suite](https://abp.io/tools/suite) code generation support for **module development**. +- New [leptontheme.com](http://leptontheme.com/) web site to show the **[Lepton Theme](https://abp.io/themes) components**. ## 2.8 (2020-05-21) See the detailed **blog post / announcement** for the v2.8 release: [https://abp.io/blog/ABP-v2.8.0-Releases-%26-Road-Map](https://abp.io/blog/ABP-v2.8.0-Releases-%26-Road-Map) -* RTL support for the MVC UI & Arabic localization. -* Completely renewed the **[Lepton Theme](https://abp.io/themes) styles** and add a new one. -* New module: Created a **real time [Chat Module](https://abp.io/modules/Volo.Chat)** that is built on ASP.NET Core SignalR. It currently has only the MVC / Razor Pages UI. Angular UI is on the way. -* Implemented **[module entity extension](../framework/architecture/modularity/extending/module-entity-extensions.md) system** for the **Angular UI**. Also improved the system to better handle float/double/decimal, date, datetime, enum and boolean properties. -* **Gravatar** integration for the Angular UI. -* Managing product groups on a **tree view** for the [EasyCRM sample application](../samples/easy-crm.md). +- RTL support for the MVC UI & Arabic localization. +- Completely renewed the **[Lepton Theme](https://abp.io/themes) styles** and add a new one. +- New module: Created a **real time [Chat Module](https://abp.io/modules/Volo.Chat)** that is built on ASP.NET Core SignalR. It currently has only the MVC / Razor Pages UI. Angular UI is on the way. +- Implemented **[module entity extension](../framework/architecture/modularity/extending/module-entity-extensions.md) system** for the **Angular UI**. Also improved the system to better handle float/double/decimal, date, datetime, enum and boolean properties. +- **Gravatar** integration for the Angular UI. +- Managing product groups on a **tree view** for the [EasyCRM sample application](../samples/easy-crm.md). ## 2.7 (2020-05-07) -See the detailed **blog post / announcement** for the v2.7 release: https://abp.io/blog/ABP-Framework-v2_7_0-Has-Been-Released +See the detailed **blog post / announcement** for the v2.7 release: [https://abp.io/blog/ABP-Framework-v2_7_0-Has-Been-Released](https://abp.io/blog/ABP-Framework-v2_7_0-Has-Been-Released) -* New module: **Text template management** (with angular and mvc UI - document is [coming](../modules/text-template-management.md)). -* **Dynamically add properties** to current entities of the depended modules (see [module entity extensions](../framework/architecture/modularity/extending/module-entity-extensions.md)) -* To be able to add **navigation properties** to entities with the ABP Suite (see [navigation properties](../suite/index.md) section). -* Dynamically add **data table columns** on the user interface (see the documents: [angular](../framework/ui/angular/data-table-column-extensions.md), [mvc](../framework/ui/mvc-razor-pages/data-table-column-extensions.md)). -* Created a rich **sample solution**, named "Easy CRM" (see the document). -* Allow to dynamically **override the logo**. -* **Optimize database migrations** & seed code for multi-tenant multi-database systems. -* ABP Suite: Make **menu item active** on navigation menu when selected. -* ABP Suite: Improve **enum usage** while creating new entities. -* Bug fixes in the [Lepton Theme](https://abp.io/themes), [ABP Suite](https://abp.io/tools/suite) and other modules. +- New module: **Text template management** (with angular and mvc UI - document is [coming](../modules/text-template-management.md)). +- **Dynamically add properties** to current entities of the depended modules (see [module entity extensions](../framework/architecture/modularity/extending/module-entity-extensions.md)) +- To be able to add **navigation properties** to entities with the ABP Suite (see [navigation properties](../suite/index.md) section). +- Dynamically add **data table columns** on the user interface (see the documents: [angular](../framework/ui/angular/data-table-column-extensions.md), [mvc](../framework/ui/mvc-razor-pages/data-table-column-extensions.md)). +- Created a rich **sample solution**, named "Easy CRM" (see the document). +- Allow to dynamically **override the logo**. +- **Optimize database migrations** & seed code for multi-tenant multi-database systems. +- ABP Suite: Make **menu item active** on navigation menu when selected. +- ABP Suite: Improve **enum usage** while creating new entities. +- Bug fixes in the [Lepton Theme](https://abp.io/themes), [ABP Suite](https://abp.io/tools/suite) and other modules. ## See Also -* [Road map](road-map.md) +- [Road map](road-map.md) + diff --git a/docs/en/release-info/road-map.md b/docs/en/release-info/road-map.md index c83a7efff2..1f443a0fa9 100644 --- a/docs/en/release-info/road-map.md +++ b/docs/en/release-info/road-map.md @@ -1,7 +1,7 @@ ```json //[doc-seo] { - "Description": "Explore the ABP Platform Road Map for insights on upcoming features, release schedules, and improvements in version 10.4, planned for May 2026." + "Description": "Explore the ABP Platform Road Map for insights on upcoming features, release schedules, and improvements in version 10.5, planned for June 2026." } ``` @@ -11,12 +11,11 @@ This document provides a road map, release schedule, and planned features for th ## Next Versions -### v10.4 +### v10.5 -After v10.3 reaches stable, the next planned version will be 10.4, which is scheduled to be released as a stable version in May 2026. We will be mostly working on the following topics: +The next planned version will be 10.5, which is scheduled to be released as a stable version in June 2026. We will be mostly working on the following topics: * Framework - * Blazor UI: Moving from Blazorise to MudBlazor * Token Verification Improvements (Refresh Token Support) * Upgrading 3rd-party Dependencies * Enhancements in the Core Points @@ -40,7 +39,6 @@ After v10.3 reaches stable, the next planned version will be 10.4, which is sche * UI/UX Improvements on Existing Application Modules * Updating Existing Tutorials & Documents (with Other UI & DB Options) - * React Native template and implementation documents ## Backlog Items diff --git a/docs/en/solution-templates/layered-web-application/mobile-applications.md b/docs/en/solution-templates/layered-web-application/mobile-applications.md index 553064284f..bf2e6375ae 100644 --- a/docs/en/solution-templates/layered-web-application/mobile-applications.md +++ b/docs/en/solution-templates/layered-web-application/mobile-applications.md @@ -100,6 +100,8 @@ You can follow [Mobile Application Development Tutorial - MAUI](../../tutorials/ This is the mobile application that is built based on Facebook's [React Native framework](https://reactnative.dev/) and [Expo](https://expo.dev/). It will be in the solution only if you've selected React Native as your mobile application option. +The UI is built with **[NativeWind v4](https://www.nativewind.dev/)** (Tailwind CSS for React Native) on top of a shadcn-inspired neutral palette, with full **light/dark mode** support. See [Styling with NativeWind](../../framework/ui/react-native/styling-with-nativewind.md) for the styling system reference. + #### Project Structure - **Environment.ts**: file using for provide application level variables like `apiUrl`, `oAuthConfig` and etc. @@ -110,16 +112,20 @@ This is the mobile application that is built based on Facebook's [React Native f - **contexts**: `contexts` folder contains [react context](https://react.dev/reference/react/createContext). You can expots your contexts in this folder. `Localization context provided in here` -- **navigators**: folder contains [react-native stacks](https://reactnavigation.org/docs/stack-navigator/). After create new *FeatureName*Navigator we need to provide in `DrawerNavigator.tsx` file as `Drawer.Screen` +- **hooks**: custom [React hooks](https://react.dev/reference/react/hooks) for shared UI and app logic. For example, `useThemeColors` returns light/dark palette values when a component needs explicit colors instead of NativeWind `className`, and `useLogout` handles signing out—plus other hooks bundled with the template. + +- **navigators**: folder contains [React Navigation](https://reactnavigation.org/) stacks. The template includes `BottomTabNavigator`, `DrawerNavigator`, `HomeNavigator`, `SettingsNavigator` and `AccountNavigator`. After creating a new *FeatureName*Navigator, register it in the appropriate parent navigator (e.g. `DrawerNavigator.tsx` or `BottomTabNavigator.tsx`). -- **screens**: is the content of navigated page. We'll pass as component property to [Stack.Screen](https://reactnavigation.org/docs/native-stack-navigator/) +- **screens**: contains the content of each navigated page. The template ships with screens for `Home`, `Account`, `Settings`, `Login`, `Register`, `ForgotPassword`, `ResetPassword`, `ChangePassword` and `ProfilePicture`. Each screen is wired to a navigator as a [Stack.Screen](https://reactnavigation.org/docs/native-stack-navigator/) component. - **store**: folder manages state-management operations. We will define `actions`, `listeners`, `reducers`, and `selectors` here. -- **styles**: folder contains app styles. `system-style.ts` comes built in template we can also add new styles. +- **theme**: folder exposes Paper-only theme colors used by `react-native-paper`'s `TextInput`. Most theming now lives in `tailwind.config.js` (see below). - **utils**: folder contains helper functions that we can use in application +In addition to `src/`, the project root hosts the NativeWind setup. `tailwind.config.js` defines design tokens, the color palette, and dark mode. `global.css` contains Tailwind's layer directives. `metro.config.js` and `babel.config.js` configure Metro and Babel so NativeWind can transform your styles. `nativewind-env.d.ts` adds TypeScript typings for the `className` prop on components. + #### Running the Application React Native applications can't be run with the solution runner. You need to run them with the React Native CLI. You can check the [React Native documentation](https://reactnative.dev/docs/environment-setup) to learn how to setup the environment for React Native development. diff --git a/docs/en/solution-templates/microservice/mobile-applications.md b/docs/en/solution-templates/microservice/mobile-applications.md index c14895b7b9..aab9b9a5f9 100644 --- a/docs/en/solution-templates/microservice/mobile-applications.md +++ b/docs/en/solution-templates/microservice/mobile-applications.md @@ -145,6 +145,8 @@ You can follow [Mobile Application Development Tutorial - MAUI](../../tutorials/ This is the mobile application that is built based on Facebook's [React Native framework](https://reactnative.dev/) and [Expo](https://expo.dev/). It will be in the solution only if you've selected React Native as your mobile application option. +The UI is built with **[NativeWind v4](https://www.nativewind.dev/)** (Tailwind CSS for React Native) on top of a shadcn-inspired neutral palette, with full **light/dark mode** support. See [Styling with NativeWind](../../framework/ui/react-native/styling-with-nativewind.md) for the styling system reference. + #### Project Structure - **Environment.ts**: file using for providing application level variables like `apiUrl`, `oAuthConfig` and etc. @@ -157,20 +159,22 @@ This is the mobile application that is built based on Facebook's [React Native f - **hocs**: this folder is added to contain higher order components. The purpose is to wrap components with additional features or properties. It initially has a `PermissionHoc.tsx` that wraps a component to check the permission grant status. -- **hooks**: covers the react native hooks where you can get a reference from [the official documentation](https://react.dev/reference/react/hooks). +- **hooks**: Custom [React hooks](https://react.dev/reference/react/hooks) for shared UI and app logic. For example, `useThemeColors` returns light/dark palette values when a component needs explicit colors instead of NativeWind `className`, and `useLogout` handles signing out—plus other hooks bundled with the template. - **interceptors**: initializes a file called `APIInterceptor.ts` that has a function to manage the http operations in a better way. -- **navigators**: folder contains [react-native stacks](https://reactnavigation.org/docs/stack-navigator/). After creating a new *FeatureName*Navigator we need to provide in `DrawerNavigator.ts` file as `Drawer.Screen` +- **navigators**: folder contains [React Navigation](https://reactnavigation.org/) stacks. The template includes `BottomTabNavigator`, `DrawerNavigator`, `HomeNavigator`, `SettingsNavigator` and `AccountNavigator`. After creating a new *FeatureName*Navigator, register it in the appropriate parent navigator (e.g. `DrawerNavigator.tsx` or `BottomTabNavigator.tsx`). -- **screens**: folder has the content of navigated page. We will pass as component property to [Stack.Screen](https://reactnavigation.org/docs/native-stack-navigator/) +- **screens**: contains the content of each navigated page. The template ships with screens for `Home`, `Account`, `Settings`, `Login`, `Register`, `ForgotPassword`, `ResetPassword`, `ChangePassword` and `ProfilePicture`. Each screen is wired to a navigator as a [Stack.Screen](https://reactnavigation.org/docs/native-stack-navigator/) component. - **store**: folder manages state-management operations. We will define `actions`, `listeners`, `reducers`, and `selectors` here. -- **styles**: folder contains app styles. `system-style.ts` comes built in template we can also add new styles. +- **theme**: folder exposes Paper-only theme colors used by `react-native-paper`'s `TextInput`. Most theming now lives in `tailwind.config.js` (see below). - **utils**: folder contains helper functions that we can use in application +In addition to `src/`, the project root hosts the NativeWind setup. `tailwind.config.js` defines design tokens, the color palette, and dark mode. `global.css` contains Tailwind's layer directives. `metro.config.js` and `babel.config.js` configure Metro and Babel so NativeWind can transform your styles. `nativewind-env.d.ts` adds TypeScript typings for the `className` prop on components. + #### Running the Application React Native applications can't be run with the solution runner. You need to run them with the React Native CLI. You can check the [React Native documentation](https://reactnative.dev/docs/environment-setup) to learn how to setup the environment for React Native development. diff --git a/docs/en/studio/release-notes.md b/docs/en/studio/release-notes.md index 5b1a1fbeb2..430bd1e892 100644 --- a/docs/en/studio/release-notes.md +++ b/docs/en/studio/release-notes.md @@ -9,40 +9,85 @@ This document contains **brief release notes** for each ABP Studio release. Release notes only include **major features** and **visible enhancements**. Therefore, they don't include all the development done in the related version. -## 2.2.6 (2026-04-08) Latest - -- Disable Scriban 7.0 cumulative output limit for template rendering +## 3.0.2 (2026-05-12) Latest + +* Modern template osx fix +* Update dark theme - BackgroundColorLighter +* Fix initial task warning handling +* Abp Studio ai agent cont +* Add Volo.Abp.Elsa to module list +* Register reCAPTCHA in HttpApi host templates for CmsKit contact endpoint +* Optimized system prompt in ABP Studio +* Register `MVC.RootUrl` in `HttpApi.Host` template + +## 3.0.1 (2026-05-06) + +* Enhanced Project Wizard: Fixed the optional module selection step for a smoother project setup experience +* React Admin Console Improvements: Resolved identified issues in the React administration templates for better stability +* MacOS Performance: Optimized the internal build pipeline for a more reliable experience on Apple devices +* Stability Fixes: Improved core UI components to ensure parameter consistency across updates + +## 3.0.0 (2026-05-06) + +* Introducing ABP AI Agent: A next-generation smart assistant to accelerate your development +* Enhanced ABP Coding Agent: Smarter code generation and guidance integrated directly into your workflow +* Modern UI Redesign: New "Bento-style" dashboard and a streamlined single sidebar for better focus +* Drag & Drop Support: Easily open your projects by dragging .sln, .slnx, or .abpsln files into the app +* Improved React Templates: Optimized authentication, local debugging, and a new "Thin UI" layered template option +* CMS Kit Newsletter Integration: Ready-to-use newsletter component for public websites +* Faster CLI Experience: Simplified abp login process and improved help output for better usability +* Enhanced Solution Runner: Smarter project management and more reliable configuration handling +* Simplified Project Structure: Legacy systems renamed to "Classic" for a cleaner transition to modern architectures +* System Stability: Significant reduction in background noise/errors and optimized performance +* Global Platform Update: Fully updated to the latest underlying framework versions (v10.3.0) + +## 2.2.7 (2026-04-20) + +* Improved Blazor WebApp template setup for easier tiered application development +* Added application version tracking in analytics events +* Fixed issues in Basic Theme public website templates +* Improved PostgreSQL vector database support in templates +* Enhanced Blazor CRUD support with built-in Book management example +* Modernized React Native template components +* Updated to ABP 10.3 and Blazorise 2.0.4 +* Improved run profile and PowerShell execution reliability +* Added AI Management and Rate Limiting modules to available module options + + +## 2.2.6 (2026-04-08) + +* Disable Scriban 7.0 cumulative output limit for template rendering ## 2.2.5 (2026-04-08) -- Upgraded GPT-5 → GPT-5.4 and improved AI management (providers, blob storage, CLI options) -- Fixed critical build issues (MongoDB, MAUI) and improved overall stability -- Enhanced monitoring (HTTP requests & exceptions) -- Added DBMS auto-detection from connection string -- Upgraded to ABP 10.2 and Scriban 7.0.0 -- Improved developer experience and telemetry (PostHog) -- Minor UI fixes and workflow adjustments (manual build trigger) +* Upgraded GPT-5 → GPT-5.4 and improved AI management (providers, blob storage, CLI options) +* Fixed critical build issues (MongoDB, MAUI) and improved overall stability +* Enhanced monitoring (HTTP requests & exceptions) +* Added DBMS auto-detection from connection string +* Upgraded to ABP 10.2 and Scriban 7.0.0 +* Improved developer experience and telemetry (PostHog) +* Minor UI fixes and workflow adjustments (manual build trigger) ## 2.2.4 (2026-03-25) -- Add `Template Create and Build` workflow -- Disable NuGetAudit in template common.props to prevent CLI deadlock during initial migration +* Add `Template Create and Build` workflow +* Disable NuGetAudit in template common.props to prevent CLI deadlock during initial migration ## 2.2.3 (2026-03-24) -- Fix PostHog environment detection +* Fix PostHog environment detection ## 2.2.1 (2026-02-20) -- Fix tiered Blazor WebApp template HttpApi reference -- Add LeptonX theme templates AuthServer and HttpApi projects -- Enable razor runtime compilation on templates -- Configure HttpClientFactoryOptions for CLI client -- Bump ABP to 10.1.1 and LeptonX to 5.1.1 -- Blazor & Angular UIs: Add AI Management option to the Startup Templates (app-nolayers, app, ms templates) -- Handle docker container start failures in solution runner -- Fix import module version dropdown ordering -- Add PostHog integration for Studio and CLI +* Fix tiered Blazor WebApp template HttpApi reference +* Add LeptonX theme templates AuthServer and HttpApi projects +* Enable razor runtime compilation on templates +* Configure HttpClientFactoryOptions for CLI client +* Bump ABP to 10.1.1 and LeptonX to 5.1.1 +* Blazor & Angular UIs: Add AI Management option to the Startup Templates (app-nolayers, app, ms templates) +* Handle docker container start failures in solution runner +* Fix import module version dropdown ordering +* Add PostHog integration for Studio and CLI ## 2.2.1 (2026-02-20) diff --git a/docs/en/studio/version-mapping.md b/docs/en/studio/version-mapping.md index a5a1e366f1..f16bb25f7a 100644 --- a/docs/en/studio/version-mapping.md +++ b/docs/en/studio/version-mapping.md @@ -11,7 +11,8 @@ This document provides a general overview of the relationship between various ve | **ABP Studio Version** | **ABP Version of Startup Template** | |------------------------|---------------------------| -| 2.2.5 - 2.2.6 | 10.2.0 | +| 2.2.7 - 3.0.2 | 10.3.0 | +| 2.2.5 - 2.2.6 | 10.2.0 | | 2.2.2 - 2.2.4 | 10.1.1 | | 2.2.1 | 10.1.0 | | 2.1.5 - 2.1.9 | 10.0.2 | diff --git a/docs/en/suite/create-solution.md b/docs/en/suite/create-solution.md index 39d3fb925e..1ec5a799cd 100644 --- a/docs/en/suite/create-solution.md +++ b/docs/en/suite/create-solution.md @@ -25,4 +25,6 @@ From ABP Suite v8.3, **create a new solution** option has been removed. Instead, it's suggessted to use [ABP Studio](../studio/index.md) or [ABP CLI](../cli/index.md) to create a new solution. After, creating an ABP Solution, then you can generate CRUD pages via ABP Suite as always. +> **Note:** ABP Suite does not generate React UI pages. If you create a modern React solution, use Suite where applicable for backend and application-layer generation, and develop the React UI in the source-owned [React UI](../framework/ui/react/index.md) application. ABP license holders can use [ABP Studio AI Agent](https://abp.io/studio/ai-agent) with predefined AI credits to generate React pages more easily. + > ABP Studio has a shortcut for running [ABP Suite](./index.md) to allow using it without starting it externally and using it on a browser, which means you can create, manage, deploy your applications in a single desktop application and also generate CRUD pages via Suite as a pre-integrated application to ABP Studio. \ No newline at end of file diff --git a/docs/en/suite/creating-many-to-many-relationship.md b/docs/en/suite/creating-many-to-many-relationship.md index f711c2a891..005cab9896 100644 --- a/docs/en/suite/creating-many-to-many-relationship.md +++ b/docs/en/suite/creating-many-to-many-relationship.md @@ -75,4 +75,6 @@ Click the **Navigations** tab. Then click the **Add navigation collection** butt The below image is the final page created by ABP Suite. The **new book** dialog has a **Categories** tab which lists all categories of the book and allows add/remove categories. +> **Note:** ABP Suite does not generate React UI for this page. The generated UI applies to Suite-supported UI stacks such as MVC, Blazor and Angular. ABP license holders can use [ABP Studio AI Agent](https://abp.io/studio/ai-agent) with predefined AI credits to generate React pages more easily. + ![navigation-collection-books-page](../images/navigation-collection-books-page.8.1.png) diff --git a/docs/en/suite/creating-master-detail-relationship.md b/docs/en/suite/creating-master-detail-relationship.md index b1d8874d5b..94bda1b7fa 100644 --- a/docs/en/suite/creating-master-detail-relationship.md +++ b/docs/en/suite/creating-master-detail-relationship.md @@ -39,6 +39,8 @@ You need to specify the **Entity type** as _Master_ (default). Then, provide the Create a child entity, specify the **Entity type** as _Child_, and associate it with a master entity. In the figure above, you can see an example of, a child entity **OrderLine** with the associated master entity **Order**. When you specify the **Entity type** as _Child_, then the UI and tests will not be generated for the child entity and instead, all of the orchestration of the entity will be delegated to its master entity. +> **Note:** ABP Suite does not generate React UI for master-detail pages. The generated UI applies to Suite-supported UI stacks such as MVC, Blazor and Angular. ABP license holders can use [ABP Studio AI Agent](https://abp.io/studio/ai-agent) with predefined AI credits to generate React pages more easily. + Application services will be generated for the child entity, so it's possible for you to consume its endpoints and create-update-list or delete the child entity specifically. Suite also respects your multi-tenancy selection and sets the child entity as a multi-tenant entity, if the master entity has multi-tenancy enabled. > **Note**: Child entities are not a good candidate to establish a many-to-many relationship and therefore that's disabled in the Suite UI. So, you can't create a many-to-many relationship for child entities, but you can create a one-to-many relationship. diff --git a/docs/en/suite/customizing-the-generated-code.md b/docs/en/suite/customizing-the-generated-code.md index e5061ca700..350405ddb0 100644 --- a/docs/en/suite/customizing-the-generated-code.md +++ b/docs/en/suite/customizing-the-generated-code.md @@ -58,6 +58,8 @@ You can see the example below, which demonstrates defining a new method in the ` For the UI side, ABP Suite provides convenient comment placeholders within pages for MVC, Blazor, and Angular UIs. These comment sections serve as hook points where you can add your custom code. By placing your custom logic with these hook points, you can enhance the UI to your needs without the fear of losing your changes during the next CRUD page generation. +> **Note:** ABP Suite does not generate React UI pages or React customization hook points. React UI is part of ABP's modern template system and is customized directly in the source-owned [React UI](../framework/ui/react/index.md) application. ABP license holders can use [ABP Studio AI Agent](https://abp.io/studio/ai-agent) with predefined AI credits to generate React pages and customize them more easily. + ### Customizations on MVC UI If you have created your application with MVC UI, you can see the extended classes for _the code-behind classes of your pages_, under the `Pages` folder: diff --git a/docs/en/suite/editing-templates.md b/docs/en/suite/editing-templates.md index 354adc8ead..dd91fee86c 100644 --- a/docs/en/suite/editing-templates.md +++ b/docs/en/suite/editing-templates.md @@ -16,6 +16,8 @@ The are 2 template filters: 1. **UI**: You can switch between `Angular`, `MVC` and `Blazor` templates. 2. **Database provider:** You can switch between `EF Core` and `MongoDb` templates. +> **Note:** ABP Suite does not include React UI templates or generate React client-side code. React UI belongs to the modern template system and should be developed in the source-owned [React UI](../framework/ui/react/index.md) application. ABP license holders can use [ABP Studio AI Agent](https://abp.io/studio/ai-agent) with predefined AI credits to generate React pages more easily. + These filters are automatically selected based on your ABP solution. The selected solution is shown on the top right of the page. In the current screenshot it is "Acme.BookStore". ABP Suite templates are embedded resources which are stored in the `Volo.Abp.Commercial.SuiteTemplates` package. When you update your project, Suite templates are also being updated. To be able to generate the correct code for your project, the version of `Volo.Abp.Commercial.SuiteTemplates` must be the same as your Suite version. diff --git a/docs/en/suite/generating-crud-page.md b/docs/en/suite/generating-crud-page.md index 8c605418e8..3325e8c03c 100644 --- a/docs/en/suite/generating-crud-page.md +++ b/docs/en/suite/generating-crud-page.md @@ -108,7 +108,9 @@ To create a new entity, make sure the *-New entity-* is selected in the **Entity * **Update database:** When you add a new migration, ABP Suite can automatically execute update-database command so that the changes are being applied to the database. -* **Create user interface:** Creates pages, modals, components, `JavaScript`, `CSS ` files and adds the new page to the main menu. If you don't have a requirement to manage the entity via user interface, you can uncheck this option. +* **Create user interface:** Creates pages, modals, components, `JavaScript`, `CSS` files and adds the new page to the main menu. If you don't have a requirement to manage the entity via user interface, you can uncheck this option. + + > **Note:** This option does not generate React UI pages. ABP Suite's UI generation targets Suite-supported UI stacks such as MVC, Blazor and Angular. For React-based solutions, build the UI in the source-owned [React UI](../framework/ui/react/index.md) application. ABP license holders can use [ABP Studio AI Agent](https://abp.io/studio/ai-agent) with predefined AI credits to generate React pages more easily. * **Excel export**: Creates a button that exports a list of all the data that were added to the entity to an Excel file. diff --git a/docs/en/suite/index.md b/docs/en/suite/index.md index b1c8d43fa7..d24b36af5a 100644 --- a/docs/en/suite/index.md +++ b/docs/en/suite/index.md @@ -21,6 +21,8 @@ ABP Suite is a complementary tool to the ABP Platform. ABP Suite allows you to build web pages in a matter of minutes. +> **Important:** ABP Suite does not generate React UI pages or client-side React code. Its CRUD UI generation is designed for the established Suite-supported UI stacks, such as MVC, Blazor and Angular. The new [React UI](../framework/ui/react/index.md) belongs to ABP's modern template system and is designed for an AI-first, AI-oriented development flow with a modern frontend stack such as React, TypeScript, Vite, TanStack Router, TanStack Query, shadcn/ui, Zod and React Hook Form. In React UI solutions, ABP Suite can still be useful for the backend and application layers where applicable, but the React UI side is expected to be developed in the source-owned React application. ABP license holders can use [ABP Studio AI Agent](https://abp.io/studio/ai-agent) with predefined AI credits to generate React pages and evolve the UI more easily. + It's a .NET Core Global tool that can be installed from the command line. If you are using [ABP Studio](../studio/index.md), you don't even need to install it because it should already be installed, when you first installed the [ABP Studio](../studio/index.md). By using the ABP Suite, you can generate CRUD pages from the database to the front-end and directly get a kickstart for your application. ABP Suite is actively developed and new features are being added version by version according to the roadmap and your feedback. diff --git a/docs/en/testing/ui-tests.md b/docs/en/testing/ui-tests.md index ac9e6eb91f..f8256ac2b1 100644 --- a/docs/en/testing/ui-tests.md +++ b/docs/en/testing/ui-tests.md @@ -34,6 +34,7 @@ See the following documents to learn Non Visual UI Testing: * [Testing in ASP.NET Core MVC / Razor Pages](../framework/ui/mvc-razor-pages/testing.md) * [Testing in Angular](../framework/ui/angular/testing.md) * [Testing in Blazor](../framework/ui/blazor/testing.md) +* [Testing in React](../framework/ui/react/unit-testing.md) ## Visual Tests diff --git a/docs/en/tutorials/book-store-with-abp-suite/part-05.md b/docs/en/tutorials/book-store-with-abp-suite/part-05.md index 73a1d9c348..865658d186 100644 --- a/docs/en/tutorials/book-store-with-abp-suite/part-05.md +++ b/docs/en/tutorials/book-store-with-abp-suite/part-05.md @@ -11,7 +11,8 @@ //[doc-params] { "UI": ["MVC","Blazor","BlazorServer", "BlazorWebApp","NG","MAUIBlazor"], - "DB": ["EF", "Mongo"] + "DB": ["EF", "Mongo"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` diff --git a/docs/en/tutorials/book-store/part-02.md b/docs/en/tutorials/book-store/part-02.md index 1fe21c2848..429d7ec17c 100644 --- a/docs/en/tutorials/book-store/part-02.md +++ b/docs/en/tutorials/book-store/part-02.md @@ -10,7 +10,8 @@ //[doc-params] { "UI": ["MVC","Blazor","BlazorServer", "BlazorWebApp", "NG", "MAUIBlazor"], - "DB": ["EF","Mongo"] + "DB": ["EF","Mongo"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` ````json @@ -549,6 +550,8 @@ When you click on the Books menu item under the Book Store parent, you will be r ### Book List +{{if BlazorUI == "Blazorise"}} + We will use the [Blazorise library](https://blazorise.com/) as the UI component kit. It is a very powerful library that supports major HTML/CSS frameworks, including Bootstrap. ABP provides a generic base class - `AbpCrudPageBase<...>`, to create CRUD style pages. This base class is compatible with the `ICrudAppService` that was used to build the `IBookAppService`. So, we can inherit from the `AbpCrudPageBase` to automate the code behind for the standard CRUD stuff. @@ -624,6 +627,81 @@ Open the `Books.razor` and replace the content as the following: While the code above is pretty easy to understand, you can check the Blazorise [Card](https://blazorise.com/docs/components/card/) and [DataGrid](https://blazorise.com/docs/extensions/datagrid/) documents to understand them better. +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +We will use the [MudBlazor library](https://mudblazor.com/) as the UI component kit. It is a Material Design component library built natively for Blazor. + +ABP provides a generic base class — `AbpMudCrudPageBase<...>`, to create CRUD style pages. This base class is compatible with the `ICrudAppService` that was used to build the `IBookAppService`. So, we can inherit from the `AbpMudCrudPageBase` to automate the code behind for the standard CRUD stuff. + +Open the `Books.razor` and replace the content as the following: + +````razor +@page "/books" +@using Volo.Abp.Application.Dtos +@using Acme.BookStore.Books +@using Acme.BookStore.Localization +@inherits AbpMudCrudPageBase + + + + + @L["Books"] + + + + + + + + + @L[$"Enum:BookType.{(int)context.Item.Type}"] + + + + + @context.Item.PublishDate.ToShortDateString() + + + + + + @context.Item.CreationTime.ToLongDateString() + + + + + + + +@code +{ + public Books() // Constructor + { + LocalizationResource = typeof(BookStoreResource); + } +} +```` + +> If you see some syntax errors, you can ignore them if your application is properly built and running. Visual Studio still has some bugs with Blazor. + +* Inherited from `AbpMudCrudPageBase` which implements all the CRUD details for us. +* `Entities`, `TotalCount`, `PageSize`, `OnDataGridReadAsync` are defined in the base class. +* `LocalizationResource` is set to the `BookStoreResource` to localize the texts. +* This page uses the standard MudBlazor `MudDataGrid` with `` definitions. ABP also ships an `AbpMudExtensibleDataGrid` that integrates with the [data table column extension system](../../framework/ui/blazor/data-table-column-extensions.md) when you need to extend module pages. + +While the code above is pretty easy to understand, you can check the MudBlazor [Card](https://mudblazor.com/components/card) and [DataGrid](https://mudblazor.com/components/datagrid) documents to understand them better. + +{{end}} + #### About the AbpCrudPageBase We will continue benefitting from `AbpCrudPageBase` for the books page. You could just inject the `IBookAppService` and perform all the server side calls yourself (thanks to the [Dynamic C# HTTP API Client Proxy](../../framework/api-development/dynamic-csharp-clients.md) system of the ABP). We will do it manually for the authors page to demonstrate how to call the server side HTTP APIs in your Blazor applications. diff --git a/docs/en/tutorials/book-store/part-03.md b/docs/en/tutorials/book-store/part-03.md index 73b2daea4e..259475b27d 100644 --- a/docs/en/tutorials/book-store/part-03.md +++ b/docs/en/tutorials/book-store/part-03.md @@ -10,7 +10,8 @@ //[doc-params] { "UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG", "MAUIBlazor"], - "DB": ["EF","Mongo"] + "DB": ["EF","Mongo"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -1094,6 +1095,8 @@ In this section, you will learn how to create a new modal dialog form to create ### Add a "New Button" Button +{{if BlazorUI == "Blazorise"}} + Open the `Books.razor` and replace the `` section with the following code: ````xml @@ -1110,6 +1113,27 @@ Open the `Books.razor` and replace the `` section with the following ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +Open the `Books.razor` and replace the `` section with the following code: + +````razor + + + @L["Books"] + + + @L["NewBook"] + + +```` + +{{end}} + This will change the card header by adding a "New book" button to the right side: ![blazor-add-book-button](./images/blazor-add-book-button-2.png) @@ -1118,6 +1142,8 @@ Now, we can add a modal that will be opened when we click the button. ### Book Creation Modal +{{if BlazorUI == "Blazorise"}} + Open the `Books.razor` and add the following code to the end of the page: ````xml @@ -1184,6 +1210,57 @@ This code requires a service; Inject the `AbpBlazorMessageLocalizerHelper` at * The form implements validation and the `AbpBlazorMessageLocalizerHelper` is used to simply localize the validation messages. * The `CreateModal` object, `CloseCreateModalAsync` and `CreateEntityAsync` methods are defined by the base class. Check out the [Blazorise documentation](https://blazorise.com/docs/) if you want to understand the `Modal` and the other components. +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +Open the `Books.razor` and add the following code to the end of the page: + +````razor + + + @L["NewBook"] + + + + + + + @foreach (BookType bookTypeValue in Enum.GetValues(typeof(BookType))) + { + @L[$"Enum:BookType.{(int)bookTypeValue}"] + } + + + + + + + + @L["Cancel"] + @L["Save"] + + +```` + +* The form uses `[Required]`/DataAnnotations for validation; messages are localized via the same `AbpResource` localization system. +* The `_createDialog` field, `CloseCreateDialogAsync`, `CreateFormRef` and `CreateEntityAsync` are all defined in `AbpMudCrudPageBase`. Check the [MudBlazor documentation](https://mudblazor.com/components/dialog) if you want to understand the `MudDialog` and other components. +* `MudDialog.Options` widens the dialog (`MaxWidth.Medium` + `FullWidth`) so the form fields are not cramped. +* `MudStack` with `Spacing="3"` keeps the inputs visually separated; without it MudBlazor inputs render flush against each other. +* `MudDatePicker.@bind-Date` requires a nullable `DateTime?`. If your DTO uses non-nullable `DateTime`, change it to `DateTime?` (`public DateTime? PublishDate { get; set; }`) when using the MudBlazor variant. + +{{end}} + That's all. Run the application and try to add a new book: ![blazor-new-book-modal](./images/blazor-new-book-modal-2.png) @@ -1194,6 +1271,8 @@ Editing a book is similar to creating a new book. ### Actions Dropdown +{{if BlazorUI == "Blazorise"}} + Open the `Books.razor` and add the following `DataGridEntityActionsColumn` section inside the `DataGridColumns` as the first item: ````xml @@ -1212,12 +1291,38 @@ Open the `Books.razor` and add the following `DataGridEntityActionsColumn` secti The `DataGridEntityActionsColumn` component is used to show an "Actions" dropdown for each row in the `DataGrid`. The `DataGridEntityActionsColumn` shows a **single button** instead of a dropdown if there is only one available action inside it: +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +Open the `Books.razor` and add the following `TemplateColumn` as the first column inside the `` section of the `MudDataGrid`: + +````razor + + + + + @L["Edit"] + + + + +```` + +* `OpenEditDialogAsync` is defined in the base class which takes the entity (book) to edit. + +This renders an "Actions" dropdown menu (`MudMenu`) for each row in the data grid. We will add the **Delete** menu item later in the *Deleting a Book* section. + +{{end}} + ![blazor-edit-book-action](./images/blazor-edit-book-action-3.png) ### Edit Modal We can now define a modal to edit the book. Add the following code to the end of the `Books.razor` page: +{{if BlazorUI == "Blazorise"}} + ````xml @@ -1273,6 +1378,49 @@ We can now define a modal to edit the book. Add the following code to the end of ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````razor + + + @EditingEntity.Name + + + + + + + @foreach (BookType bookTypeValue in Enum.GetValues(typeof(BookType))) + { + @L[$"Enum:BookType.{(int)bookTypeValue}"] + } + + + + + + + + @L["Cancel"] + @L["Save"] + + +```` + +{{end}} + ### Mapperly Configuration The base `AbpCrudPageBase` uses the [object to object mapping](../../framework/infrastructure/object-to-object-mapping.md) system to convert an incoming `BookDto` object to a `CreateUpdateBookDto` object. So, we need to define the mapping. @@ -1304,7 +1452,11 @@ You can now run the application and try to edit a book. ## Deleting a Book -Open the `Books.razor` page and add the following `EntityAction` code under the "Edit" action inside `EntityActions`: +Open the `Books.razor` page and add the following entity action code under the "Edit" action. + +{{if BlazorUI == "Blazorise"}} + +Add the following `EntityAction` code under the "Edit" action inside `EntityActions`: ````xml ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +Add the following `MudMenuItem` after the "Edit" item inside the actions `MudMenu`: + +````razor + + @L["Delete"] + +```` + +{{end}} + * `DeleteEntityAsync` is defined in the base class that deletes the entity by performing a call to the server. * `ConfirmationMessage` is a callback to show a confirmation message before executing the action. * `GetDeleteConfirmationMessage` is defined in the base class. You can override this method (or pass another value to the `ConfirmationMessage` parameter) to customize the localization message. @@ -1327,6 +1498,8 @@ Run the application and try to delete a book. Here's the complete code to create the book management CRUD page, that has been developed in the last two parts: +{{if BlazorUI == "Blazorise"}} + ````xml @page "/books" @using Volo.Abp.Application.Dtos @@ -1521,3 +1694,142 @@ Here's the complete code to create the book management CRUD page, that has been {{end}} +{{if BlazorUI == "MudBlazor"}} + +````razor +@page "/books" +@using Volo.Abp.Application.Dtos +@using Acme.BookStore.Books +@using Acme.BookStore.Localization +@using Microsoft.Extensions.Localization +@inherits AbpMudCrudPageBase + + + + + @L["Books"] + + + @L["NewBook"] + + + + + + + + + @L["Edit"] + @L["Delete"] + + + + + + + @L[$"Enum:BookType.{(int)context.Item.Type}"] + + + + + @context.Item.PublishDate.ToShortDateString() + + + + + + @context.Item.CreationTime.ToLongDateString() + + + + + + + + + + @L["NewBook"] + + + + + + + @foreach (BookType bookTypeValue in Enum.GetValues(typeof(BookType))) + { + @L[$"Enum:BookType.{(int)bookTypeValue}"] + } + + + + + + + + @L["Cancel"] + @L["Save"] + + + + + + @EditingEntity.Name + + + + + + + @foreach (BookType bookTypeValue in Enum.GetValues(typeof(BookType))) + { + @L[$"Enum:BookType.{(int)bookTypeValue}"] + } + + + + + + + + @L["Cancel"] + @L["Save"] + + + +@code +{ + public Books() // Constructor + { + LocalizationResource = typeof(BookStoreResource); + } +} +```` + +{{end}} + +{{end}} + diff --git a/docs/en/tutorials/book-store/part-09.md b/docs/en/tutorials/book-store/part-09.md index b70fd3e875..22c144dd14 100644 --- a/docs/en/tutorials/book-store/part-09.md +++ b/docs/en/tutorials/book-store/part-09.md @@ -10,7 +10,8 @@ //[doc-params] { "UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG", "MAUIBlazor"], - "DB": ["EF","Mongo"] + "DB": ["EF","Mongo"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -837,6 +838,8 @@ That's all! This is a fully working CRUD page, you can create, edit and delete a Create a new Razor Component Page, `/Pages/Authors.razor`, in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor`{{ else if UI == "MAUIBlazor" }}`Acme.BookStore.MauiBlazor`{{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project with the following content: +{{if BlazorUI == "Blazorise"}} + ````xml @page "/authors" @using Acme.BookStore.Authors @@ -1017,11 +1020,133 @@ Create a new Razor Component Page, `/Pages/Authors.razor`, in the {{ if UI == "B ```` -* This code is similar to the `Books.razor`, except it doesn't inherit from the `AbpCrudPageBase`, but uses its own implementation. +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````razor +@page "/authors" +@using Acme.BookStore.Authors +@using Acme.BookStore.Localization +@inherits BookStoreComponentBase +@inject IAuthorAppService AuthorAppService + + + + + @L["Authors"] + + + @if (CanCreateAuthor) + { + + @L["NewAuthor"] + + } + + + + + + + + + @if (CanEditAuthor) + { + + @L["Edit"] + + } + @if (CanDeleteAuthor) + { + + @L["Delete"] + + } + + + + + + + @context.Item.BirthDate.ToShortDateString() + + + + + + + + + + @L["NewAuthor"] + + + + + + + + + + + + @L["Cancel"] + + @L["Save"] + + + + + + + @EditingAuthor.Name + + + + + + + + + + + + @L["Cancel"] + + @L["Save"] + + + +```` + +{{end}} + +* This code is similar to the `Books.razor`, except it doesn't inherit from the `AbpCrudPageBase`/`AbpMudCrudPageBase`, but uses its own implementation. * Injects the `IAuthorAppService` to consume the server side HTTP APIs from the UI. We can directly inject application service interfaces and use just like regular method calls by the help of [Dynamic C# HTTP API Client Proxy System](../../framework/api-development/dynamic-csharp-clients.md), which performs REST API calls for us. See the `Authors` class below to see the usage. Create a new code behind file, `Authors.razor.cs`, under the `Pages` folder, with the following content: +{{if BlazorUI == "Blazorise"}} + ````csharp using System; using System.Collections.Generic; @@ -1195,6 +1320,197 @@ public partial class Authors } ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````csharp +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Acme.BookStore.Authors; +using Acme.BookStore.Permissions; +using Microsoft.AspNetCore.Authorization; +using MudBlazor; +using Volo.Abp.Application.Dtos; + +{{ if UI == "BlazorServer" }}namespace Acme.BookStore.Blazor.Pages;{{ else if UI == "MAUIBlazor" }}namespace Acme.BookStore.MauiBlazor.Pages;{{ else }}namespace Acme.BookStore.Blazor.Client.Pages;{{ end }} + +public partial class Authors +{ + private IReadOnlyList AuthorList { get; set; } + + private int PageSize { get; } = LimitedResultRequestDto.DefaultMaxResultCount; + private int CurrentPage { get; set; } + private string CurrentSorting { get; set; } + private int TotalCount { get; set; } + + private bool CanCreateAuthor { get; set; } + private bool CanEditAuthor { get; set; } + private bool CanDeleteAuthor { get; set; } + + private CreateAuthorDto NewAuthor { get; set; } + + private Guid EditingAuthorId { get; set; } + private UpdateAuthorDto EditingAuthor { get; set; } + + private MudDialog CreateAuthorDialog { get; set; } + private MudDialog EditAuthorDialog { get; set; } + + private MudForm CreateFormRef; + private MudForm EditFormRef; + + // MudDatePicker requires nullable DateTime, while AuthorDto.BirthDate is non-nullable. + // Bind to a nullable wrapper and sync back to the DTO before saving. + private DateTime? NewAuthorBirthDate + { + get => NewAuthor?.BirthDate; + set { if (NewAuthor != null && value.HasValue) NewAuthor.BirthDate = value.Value; } + } + + private DateTime? EditingAuthorBirthDate + { + get => EditingAuthor?.BirthDate; + set { if (EditingAuthor != null && value.HasValue) EditingAuthor.BirthDate = value.Value; } + } + + public Authors() + { + NewAuthor = new CreateAuthorDto(); + EditingAuthor = new UpdateAuthorDto(); + } + + protected override async Task OnInitializedAsync() + { + await SetPermissionsAsync(); + await GetAuthorsAsync(); + } + + private async Task SetPermissionsAsync() + { + CanCreateAuthor = await AuthorizationService + .IsGrantedAsync(BookStorePermissions.Authors.Create); + + CanEditAuthor = await AuthorizationService + .IsGrantedAsync(BookStorePermissions.Authors.Edit); + + CanDeleteAuthor = await AuthorizationService + .IsGrantedAsync(BookStorePermissions.Authors.Delete); + } + + private async Task GetAuthorsAsync() + { + var result = await AuthorAppService.GetListAsync( + new GetAuthorListDto + { + MaxResultCount = PageSize, + SkipCount = CurrentPage * PageSize, + Sorting = CurrentSorting + } + ); + + AuthorList = result.Items; + TotalCount = (int)result.TotalCount; + } + + private async Task> OnDataGridReadAsync(GridState state) + { + CurrentSorting = state.SortDefinitions + .Where(s => !string.IsNullOrWhiteSpace(s.SortBy?.ToString())) + .Select(s => $"{s.SortBy}{(s.Descending ? " DESC" : "")}") + .JoinAsString(","); + CurrentPage = state.Page; + + await GetAuthorsAsync(); + + return new GridData { Items = AuthorList, TotalItems = TotalCount }; + } + + private async Task OpenCreateAuthorDialogAsync() + { + NewAuthor = new CreateAuthorDto(); + if (CreateFormRef != null) await CreateFormRef.ResetAsync(); + await InvokeAsync(() => CreateAuthorDialog.ShowAsync()); + } + + private Task CloseCreateAuthorDialogAsync() + { + return InvokeAsync(() => CreateAuthorDialog.CloseAsync()); + } + + private async Task OpenEditAuthorDialogAsync(AuthorDto author) + { + EditingAuthorId = author.Id; + EditingAuthor = ObjectMapper.Map(author); + if (EditFormRef != null) await EditFormRef.ResetAsync(); + await InvokeAsync(() => EditAuthorDialog.ShowAsync()); + } + + private Task CloseEditAuthorDialogAsync() + { + return InvokeAsync(() => EditAuthorDialog.CloseAsync()); + } + + private async Task DeleteAuthorAsync(AuthorDto author) + { + try + { + var confirmMessage = L["AuthorDeletionConfirmationMessage", author.Name]; + if (!await Message.Confirm(confirmMessage)) + { + return; + } + + await AuthorAppService.DeleteAsync(author.Id); + await GetAuthorsAsync(); + } + catch(Exception ex) + { + await HandleErrorAsync(ex); + } + } + + private async Task CreateAuthorAsync() + { + try + { + await CreateFormRef.Validate(); + if (CreateFormRef.IsValid) + { + await AuthorAppService.CreateAsync(NewAuthor); + await GetAuthorsAsync(); + await InvokeAsync(() => CreateAuthorDialog.CloseAsync()); + } + } + catch(Exception ex) + { + await HandleErrorAsync(ex); + } + } + + private async Task UpdateAuthorAsync() + { + try + { + await EditFormRef.Validate(); + if (EditFormRef.IsValid) + { + await AuthorAppService.UpdateAsync(EditingAuthorId, EditingAuthor); + await GetAuthorsAsync(); + await InvokeAsync(() => EditAuthorDialog.CloseAsync()); + } + } + catch(Exception ex) + { + await HandleErrorAsync(ex); + } + } +} +```` + +{{end}} + This class typically defines the properties and methods used by the `Authors.razor` page. ### Object Mapping diff --git a/docs/en/tutorials/book-store/part-10.md b/docs/en/tutorials/book-store/part-10.md index e03ada5a96..f8ff85e5d5 100644 --- a/docs/en/tutorials/book-store/part-10.md +++ b/docs/en/tutorials/book-store/part-10.md @@ -10,7 +10,8 @@ //[doc-params] { "UI": ["MVC","Blazor","BlazorServer","BlazorWebApp","NG", "MAUIBlazor"], - "DB": ["EF","Mongo"] + "DB": ["EF","Mongo"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -1115,7 +1116,9 @@ That's all. Just run the application and try to create or edit an author. ### The Book List -It is very easy to show the *Author Name* in the book list. Open the `/Pages/Books.razor` file in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor` {{ else if UI == "MAUIBlazor" }}`Acme.BookStore.MauiBlazor` {{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project and add the following `DataGridColumn` definition just after the `Name` (book name) column: +It is very easy to show the *Author Name* in the book list. Open the `/Pages/Books.razor` file in the {{ if UI == "BlazorServer" }}`Acme.BookStore.Blazor` {{ else if UI == "MAUIBlazor" }}`Acme.BookStore.MauiBlazor` {{ else }}`Acme.BookStore.Blazor.Client`{{ end }} project and add the following column definition just after the `Name` (book name) column: + +{{if BlazorUI == "Blazorise"}} ````xml ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````razor + +```` + +{{end}} + When you run the application, you can see the *Author* column on the table: ![blazor-bookstore-book-list-with-authors](images/blazor-bookstore-book-list-with-authors-2.png) @@ -1147,6 +1160,8 @@ protected override async Task OnInitializedAsync() * It is essential to call the `base.OnInitializedAsync()` since `AbpCrudPageBase` has some initialization code to be executed. +{{if BlazorUI == "Blazorise"}} + Override the `OpenCreateModalAsync` method and adding the following code: ````csharp @@ -1199,7 +1214,67 @@ The final `@code` block should be the following: } ```` -Finally, add the following `Field` definition into the `ModalBody` of the *Create* modal, as the first item, before the `Name` field: +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +Override the `OpenCreateDialogAsync` method and adding the following code: + +````csharp +protected override async Task OpenCreateDialogAsync() +{ + if (!authorList.Any()) + { + throw new UserFriendlyException(message: L["AnAuthorIsRequiredForCreatingBook"]); + } + + await base.OpenCreateDialogAsync(); + NewEntity.AuthorId = authorList.First().Id; +} +```` + +The final `@code` block should be the following: + +````csharp +@code +{ + //ADDED A NEW FIELD + IReadOnlyList authorList = Array.Empty(); + + public Books() // Constructor + { + LocalizationResource = typeof(BookStoreResource); + + CreatePolicyName = BookStorePermissions.Books.Create; + UpdatePolicyName = BookStorePermissions.Books.Edit; + DeletePolicyName = BookStorePermissions.Books.Delete; + } + + //GET AUTHORS ON INITIALIZATION + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + authorList = (await AppService.GetAuthorLookupAsync()).Items; + } + + protected override async Task OpenCreateDialogAsync() + { + if (!authorList.Any()) + { + throw new UserFriendlyException(message: L["AnAuthorIsRequiredForCreatingBook"]); + } + + await base.OpenCreateDialogAsync(); + NewEntity.AuthorId = authorList.First().Id; + } +} +```` + +{{end}} + +Finally, add the following field definition into the *Create* modal/dialog, as the first item, before the `Name` field: + +{{if BlazorUI == "Blazorise"}} ````xml @@ -1215,6 +1290,21 @@ Finally, add the following `Field` definition into the `ModalBody` of the *Creat ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````razor + + @foreach (var author in authorList) + { + @author.Name + } + +```` + +{{end}} + This requires to add a new localization key to the `en.json` file: ````js @@ -1227,7 +1317,9 @@ You can run the application to see the *Author Selection* while creating a new b ### Edit Book Modal -Add the following `Field` definition into the `ModalBody` of the *Edit* modal, as the first item, before the `Name` field: +Add the following field definition into the *Edit* modal/dialog, as the first item, before the `Name` field: + +{{if BlazorUI == "Blazorise"}} ````xml @@ -1243,6 +1335,21 @@ Add the following `Field` definition into the `ModalBody` of the *Edit* modal, a ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````razor + + @foreach (var author in authorList) + { + @author.Name + } + +```` + +{{end}} + That's all. We are reusing the `authorList` defined for the *Create* modal. {{end}} diff --git a/docs/en/tutorials/modular-crm/part-03.md b/docs/en/tutorials/modular-crm/part-03.md index e0db36d617..d34861a7ab 100644 --- a/docs/en/tutorials/modular-crm/part-03.md +++ b/docs/en/tutorials/modular-crm/part-03.md @@ -10,7 +10,8 @@ ````json //[doc-params] { - "UI": ["MVC", "BlazorWebApp", "NG"] + "UI": ["MVC", "BlazorWebApp", "NG"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -515,6 +516,8 @@ Open the `ModularCrm.Catalog` .NET solution in your IDE, and find the `Pages/Cat Replace the `Index.razor` file with the following content: +{{if BlazorUI == "Blazorise"}} + ````razor @page "/catalog" @using System.Collections.Generic @@ -547,6 +550,44 @@ Replace the `Index.razor` file with the following content: } ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````razor +@page "/catalog" +@using System.Collections.Generic +@using System.Threading.Tasks +@using ModularCrm.Catalog +@inject IProductAppService ProductAppService + +Products + + + + + @foreach (var product in Products) + { + + @product.Name (stock: @product.StockCount) + + } + + + + +@code { + private List Products { get; set; } = new(); + + protected override async Task OnInitializedAsync() + { + Products = await ProductAppService.GetListAsync(); + } +} +```` + +{{end}} + Here, you inject `IProductAppService`, get all products in `OnInitializedAsync`, and then render the result in a simple list. {{end}} diff --git a/docs/en/tutorials/modular-crm/part-05.md b/docs/en/tutorials/modular-crm/part-05.md index 4387ff6908..4f5b4fc2b5 100644 --- a/docs/en/tutorials/modular-crm/part-05.md +++ b/docs/en/tutorials/modular-crm/part-05.md @@ -10,7 +10,8 @@ ````json //[doc-params] { - "UI": ["MVC", "BlazorWebApp", "NG"] + "UI": ["MVC", "BlazorWebApp", "NG"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -525,6 +526,8 @@ public class OrderingMenuContributor : IMenuContributor Replace the `Index.razor` content in the `Pages/Ordering` folder of the `ModularCrm.Ordering.Blazor` project with the following code block: +{{if BlazorUI == "Blazorise"}} + ````razor @page "/ordering" @using System.Collections.Generic @@ -559,6 +562,46 @@ Replace the `Index.razor` content in the `Pages/Ordering` folder of the `Modular } ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````razor +@page "/ordering" +@using System.Collections.Generic +@using System.Threading.Tasks +@using ModularCrm.Ordering +@inject IOrderAppService OrderAppService + +Orders + + + + + @foreach (var order in Orders) + { + + Customer: @order.CustomerName
+ Product: @order.ProductId
+ State: @order.State +
+ } +
+
+
+ +@code { + private List Orders { get; set; } = new(); + + protected override async Task OnInitializedAsync() + { + Orders = await OrderAppService.GetListAsync(); + } +} +```` + +{{end}} + This page shows a list of orders on the UI. You haven't created a UI to create new orders, and we will not do it to keep this tutorial simple. If you want to learn how to create advanced UIs with ABP, please follow the [Book Store tutorial](../book-store/index.md). ### Editing the Menu Item diff --git a/docs/en/tutorials/modular-crm/part-06.md b/docs/en/tutorials/modular-crm/part-06.md index 31cc0cb48c..1065e5df6e 100644 --- a/docs/en/tutorials/modular-crm/part-06.md +++ b/docs/en/tutorials/modular-crm/part-06.md @@ -10,7 +10,8 @@ ````json //[doc-params] { - "UI": ["MVC", "BlazorWebApp", "NG"] + "UI": ["MVC", "BlazorWebApp", "NG"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -296,6 +297,8 @@ As you can see, we can see the product names instead of product IDs. Open the `Index.razor` file, and change the `@order.ProductId` part to `@order.ProductName` to write the product name instead of the product ID. The final `Index.razor` content should be the following: +{{if BlazorUI == "Blazorise"}} + ````razor @page "/ordering" @using System.Collections.Generic @@ -330,6 +333,46 @@ Open the `Index.razor` file, and change the `@order.ProductId` part to `@order.P } ```` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +````razor +@page "/ordering" +@using System.Collections.Generic +@using System.Threading.Tasks +@using ModularCrm.Ordering +@inject IOrderAppService OrderAppService + +Orders + + + + + @foreach (var order in Orders) + { + + Customer: @order.CustomerName
+ Product: @order.ProductName
+ State: @order.State +
+ } +
+
+
+ +@code { + private List Orders { get; set; } = new(); + + protected override async Task OnInitializedAsync() + { + Orders = await OrderAppService.GetListAsync(); + } +} +```` + +{{end}} + That's all. Now, you can graph build the main application and run it in ABP Studio to see the result: ![abp-studio-browser-list-of-orders-with-product-name](images/abp-studio-browser-list-of-orders-with-product-name.png) diff --git a/docs/en/tutorials/modular-crm/part-08.md b/docs/en/tutorials/modular-crm/part-08.md index f2739a53bb..2d911f93ec 100644 --- a/docs/en/tutorials/modular-crm/part-08.md +++ b/docs/en/tutorials/modular-crm/part-08.md @@ -176,8 +176,20 @@ Now, you know the fundamental principles and mechanics of building sophisticated ## Download the Source Code +{{if UI == "MVC"}} + +You can download the completed sample solution [here](https://github.com/abpframework/abp-samples/tree/master/ModularCRM). + +{{else if UI == "BlazorWebApp"}} + You can download the completed sample solution [here](https://github.com/abpframework/abp-samples/tree/master/ModularCRM-BlazorWebApp). +{{else if UI == "NG"}} + +You can download the completed sample solution [here](https://github.com/abpframework/abp-samples/tree/master/NG.ModularCRM). + +{{end}} + ## See Also See the following sections for additional resources. diff --git a/docs/en/tutorials/todo/layered/index.md b/docs/en/tutorials/todo/layered/index.md index c8d95b627c..f0616c7765 100644 --- a/docs/en/tutorials/todo/layered/index.md +++ b/docs/en/tutorials/todo/layered/index.md @@ -11,7 +11,8 @@ //[doc-params] { "UI": ["MVC", "Blazor", "BlazorServer", "BlazorWebApp" ,"NG", "MAUIBlazor"], - "DB": ["EF", "Mongo"] + "DB": ["EF", "Mongo"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -637,6 +638,8 @@ See the *Dynamic C# Proxies & Auto API Controllers* section below to learn how w Open the `Index.razor` file in the `Pages` folder of the {{if UI=="Blazor" || UI=="BlazorWebApp"}} *TodoApp.Blazor.Client* {{else if UI=="BlazorServer"}} *TodoApp.Blazor* {{else if UI=="MAUIBlazor"}} *TodoApp.MauiBlazor* {{end}} project and replace the content with the following code block: +{{if BlazorUI == "Blazorise"}} + ```xml @page "/" @inherits TodoAppComponentBase @@ -675,6 +678,47 @@ Open the `Index.razor` file in the `Pages` folder of the {{if UI=="Blazor" || UI ``` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +```razor +@page "/" +@inherits TodoAppComponentBase + + + + + + TODO LIST + + + + + + + Submit + + + + @foreach (var todoItem in TodoItems) + { + + + @todoItem.Text + + } + + + + +``` + +{{end}} + ### Index.razor.css As the final touch, open the `Index.razor.css` file in the `Pages` folder of the {{if UI=="Blazor" || UI=="BlazorWebApp"}}*TodoApp.Blazor.Client*{{else if UI=="BlazorServer"}} *TodoApp.Blazor* {{else if UI=="MAUIBlazor"}} *TodoApp.MauiBlazor* {{end}} project and add the following content: diff --git a/docs/en/tutorials/todo/single-layer/index.md b/docs/en/tutorials/todo/single-layer/index.md index 6a7f45ce62..b62abfb2b9 100644 --- a/docs/en/tutorials/todo/single-layer/index.md +++ b/docs/en/tutorials/todo/single-layer/index.md @@ -11,7 +11,8 @@ //[doc-params] { "UI": ["MVC", "Blazor", "BlazorServer", "NG"], - "DB": ["EF", "Mongo"] + "DB": ["EF", "Mongo"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -621,6 +622,8 @@ This class uses the {{if UI=="Blazor"}}`ITodoAppService`{{else}}`TodoAppService` Open the `Index.razor` file in the {{if UI=="BlazorServer"}}`Components/Pages`{{else}}`Pages`{{end}} folder and replace the content with the following code block: +{{if BlazorUI == "Blazorise"}} + ```xml @page "/" @inherits TodoAppComponentBase @@ -660,6 +663,47 @@ Open the `Index.razor` file in the {{if UI=="BlazorServer"}}`Components/Pages`{{ ``` +{{end}} + +{{if BlazorUI == "MudBlazor"}} + +```razor +@page "/" +@inherits TodoAppComponentBase + + + + + + TODO LIST + + + + + + + Submit + + + + @foreach (var todoItem in TodoItems) + { + + + @todoItem.Text + + } + + + + +``` + +{{end}} + ### Index.razor.css As the final touch, open the `Index.razor.css` file in the {{if UI=="BlazorServer"}}`Components/Pages`{{else}}`Pages`{{end}} folder and add the following code block at the end of the file: diff --git a/docs/en/ui-themes/basic-theme/index.md b/docs/en/ui-themes/basic-theme/index.md index c87491d1b4..71ee739721 100644 --- a/docs/en/ui-themes/basic-theme/index.md +++ b/docs/en/ui-themes/basic-theme/index.md @@ -19,6 +19,8 @@ See the [Theming document](../../framework/ui/mvc-razor-pages/theming.md) to lea The Basic Theme has implementation for the following UI types: - [MVC UI](../../framework/ui/mvc-razor-pages/basic-theme.md) -- [Blazor UI](../../framework/ui/blazor/basic-theme.md) +- [Blazor UI](../../framework/ui/blazor/basic-theme.md) — available in two Blazor UI library variants: + - **Blazorise** (default): `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.BasicTheme` + - **MudBlazor**: `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorBasicTheme` - [Angular UI](../../framework/ui/angular/basic-theme.md) diff --git a/docs/en/ui-themes/index.md b/docs/en/ui-themes/index.md index 2beaa0dc05..36df6270ae 100644 --- a/docs/en/ui-themes/index.md +++ b/docs/en/ui-themes/index.md @@ -33,6 +33,15 @@ See the following documents based on the UI type you are using: - [Basic Theme - Blazor UI](../framework/ui/blazor/basic-theme.md) - [Basic Theme - Angular UI](../framework/ui/angular/basic-theme.md) +## Blazor UI Library + +When you create an ABP solution with a Blazor host (Blazor Server, Blazor WebAssembly or Blazor WebApp), you can additionally choose the underlying Blazor component library: + +* **Blazorise** — the original ABP default, based on Bootstrap. +* **MudBlazor** — a Material-Design component library, available as an alternative variant. Each official theme has a MudBlazor version (e.g. `MudBlazorLeptonXTheme`, `MudBlazorLeptonXLiteTheme`, `MudBlazorBasicTheme`). + +The choice is made at solution creation time via the `--blazor-ui-library` option (`abp new ... -bul mudblazor`) or in the ABP Studio new-solution wizard. + ## See Also * [Theming - MVC UI](../framework/ui/mvc-razor-pages/theming.md) diff --git a/docs/en/ui-themes/lepton-x-lite/blazor.md b/docs/en/ui-themes/lepton-x-lite/blazor.md index 7190ac1fc2..6b15e1f134 100644 --- a/docs/en/ui-themes/lepton-x-lite/blazor.md +++ b/docs/en/ui-themes/lepton-x-lite/blazor.md @@ -10,7 +10,8 @@ ````json //[doc-params] { - "UI": ["Blazor", "BlazorServer"] + "UI": ["Blazor", "BlazorServer"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` @@ -20,6 +21,19 @@ LeptonX Lite has implementation for the ABP Blazor WebAssembly & Blazor Server. > See the [Theming document](../../framework/ui/mvc-razor-pages/theming.md) to learn about themes. +{{if BlazorUI == "MudBlazor"}} + +> **MudBlazor Variant** — When the `--blazor-ui-library mudblazor` option is used, the LeptonX Lite theme ships as a MudBlazor variant. Replace `LeptonXLiteTheme` with `MudBlazorLeptonXLiteTheme` everywhere in this document (package names, module type names and namespaces). The installation steps, layout customization API and override mechanism are the same; only the package and namespace prefix change. The component implementations use MudBlazor primitives (`MudAppBar`, `MudDrawer`, `MudNavLink`, etc.) instead of Blazorise components. +> +> Concrete package names you will see when using the MudBlazor variant: +> +> * `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorLeptonXLiteTheme` +> * `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorLeptonXLiteTheme.Bundling` +> * Module types: `Abp{...}MudBlazorLeptonXLiteThemeModule`, `Abp{...}MudBlazorLeptonXLiteThemeBundlingModule` +> * Layout namespace: `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorLeptonXLiteTheme.Themes.MudBlazorLeptonXLite` + +{{end}} + ## Installation This theme is **already installed** when you create a new solution using the startup templates. If you are using any other template, you can install this theme by following the steps below: diff --git a/docs/en/ui-themes/lepton-x/blazor.md b/docs/en/ui-themes/lepton-x/blazor.md index 29e8deb6c3..07d07eea00 100644 --- a/docs/en/ui-themes/lepton-x/blazor.md +++ b/docs/en/ui-themes/lepton-x/blazor.md @@ -10,12 +10,26 @@ ````json //[doc-params] { - "UI": ["Blazor", "BlazorServer"] + "UI": ["Blazor", "BlazorServer"], + "BlazorUI": ["Blazorise", "MudBlazor"] } ```` LeptonX theme is implemented and ready to use with ABP. No custom implementation is needed for Blazor Server & WebAssembly. +{{if BlazorUI == "MudBlazor"}} + +> **MudBlazor Variant** — When the `--blazor-ui-library mudblazor` option is used, the LeptonX theme ships as a MudBlazor variant. Replace `LeptonXTheme` with `MudBlazorLeptonXTheme` everywhere in this document (package names, module type names and namespaces). The installation steps, layout customization API and override mechanism are the same; only the package and namespace prefix change. Component implementations use MudBlazor primitives (`MudAppBar`, `MudDrawer`, `MudNavLink`, `MudMenu`, etc.) instead of Blazorise components. +> +> Concrete package names you will see when using the MudBlazor variant: +> +> * `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorLeptonXTheme` +> * `Volo.Abp.AspNetCore.Components.{Server,WebAssembly}.MudBlazorLeptonXTheme.Bundling` +> * Module types: `Abp{...}MudBlazorLeptonXThemeModule`, `Abp{...}MudBlazorLeptonXThemeBundlingModule` +> * Layout components live under `Volo.Abp.AspNetCore.Components.{Web,Server,WebAssembly}.MudBlazorLeptonXTheme.Components.ApplicationLayout` (with `SideMenu`, `TopMenu`, and `PublicWebsiteLayout` sub-namespaces). + +{{end}} + ## Installation {{if UI == "Blazor"}} diff --git a/framework/Volo.Abp.abpmdl b/framework/Volo.Abp.abpmdl index afc52ca801..0935fedade 100644 --- a/framework/Volo.Abp.abpmdl +++ b/framework/Volo.Abp.abpmdl @@ -478,6 +478,10 @@ "path": "test/Volo.Abp.ObjectMapping.Tests/Volo.Abp.ObjectMapping.Tests.abppkg", "folder": "test" }, + "Volo.Abp.RabbitMQ.Tests": { + "path": "test/Volo.Abp.RabbitMQ.Tests/Volo.Abp.RabbitMQ.Tests.abppkg", + "folder": "test" + }, "Volo.Abp.Ddd.Application.Contracts": { "path": "src/Volo.Abp.Ddd.Application.Contracts/Volo.Abp.Ddd.Application.Contracts.abppkg", "folder": "src" diff --git a/framework/Volo.Abp.slnx b/framework/Volo.Abp.slnx index 26d462fb4f..5f4adbd622 100644 --- a/framework/Volo.Abp.slnx +++ b/framework/Volo.Abp.slnx @@ -10,13 +10,19 @@ + + + + + + @@ -137,6 +143,7 @@ + @@ -180,6 +187,7 @@ + @@ -240,6 +248,7 @@ + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/AbpAspNetCoreComponentsMauiBlazorThemingMudBlazorBundlingModule.cs b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/AbpAspNetCoreComponentsMauiBlazorThemingMudBlazorBundlingModule.cs new file mode 100644 index 0000000000..f803cd8985 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/AbpAspNetCoreComponentsMauiBlazorThemingMudBlazorBundlingModule.cs @@ -0,0 +1,34 @@ +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling; + +[DependsOn( + typeof(AbpAspNetCoreMvcUiBundlingAbstractionsModule) +)] +public class AbpAspNetCoreComponentsMauiBlazorThemingMudBlazorBundlingModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.GlobalAssets.Enabled = true; + options.GlobalAssets.GlobalStyleBundleName = MauiBlazorMudBlazorStandardBundles.Styles.Global; + options.GlobalAssets.GlobalScriptBundleName = MauiBlazorMudBlazorStandardBundles.Scripts.Global; + + options + .StyleBundles + .Add(MauiBlazorMudBlazorStandardBundles.Styles.Global, bundle => + { + bundle.AddContributors(typeof(MauiBlazorMudBlazorStyleContributor)); + }); + + options + .ScriptBundles + .Add(MauiBlazorMudBlazorStandardBundles.Scripts.Global, bundle => + { + bundle.AddContributors(typeof(MauiBlazorMudBlazorScriptContributor)); + }); + }); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/FodyWeavers.xml b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/FodyWeavers.xml new file mode 100644 index 0000000000..7e9f94ead6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/FodyWeavers.xsd b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/FodyWeavers.xsd new file mode 100644 index 0000000000..e6bb89a4a1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/MauiBlazorMudBlazorScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/MauiBlazorMudBlazorScriptContributor.cs new file mode 100644 index 0000000000..73bb95c5bd --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/MauiBlazorMudBlazorScriptContributor.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling; + +public class MauiBlazorMudBlazorScriptContributor : BundleContributor +{ + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("_content/MudBlazor/MudBlazor.min.js"); + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/abp.js"); + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/lang-utils.js"); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/MauiBlazorMudBlazorStandardBundles.cs b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/MauiBlazorMudBlazorStandardBundles.cs new file mode 100644 index 0000000000..54a460d0c4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/MauiBlazorMudBlazorStandardBundles.cs @@ -0,0 +1,14 @@ +namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling; + +public class MauiBlazorMudBlazorStandardBundles +{ + public static class Styles + { + public static string Global = "MauiBlazorMudBlazor.Global"; + } + + public static class Scripts + { + public static string Global = "MauiBlazorMudBlazor.Global"; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/MauiBlazorMudBlazorStyleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/MauiBlazorMudBlazorStyleContributor.cs new file mode 100644 index 0000000000..c78f1474d2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/MauiBlazorMudBlazorStyleContributor.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling; + +public class MauiBlazorMudBlazorStyleContributor : BundleContributor +{ + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor/libs/fontawesome/css/all.css"); + context.Files.AddIfNotContains("_content/MudBlazor/MudBlazor.min.css"); + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/css/abp.css"); + context.Files.AddIfNotContains("_content/Volo.Abp.MudBlazorUI/volo.abp.mudblazorui.css"); + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor/libs/flag-icon/css/flag-icon.css"); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling.csproj b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling.csproj new file mode 100644 index 0000000000..b33c5a2adf --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling.csproj @@ -0,0 +1,16 @@ + + + + + + + net10.0 + enable + Nullable + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor/AbpAspNetCoreComponentsMauiBlazorThemingMudBlazorModule.cs b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor/AbpAspNetCoreComponentsMauiBlazorThemingMudBlazorModule.cs new file mode 100644 index 0000000000..48cc5a794f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor/AbpAspNetCoreComponentsMauiBlazorThemingMudBlazorModule.cs @@ -0,0 +1,15 @@ +using Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor; +using Volo.Abp.AspNetCore.Components.MauiBlazor; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor; + +[DependsOn( + typeof(AbpAspNetCoreComponentsMauiBlazorThemingMudBlazorBundlingModule), + typeof(AbpAspNetCoreComponentsWebThemingMudBlazorModule), + typeof(AbpAspNetCoreComponentsMauiBlazorModule) +)] +public class AbpAspNetCoreComponentsMauiBlazorThemingMudBlazorModule : AbpModule +{ +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor/FodyWeavers.xml b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor/FodyWeavers.xml new file mode 100644 index 0000000000..7e9f94ead6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor/FodyWeavers.xsd b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor/FodyWeavers.xsd new file mode 100644 index 0000000000..ffa6fc4b78 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.csproj b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.csproj new file mode 100644 index 0000000000..5a24de82c0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.csproj @@ -0,0 +1,18 @@ + + + + + + + net10.0 + enable + Nullable + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/AbpAspNetCoreComponentsServerThemingMudBlazorModule.cs b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/AbpAspNetCoreComponentsServerThemingMudBlazorModule.cs new file mode 100644 index 0000000000..7d7a784256 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/AbpAspNetCoreComponentsServerThemingMudBlazorModule.cs @@ -0,0 +1,36 @@ +using Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor.Bundling; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.Modularity; +using Volo.Abp.AspNetCore.Mvc.UI.Packages; + +namespace Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor; + +[DependsOn( + typeof(AbpAspNetCoreComponentsServerModule), + typeof(AbpAspNetCoreMvcUiPackagesModule), + typeof(AbpAspNetCoreComponentsWebThemingMudBlazorModule), + typeof(AbpAspNetCoreMvcUiBundlingModule) + )] +public class AbpAspNetCoreComponentsServerThemingMudBlazorModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options + .StyleBundles + .Add(BlazorServerMudBlazorStandardBundles.Styles.Global, bundle => + { + bundle.AddContributors(typeof(BlazorServerMudBlazorStyleContributor)); + }); + + options + .ScriptBundles + .Add(BlazorServerMudBlazorStandardBundles.Scripts.Global, bundle => + { + bundle.AddContributors(typeof(BlazorServerMudBlazorScriptContributor)); + }); + }); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/Bundling/BlazorServerMudBlazorComponentBundleManager.cs b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/Bundling/BlazorServerMudBlazorComponentBundleManager.cs new file mode 100644 index 0000000000..09a67ef6fa --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/Bundling/BlazorServerMudBlazorComponentBundleManager.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Bundling; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Bundling; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor.Bundling; + +public class BlazorServerMudBlazorComponentBundleManager : IComponentBundleManager, ITransientDependency +{ + protected IBundleManager BundleManager { get; } + + public BlazorServerMudBlazorComponentBundleManager(IBundleManager bundleManager) + { + BundleManager = bundleManager; + } + + public virtual async Task> GetStyleBundleFilesAsync(string bundleName) + { + return (await BundleManager.GetStyleBundleFilesAsync(bundleName)).Select(f => f.FileName).ToList(); + } + + public virtual async Task> GetScriptBundleFilesAsync(string bundleName) + { + return (await BundleManager.GetScriptBundleFilesAsync(bundleName)).Select(f => f.FileName).ToList(); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/Bundling/BlazorServerMudBlazorScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/Bundling/BlazorServerMudBlazorScriptContributor.cs new file mode 100644 index 0000000000..72a49486ee --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/Bundling/BlazorServerMudBlazorScriptContributor.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.Components.Web; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor.Bundling; + +public class BlazorServerMudBlazorScriptContributor : BundleContributor +{ + public override void ConfigureBundle(BundleConfigurationContext context) + { + var options = context.ServiceProvider.GetRequiredService>().Value; + if (!options.IsBlazorWebApp) + { + context.Files.AddIfNotContains("/_framework/blazor.server.js"); + } + context.Files.AddIfNotContains("/_content/MudBlazor/MudBlazor.min.js"); + context.Files.AddIfNotContains("/_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/abp.js"); + context.Files.AddIfNotContains("/_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/authentication-state-listener.js"); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/Bundling/BlazorServerMudBlazorStandardBundles.cs b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/Bundling/BlazorServerMudBlazorStandardBundles.cs new file mode 100644 index 0000000000..1b463f8871 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/Bundling/BlazorServerMudBlazorStandardBundles.cs @@ -0,0 +1,14 @@ +namespace Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor.Bundling; + +public class BlazorServerMudBlazorStandardBundles +{ + public static class Styles + { + public static string Global = "Blazor.Global"; + } + + public static class Scripts + { + public static string Global = "Blazor.Global"; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/Bundling/BlazorServerMudBlazorStyleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/Bundling/BlazorServerMudBlazorStyleContributor.cs new file mode 100644 index 0000000000..c2f76d35ed --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/Bundling/BlazorServerMudBlazorStyleContributor.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor.Bundling; + +public class BlazorServerMudBlazorStyleContributor : BundleContributor +{ + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("/_content/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/libs/fontawesome/css/all.css"); + context.Files.AddIfNotContains("/_content/MudBlazor/MudBlazor.min.css"); + context.Files.AddIfNotContains("/_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/css/abp.css"); + context.Files.AddIfNotContains("/_content/Volo.Abp.MudBlazorUI/volo.abp.mudblazorui.css"); + context.Files.AddIfNotContains("/_content/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/libs/flag-icon/css/flag-icon.css"); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/FodyWeavers.xml b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/FodyWeavers.xml new file mode 100644 index 0000000000..7e9f94ead6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/FodyWeavers.xsd b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/FodyWeavers.xsd new file mode 100644 index 0000000000..ffa6fc4b78 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor.abppkg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor.abppkg new file mode 100644 index 0000000000..e0c33eaab1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor.abppkg @@ -0,0 +1,3 @@ +{ + "role": "lib.framework" +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor.csproj b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor.csproj new file mode 100644 index 0000000000..b4464b5367 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor.csproj @@ -0,0 +1,20 @@ + + + + + + + net10.0 + enable + Nullable + true + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/css/flag-icon.css b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/css/flag-icon.css new file mode 100644 index 0000000000..b54375090b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/css/flag-icon.css @@ -0,0 +1,1568 @@ +.flag-icon-background { + background-size: contain; + background-position: 50%; + background-repeat: no-repeat; +} +.flag-icon { + background-size: contain; + background-position: 50%; + background-repeat: no-repeat; + position: relative; + display: inline-block; + width: 1.33333333em; + line-height: 1em; +} +.flag-icon:before { + content: "\00a0"; +} +.flag-icon.flag-icon-squared { + width: 1em; +} +.flag-icon-ad { + background-image: url(../flags/4x3/ad.svg); +} +.flag-icon-ad.flag-icon-squared { + background-image: url(../flags/1x1/ad.svg); +} +.flag-icon-ae { + background-image: url(../flags/4x3/ae.svg); +} +.flag-icon-ae.flag-icon-squared { + background-image: url(../flags/1x1/ae.svg); +} +.flag-icon-af { + background-image: url(../flags/4x3/af.svg); +} +.flag-icon-af.flag-icon-squared { + background-image: url(../flags/1x1/af.svg); +} +.flag-icon-ag { + background-image: url(../flags/4x3/ag.svg); +} +.flag-icon-ag.flag-icon-squared { + background-image: url(../flags/1x1/ag.svg); +} +.flag-icon-ai { + background-image: url(../flags/4x3/ai.svg); +} +.flag-icon-ai.flag-icon-squared { + background-image: url(../flags/1x1/ai.svg); +} +.flag-icon-al { + background-image: url(../flags/4x3/al.svg); +} +.flag-icon-al.flag-icon-squared { + background-image: url(../flags/1x1/al.svg); +} +.flag-icon-am { + background-image: url(../flags/4x3/am.svg); +} +.flag-icon-am.flag-icon-squared { + background-image: url(../flags/1x1/am.svg); +} +.flag-icon-ao { + background-image: url(../flags/4x3/ao.svg); +} +.flag-icon-ao.flag-icon-squared { + background-image: url(../flags/1x1/ao.svg); +} +.flag-icon-aq { + background-image: url(../flags/4x3/aq.svg); +} +.flag-icon-aq.flag-icon-squared { + background-image: url(../flags/1x1/aq.svg); +} +.flag-icon-ar { + background-image: url(../flags/4x3/ar.svg); +} +.flag-icon-ar.flag-icon-squared { + background-image: url(../flags/1x1/ar.svg); +} +.flag-icon-as { + background-image: url(../flags/4x3/as.svg); +} +.flag-icon-as.flag-icon-squared { + background-image: url(../flags/1x1/as.svg); +} +.flag-icon-at { + background-image: url(../flags/4x3/at.svg); +} +.flag-icon-at.flag-icon-squared { + background-image: url(../flags/1x1/at.svg); +} +.flag-icon-au { + background-image: url(../flags/4x3/au.svg); +} +.flag-icon-au.flag-icon-squared { + background-image: url(../flags/1x1/au.svg); +} +.flag-icon-aw { + background-image: url(../flags/4x3/aw.svg); +} +.flag-icon-aw.flag-icon-squared { + background-image: url(../flags/1x1/aw.svg); +} +.flag-icon-ax { + background-image: url(../flags/4x3/ax.svg); +} +.flag-icon-ax.flag-icon-squared { + background-image: url(../flags/1x1/ax.svg); +} +.flag-icon-az { + background-image: url(../flags/4x3/az.svg); +} +.flag-icon-az.flag-icon-squared { + background-image: url(../flags/1x1/az.svg); +} +.flag-icon-ba { + background-image: url(../flags/4x3/ba.svg); +} +.flag-icon-ba.flag-icon-squared { + background-image: url(../flags/1x1/ba.svg); +} +.flag-icon-bb { + background-image: url(../flags/4x3/bb.svg); +} +.flag-icon-bb.flag-icon-squared { + background-image: url(../flags/1x1/bb.svg); +} +.flag-icon-bd { + background-image: url(../flags/4x3/bd.svg); +} +.flag-icon-bd.flag-icon-squared { + background-image: url(../flags/1x1/bd.svg); +} +.flag-icon-be { + background-image: url(../flags/4x3/be.svg); +} +.flag-icon-be.flag-icon-squared { + background-image: url(../flags/1x1/be.svg); +} +.flag-icon-bf { + background-image: url(../flags/4x3/bf.svg); +} +.flag-icon-bf.flag-icon-squared { + background-image: url(../flags/1x1/bf.svg); +} +.flag-icon-bg { + background-image: url(../flags/4x3/bg.svg); +} +.flag-icon-bg.flag-icon-squared { + background-image: url(../flags/1x1/bg.svg); +} +.flag-icon-bh { + background-image: url(../flags/4x3/bh.svg); +} +.flag-icon-bh.flag-icon-squared { + background-image: url(../flags/1x1/bh.svg); +} +.flag-icon-bi { + background-image: url(../flags/4x3/bi.svg); +} +.flag-icon-bi.flag-icon-squared { + background-image: url(../flags/1x1/bi.svg); +} +.flag-icon-bj { + background-image: url(../flags/4x3/bj.svg); +} +.flag-icon-bj.flag-icon-squared { + background-image: url(../flags/1x1/bj.svg); +} +.flag-icon-bl { + background-image: url(../flags/4x3/bl.svg); +} +.flag-icon-bl.flag-icon-squared { + background-image: url(../flags/1x1/bl.svg); +} +.flag-icon-bm { + background-image: url(../flags/4x3/bm.svg); +} +.flag-icon-bm.flag-icon-squared { + background-image: url(../flags/1x1/bm.svg); +} +.flag-icon-bn { + background-image: url(../flags/4x3/bn.svg); +} +.flag-icon-bn.flag-icon-squared { + background-image: url(../flags/1x1/bn.svg); +} +.flag-icon-bo { + background-image: url(../flags/4x3/bo.svg); +} +.flag-icon-bo.flag-icon-squared { + background-image: url(../flags/1x1/bo.svg); +} +.flag-icon-bq { + background-image: url(../flags/4x3/bq.svg); +} +.flag-icon-bq.flag-icon-squared { + background-image: url(../flags/1x1/bq.svg); +} +.flag-icon-br { + background-image: url(../flags/4x3/br.svg); +} +.flag-icon-br.flag-icon-squared { + background-image: url(../flags/1x1/br.svg); +} +.flag-icon-bs { + background-image: url(../flags/4x3/bs.svg); +} +.flag-icon-bs.flag-icon-squared { + background-image: url(../flags/1x1/bs.svg); +} +.flag-icon-bt { + background-image: url(../flags/4x3/bt.svg); +} +.flag-icon-bt.flag-icon-squared { + background-image: url(../flags/1x1/bt.svg); +} +.flag-icon-bv { + background-image: url(../flags/4x3/bv.svg); +} +.flag-icon-bv.flag-icon-squared { + background-image: url(../flags/1x1/bv.svg); +} +.flag-icon-bw { + background-image: url(../flags/4x3/bw.svg); +} +.flag-icon-bw.flag-icon-squared { + background-image: url(../flags/1x1/bw.svg); +} +.flag-icon-by { + background-image: url(../flags/4x3/by.svg); +} +.flag-icon-by.flag-icon-squared { + background-image: url(../flags/1x1/by.svg); +} +.flag-icon-bz { + background-image: url(../flags/4x3/bz.svg); +} +.flag-icon-bz.flag-icon-squared { + background-image: url(../flags/1x1/bz.svg); +} +.flag-icon-ca { + background-image: url(../flags/4x3/ca.svg); +} +.flag-icon-ca.flag-icon-squared { + background-image: url(../flags/1x1/ca.svg); +} +.flag-icon-cc { + background-image: url(../flags/4x3/cc.svg); +} +.flag-icon-cc.flag-icon-squared { + background-image: url(../flags/1x1/cc.svg); +} +.flag-icon-cd { + background-image: url(../flags/4x3/cd.svg); +} +.flag-icon-cd.flag-icon-squared { + background-image: url(../flags/1x1/cd.svg); +} +.flag-icon-cf { + background-image: url(../flags/4x3/cf.svg); +} +.flag-icon-cf.flag-icon-squared { + background-image: url(../flags/1x1/cf.svg); +} +.flag-icon-cg { + background-image: url(../flags/4x3/cg.svg); +} +.flag-icon-cg.flag-icon-squared { + background-image: url(../flags/1x1/cg.svg); +} +.flag-icon-ch { + background-image: url(../flags/4x3/ch.svg); +} +.flag-icon-ch.flag-icon-squared { + background-image: url(../flags/1x1/ch.svg); +} +.flag-icon-ci { + background-image: url(../flags/4x3/ci.svg); +} +.flag-icon-ci.flag-icon-squared { + background-image: url(../flags/1x1/ci.svg); +} +.flag-icon-ck { + background-image: url(../flags/4x3/ck.svg); +} +.flag-icon-ck.flag-icon-squared { + background-image: url(../flags/1x1/ck.svg); +} +.flag-icon-cl { + background-image: url(../flags/4x3/cl.svg); +} +.flag-icon-cl.flag-icon-squared { + background-image: url(../flags/1x1/cl.svg); +} +.flag-icon-cm { + background-image: url(../flags/4x3/cm.svg); +} +.flag-icon-cm.flag-icon-squared { + background-image: url(../flags/1x1/cm.svg); +} +.flag-icon-cn { + background-image: url(../flags/4x3/cn.svg); +} +.flag-icon-cn.flag-icon-squared { + background-image: url(../flags/1x1/cn.svg); +} +.flag-icon-co { + background-image: url(../flags/4x3/co.svg); +} +.flag-icon-co.flag-icon-squared { + background-image: url(../flags/1x1/co.svg); +} +.flag-icon-cr { + background-image: url(../flags/4x3/cr.svg); +} +.flag-icon-cr.flag-icon-squared { + background-image: url(../flags/1x1/cr.svg); +} +.flag-icon-cu { + background-image: url(../flags/4x3/cu.svg); +} +.flag-icon-cu.flag-icon-squared { + background-image: url(../flags/1x1/cu.svg); +} +.flag-icon-cv { + background-image: url(../flags/4x3/cv.svg); +} +.flag-icon-cv.flag-icon-squared { + background-image: url(../flags/1x1/cv.svg); +} +.flag-icon-cw { + background-image: url(../flags/4x3/cw.svg); +} +.flag-icon-cw.flag-icon-squared { + background-image: url(../flags/1x1/cw.svg); +} +.flag-icon-cx { + background-image: url(../flags/4x3/cx.svg); +} +.flag-icon-cx.flag-icon-squared { + background-image: url(../flags/1x1/cx.svg); +} +.flag-icon-cy { + background-image: url(../flags/4x3/cy.svg); +} +.flag-icon-cy.flag-icon-squared { + background-image: url(../flags/1x1/cy.svg); +} +.flag-icon-cz { + background-image: url(../flags/4x3/cz.svg); +} +.flag-icon-cz.flag-icon-squared { + background-image: url(../flags/1x1/cz.svg); +} +.flag-icon-de { + background-image: url(../flags/4x3/de.svg); +} +.flag-icon-de.flag-icon-squared { + background-image: url(../flags/1x1/de.svg); +} +.flag-icon-dj { + background-image: url(../flags/4x3/dj.svg); +} +.flag-icon-dj.flag-icon-squared { + background-image: url(../flags/1x1/dj.svg); +} +.flag-icon-dk { + background-image: url(../flags/4x3/dk.svg); +} +.flag-icon-dk.flag-icon-squared { + background-image: url(../flags/1x1/dk.svg); +} +.flag-icon-dm { + background-image: url(../flags/4x3/dm.svg); +} +.flag-icon-dm.flag-icon-squared { + background-image: url(../flags/1x1/dm.svg); +} +.flag-icon-do { + background-image: url(../flags/4x3/do.svg); +} +.flag-icon-do.flag-icon-squared { + background-image: url(../flags/1x1/do.svg); +} +.flag-icon-dz { + background-image: url(../flags/4x3/dz.svg); +} +.flag-icon-dz.flag-icon-squared { + background-image: url(../flags/1x1/dz.svg); +} +.flag-icon-ec { + background-image: url(../flags/4x3/ec.svg); +} +.flag-icon-ec.flag-icon-squared { + background-image: url(../flags/1x1/ec.svg); +} +.flag-icon-ee { + background-image: url(../flags/4x3/ee.svg); +} +.flag-icon-ee.flag-icon-squared { + background-image: url(../flags/1x1/ee.svg); +} +.flag-icon-eg { + background-image: url(../flags/4x3/eg.svg); +} +.flag-icon-eg.flag-icon-squared { + background-image: url(../flags/1x1/eg.svg); +} +.flag-icon-eh { + background-image: url(../flags/4x3/eh.svg); +} +.flag-icon-eh.flag-icon-squared { + background-image: url(../flags/1x1/eh.svg); +} +.flag-icon-er { + background-image: url(../flags/4x3/er.svg); +} +.flag-icon-er.flag-icon-squared { + background-image: url(../flags/1x1/er.svg); +} +.flag-icon-es { + background-image: url(../flags/4x3/es.svg); +} +.flag-icon-es.flag-icon-squared { + background-image: url(../flags/1x1/es.svg); +} +.flag-icon-et { + background-image: url(../flags/4x3/et.svg); +} +.flag-icon-et.flag-icon-squared { + background-image: url(../flags/1x1/et.svg); +} +.flag-icon-fi { + background-image: url(../flags/4x3/fi.svg); +} +.flag-icon-fi.flag-icon-squared { + background-image: url(../flags/1x1/fi.svg); +} +.flag-icon-fj { + background-image: url(../flags/4x3/fj.svg); +} +.flag-icon-fj.flag-icon-squared { + background-image: url(../flags/1x1/fj.svg); +} +.flag-icon-fk { + background-image: url(../flags/4x3/fk.svg); +} +.flag-icon-fk.flag-icon-squared { + background-image: url(../flags/1x1/fk.svg); +} +.flag-icon-fm { + background-image: url(../flags/4x3/fm.svg); +} +.flag-icon-fm.flag-icon-squared { + background-image: url(../flags/1x1/fm.svg); +} +.flag-icon-fo { + background-image: url(../flags/4x3/fo.svg); +} +.flag-icon-fo.flag-icon-squared { + background-image: url(../flags/1x1/fo.svg); +} +.flag-icon-fr { + background-image: url(../flags/4x3/fr.svg); +} +.flag-icon-fr.flag-icon-squared { + background-image: url(../flags/1x1/fr.svg); +} +.flag-icon-ga { + background-image: url(../flags/4x3/ga.svg); +} +.flag-icon-ga.flag-icon-squared { + background-image: url(../flags/1x1/ga.svg); +} +.flag-icon-gb { + background-image: url(../flags/4x3/gb.svg); +} +.flag-icon-gb.flag-icon-squared { + background-image: url(../flags/1x1/gb.svg); +} +.flag-icon-gd { + background-image: url(../flags/4x3/gd.svg); +} +.flag-icon-gd.flag-icon-squared { + background-image: url(../flags/1x1/gd.svg); +} +.flag-icon-ge { + background-image: url(../flags/4x3/ge.svg); +} +.flag-icon-ge.flag-icon-squared { + background-image: url(../flags/1x1/ge.svg); +} +.flag-icon-gf { + background-image: url(../flags/4x3/gf.svg); +} +.flag-icon-gf.flag-icon-squared { + background-image: url(../flags/1x1/gf.svg); +} +.flag-icon-gg { + background-image: url(../flags/4x3/gg.svg); +} +.flag-icon-gg.flag-icon-squared { + background-image: url(../flags/1x1/gg.svg); +} +.flag-icon-gh { + background-image: url(../flags/4x3/gh.svg); +} +.flag-icon-gh.flag-icon-squared { + background-image: url(../flags/1x1/gh.svg); +} +.flag-icon-gi { + background-image: url(../flags/4x3/gi.svg); +} +.flag-icon-gi.flag-icon-squared { + background-image: url(../flags/1x1/gi.svg); +} +.flag-icon-gl { + background-image: url(../flags/4x3/gl.svg); +} +.flag-icon-gl.flag-icon-squared { + background-image: url(../flags/1x1/gl.svg); +} +.flag-icon-gm { + background-image: url(../flags/4x3/gm.svg); +} +.flag-icon-gm.flag-icon-squared { + background-image: url(../flags/1x1/gm.svg); +} +.flag-icon-gn { + background-image: url(../flags/4x3/gn.svg); +} +.flag-icon-gn.flag-icon-squared { + background-image: url(../flags/1x1/gn.svg); +} +.flag-icon-gp { + background-image: url(../flags/4x3/gp.svg); +} +.flag-icon-gp.flag-icon-squared { + background-image: url(../flags/1x1/gp.svg); +} +.flag-icon-gq { + background-image: url(../flags/4x3/gq.svg); +} +.flag-icon-gq.flag-icon-squared { + background-image: url(../flags/1x1/gq.svg); +} +.flag-icon-gr { + background-image: url(../flags/4x3/gr.svg); +} +.flag-icon-gr.flag-icon-squared { + background-image: url(../flags/1x1/gr.svg); +} +.flag-icon-gs { + background-image: url(../flags/4x3/gs.svg); +} +.flag-icon-gs.flag-icon-squared { + background-image: url(../flags/1x1/gs.svg); +} +.flag-icon-gt { + background-image: url(../flags/4x3/gt.svg); +} +.flag-icon-gt.flag-icon-squared { + background-image: url(../flags/1x1/gt.svg); +} +.flag-icon-gu { + background-image: url(../flags/4x3/gu.svg); +} +.flag-icon-gu.flag-icon-squared { + background-image: url(../flags/1x1/gu.svg); +} +.flag-icon-gw { + background-image: url(../flags/4x3/gw.svg); +} +.flag-icon-gw.flag-icon-squared { + background-image: url(../flags/1x1/gw.svg); +} +.flag-icon-gy { + background-image: url(../flags/4x3/gy.svg); +} +.flag-icon-gy.flag-icon-squared { + background-image: url(../flags/1x1/gy.svg); +} +.flag-icon-hk { + background-image: url(../flags/4x3/hk.svg); +} +.flag-icon-hk.flag-icon-squared { + background-image: url(../flags/1x1/hk.svg); +} +.flag-icon-hm { + background-image: url(../flags/4x3/hm.svg); +} +.flag-icon-hm.flag-icon-squared { + background-image: url(../flags/1x1/hm.svg); +} +.flag-icon-hn { + background-image: url(../flags/4x3/hn.svg); +} +.flag-icon-hn.flag-icon-squared { + background-image: url(../flags/1x1/hn.svg); +} +.flag-icon-hr { + background-image: url(../flags/4x3/hr.svg); +} +.flag-icon-hr.flag-icon-squared { + background-image: url(../flags/1x1/hr.svg); +} +.flag-icon-ht { + background-image: url(../flags/4x3/ht.svg); +} +.flag-icon-ht.flag-icon-squared { + background-image: url(../flags/1x1/ht.svg); +} +.flag-icon-hu { + background-image: url(../flags/4x3/hu.svg); +} +.flag-icon-hu.flag-icon-squared { + background-image: url(../flags/1x1/hu.svg); +} +.flag-icon-id { + background-image: url(../flags/4x3/id.svg); +} +.flag-icon-id.flag-icon-squared { + background-image: url(../flags/1x1/id.svg); +} +.flag-icon-ie { + background-image: url(../flags/4x3/ie.svg); +} +.flag-icon-ie.flag-icon-squared { + background-image: url(../flags/1x1/ie.svg); +} +.flag-icon-il { + background-image: url(../flags/4x3/il.svg); +} +.flag-icon-il.flag-icon-squared { + background-image: url(../flags/1x1/il.svg); +} +.flag-icon-im { + background-image: url(../flags/4x3/im.svg); +} +.flag-icon-im.flag-icon-squared { + background-image: url(../flags/1x1/im.svg); +} +.flag-icon-in { + background-image: url(../flags/4x3/in.svg); +} +.flag-icon-in.flag-icon-squared { + background-image: url(../flags/1x1/in.svg); +} +.flag-icon-io { + background-image: url(../flags/4x3/io.svg); +} +.flag-icon-io.flag-icon-squared { + background-image: url(../flags/1x1/io.svg); +} +.flag-icon-iq { + background-image: url(../flags/4x3/iq.svg); +} +.flag-icon-iq.flag-icon-squared { + background-image: url(../flags/1x1/iq.svg); +} +.flag-icon-ir { + background-image: url(../flags/4x3/ir.svg); +} +.flag-icon-ir.flag-icon-squared { + background-image: url(../flags/1x1/ir.svg); +} +.flag-icon-is { + background-image: url(../flags/4x3/is.svg); +} +.flag-icon-is.flag-icon-squared { + background-image: url(../flags/1x1/is.svg); +} +.flag-icon-it { + background-image: url(../flags/4x3/it.svg); +} +.flag-icon-it.flag-icon-squared { + background-image: url(../flags/1x1/it.svg); +} +.flag-icon-je { + background-image: url(../flags/4x3/je.svg); +} +.flag-icon-je.flag-icon-squared { + background-image: url(../flags/1x1/je.svg); +} +.flag-icon-jm { + background-image: url(../flags/4x3/jm.svg); +} +.flag-icon-jm.flag-icon-squared { + background-image: url(../flags/1x1/jm.svg); +} +.flag-icon-jo { + background-image: url(../flags/4x3/jo.svg); +} +.flag-icon-jo.flag-icon-squared { + background-image: url(../flags/1x1/jo.svg); +} +.flag-icon-jp { + background-image: url(../flags/4x3/jp.svg); +} +.flag-icon-jp.flag-icon-squared { + background-image: url(../flags/1x1/jp.svg); +} +.flag-icon-ke { + background-image: url(../flags/4x3/ke.svg); +} +.flag-icon-ke.flag-icon-squared { + background-image: url(../flags/1x1/ke.svg); +} +.flag-icon-kg { + background-image: url(../flags/4x3/kg.svg); +} +.flag-icon-kg.flag-icon-squared { + background-image: url(../flags/1x1/kg.svg); +} +.flag-icon-kh { + background-image: url(../flags/4x3/kh.svg); +} +.flag-icon-kh.flag-icon-squared { + background-image: url(../flags/1x1/kh.svg); +} +.flag-icon-ki { + background-image: url(../flags/4x3/ki.svg); +} +.flag-icon-ki.flag-icon-squared { + background-image: url(../flags/1x1/ki.svg); +} +.flag-icon-km { + background-image: url(../flags/4x3/km.svg); +} +.flag-icon-km.flag-icon-squared { + background-image: url(../flags/1x1/km.svg); +} +.flag-icon-kn { + background-image: url(../flags/4x3/kn.svg); +} +.flag-icon-kn.flag-icon-squared { + background-image: url(../flags/1x1/kn.svg); +} +.flag-icon-kp { + background-image: url(../flags/4x3/kp.svg); +} +.flag-icon-kp.flag-icon-squared { + background-image: url(../flags/1x1/kp.svg); +} +.flag-icon-kr { + background-image: url(../flags/4x3/kr.svg); +} +.flag-icon-kr.flag-icon-squared { + background-image: url(../flags/1x1/kr.svg); +} +.flag-icon-kw { + background-image: url(../flags/4x3/kw.svg); +} +.flag-icon-kw.flag-icon-squared { + background-image: url(../flags/1x1/kw.svg); +} +.flag-icon-ky { + background-image: url(../flags/4x3/ky.svg); +} +.flag-icon-ky.flag-icon-squared { + background-image: url(../flags/1x1/ky.svg); +} +.flag-icon-kz { + background-image: url(../flags/4x3/kz.svg); +} +.flag-icon-kz.flag-icon-squared { + background-image: url(../flags/1x1/kz.svg); +} +.flag-icon-la { + background-image: url(../flags/4x3/la.svg); +} +.flag-icon-la.flag-icon-squared { + background-image: url(../flags/1x1/la.svg); +} +.flag-icon-lb { + background-image: url(../flags/4x3/lb.svg); +} +.flag-icon-lb.flag-icon-squared { + background-image: url(../flags/1x1/lb.svg); +} +.flag-icon-lc { + background-image: url(../flags/4x3/lc.svg); +} +.flag-icon-lc.flag-icon-squared { + background-image: url(../flags/1x1/lc.svg); +} +.flag-icon-li { + background-image: url(../flags/4x3/li.svg); +} +.flag-icon-li.flag-icon-squared { + background-image: url(../flags/1x1/li.svg); +} +.flag-icon-lk { + background-image: url(../flags/4x3/lk.svg); +} +.flag-icon-lk.flag-icon-squared { + background-image: url(../flags/1x1/lk.svg); +} +.flag-icon-lr { + background-image: url(../flags/4x3/lr.svg); +} +.flag-icon-lr.flag-icon-squared { + background-image: url(../flags/1x1/lr.svg); +} +.flag-icon-ls { + background-image: url(../flags/4x3/ls.svg); +} +.flag-icon-ls.flag-icon-squared { + background-image: url(../flags/1x1/ls.svg); +} +.flag-icon-lt { + background-image: url(../flags/4x3/lt.svg); +} +.flag-icon-lt.flag-icon-squared { + background-image: url(../flags/1x1/lt.svg); +} +.flag-icon-lu { + background-image: url(../flags/4x3/lu.svg); +} +.flag-icon-lu.flag-icon-squared { + background-image: url(../flags/1x1/lu.svg); +} +.flag-icon-lv { + background-image: url(../flags/4x3/lv.svg); +} +.flag-icon-lv.flag-icon-squared { + background-image: url(../flags/1x1/lv.svg); +} +.flag-icon-ly { + background-image: url(../flags/4x3/ly.svg); +} +.flag-icon-ly.flag-icon-squared { + background-image: url(../flags/1x1/ly.svg); +} +.flag-icon-ma { + background-image: url(../flags/4x3/ma.svg); +} +.flag-icon-ma.flag-icon-squared { + background-image: url(../flags/1x1/ma.svg); +} +.flag-icon-mc { + background-image: url(../flags/4x3/mc.svg); +} +.flag-icon-mc.flag-icon-squared { + background-image: url(../flags/1x1/mc.svg); +} +.flag-icon-md { + background-image: url(../flags/4x3/md.svg); +} +.flag-icon-md.flag-icon-squared { + background-image: url(../flags/1x1/md.svg); +} +.flag-icon-me { + background-image: url(../flags/4x3/me.svg); +} +.flag-icon-me.flag-icon-squared { + background-image: url(../flags/1x1/me.svg); +} +.flag-icon-mf { + background-image: url(../flags/4x3/mf.svg); +} +.flag-icon-mf.flag-icon-squared { + background-image: url(../flags/1x1/mf.svg); +} +.flag-icon-mg { + background-image: url(../flags/4x3/mg.svg); +} +.flag-icon-mg.flag-icon-squared { + background-image: url(../flags/1x1/mg.svg); +} +.flag-icon-mh { + background-image: url(../flags/4x3/mh.svg); +} +.flag-icon-mh.flag-icon-squared { + background-image: url(../flags/1x1/mh.svg); +} +.flag-icon-mk { + background-image: url(../flags/4x3/mk.svg); +} +.flag-icon-mk.flag-icon-squared { + background-image: url(../flags/1x1/mk.svg); +} +.flag-icon-ml { + background-image: url(../flags/4x3/ml.svg); +} +.flag-icon-ml.flag-icon-squared { + background-image: url(../flags/1x1/ml.svg); +} +.flag-icon-mm { + background-image: url(../flags/4x3/mm.svg); +} +.flag-icon-mm.flag-icon-squared { + background-image: url(../flags/1x1/mm.svg); +} +.flag-icon-mn { + background-image: url(../flags/4x3/mn.svg); +} +.flag-icon-mn.flag-icon-squared { + background-image: url(../flags/1x1/mn.svg); +} +.flag-icon-mo { + background-image: url(../flags/4x3/mo.svg); +} +.flag-icon-mo.flag-icon-squared { + background-image: url(../flags/1x1/mo.svg); +} +.flag-icon-mp { + background-image: url(../flags/4x3/mp.svg); +} +.flag-icon-mp.flag-icon-squared { + background-image: url(../flags/1x1/mp.svg); +} +.flag-icon-mq { + background-image: url(../flags/4x3/mq.svg); +} +.flag-icon-mq.flag-icon-squared { + background-image: url(../flags/1x1/mq.svg); +} +.flag-icon-mr { + background-image: url(../flags/4x3/mr.svg); +} +.flag-icon-mr.flag-icon-squared { + background-image: url(../flags/1x1/mr.svg); +} +.flag-icon-ms { + background-image: url(../flags/4x3/ms.svg); +} +.flag-icon-ms.flag-icon-squared { + background-image: url(../flags/1x1/ms.svg); +} +.flag-icon-mt { + background-image: url(../flags/4x3/mt.svg); +} +.flag-icon-mt.flag-icon-squared { + background-image: url(../flags/1x1/mt.svg); +} +.flag-icon-mu { + background-image: url(../flags/4x3/mu.svg); +} +.flag-icon-mu.flag-icon-squared { + background-image: url(../flags/1x1/mu.svg); +} +.flag-icon-mv { + background-image: url(../flags/4x3/mv.svg); +} +.flag-icon-mv.flag-icon-squared { + background-image: url(../flags/1x1/mv.svg); +} +.flag-icon-mw { + background-image: url(../flags/4x3/mw.svg); +} +.flag-icon-mw.flag-icon-squared { + background-image: url(../flags/1x1/mw.svg); +} +.flag-icon-mx { + background-image: url(../flags/4x3/mx.svg); +} +.flag-icon-mx.flag-icon-squared { + background-image: url(../flags/1x1/mx.svg); +} +.flag-icon-my { + background-image: url(../flags/4x3/my.svg); +} +.flag-icon-my.flag-icon-squared { + background-image: url(../flags/1x1/my.svg); +} +.flag-icon-mz { + background-image: url(../flags/4x3/mz.svg); +} +.flag-icon-mz.flag-icon-squared { + background-image: url(../flags/1x1/mz.svg); +} +.flag-icon-na { + background-image: url(../flags/4x3/na.svg); +} +.flag-icon-na.flag-icon-squared { + background-image: url(../flags/1x1/na.svg); +} +.flag-icon-nc { + background-image: url(../flags/4x3/nc.svg); +} +.flag-icon-nc.flag-icon-squared { + background-image: url(../flags/1x1/nc.svg); +} +.flag-icon-ne { + background-image: url(../flags/4x3/ne.svg); +} +.flag-icon-ne.flag-icon-squared { + background-image: url(../flags/1x1/ne.svg); +} +.flag-icon-nf { + background-image: url(../flags/4x3/nf.svg); +} +.flag-icon-nf.flag-icon-squared { + background-image: url(../flags/1x1/nf.svg); +} +.flag-icon-ng { + background-image: url(../flags/4x3/ng.svg); +} +.flag-icon-ng.flag-icon-squared { + background-image: url(../flags/1x1/ng.svg); +} +.flag-icon-ni { + background-image: url(../flags/4x3/ni.svg); +} +.flag-icon-ni.flag-icon-squared { + background-image: url(../flags/1x1/ni.svg); +} +.flag-icon-nl { + background-image: url(../flags/4x3/nl.svg); +} +.flag-icon-nl.flag-icon-squared { + background-image: url(../flags/1x1/nl.svg); +} +.flag-icon-no { + background-image: url(../flags/4x3/no.svg); +} +.flag-icon-no.flag-icon-squared { + background-image: url(../flags/1x1/no.svg); +} +.flag-icon-np { + background-image: url(../flags/4x3/np.svg); +} +.flag-icon-np.flag-icon-squared { + background-image: url(../flags/1x1/np.svg); +} +.flag-icon-nr { + background-image: url(../flags/4x3/nr.svg); +} +.flag-icon-nr.flag-icon-squared { + background-image: url(../flags/1x1/nr.svg); +} +.flag-icon-nu { + background-image: url(../flags/4x3/nu.svg); +} +.flag-icon-nu.flag-icon-squared { + background-image: url(../flags/1x1/nu.svg); +} +.flag-icon-nz { + background-image: url(../flags/4x3/nz.svg); +} +.flag-icon-nz.flag-icon-squared { + background-image: url(../flags/1x1/nz.svg); +} +.flag-icon-om { + background-image: url(../flags/4x3/om.svg); +} +.flag-icon-om.flag-icon-squared { + background-image: url(../flags/1x1/om.svg); +} +.flag-icon-pa { + background-image: url(../flags/4x3/pa.svg); +} +.flag-icon-pa.flag-icon-squared { + background-image: url(../flags/1x1/pa.svg); +} +.flag-icon-pe { + background-image: url(../flags/4x3/pe.svg); +} +.flag-icon-pe.flag-icon-squared { + background-image: url(../flags/1x1/pe.svg); +} +.flag-icon-pf { + background-image: url(../flags/4x3/pf.svg); +} +.flag-icon-pf.flag-icon-squared { + background-image: url(../flags/1x1/pf.svg); +} +.flag-icon-pg { + background-image: url(../flags/4x3/pg.svg); +} +.flag-icon-pg.flag-icon-squared { + background-image: url(../flags/1x1/pg.svg); +} +.flag-icon-ph { + background-image: url(../flags/4x3/ph.svg); +} +.flag-icon-ph.flag-icon-squared { + background-image: url(../flags/1x1/ph.svg); +} +.flag-icon-pk { + background-image: url(../flags/4x3/pk.svg); +} +.flag-icon-pk.flag-icon-squared { + background-image: url(../flags/1x1/pk.svg); +} +.flag-icon-pl { + background-image: url(../flags/4x3/pl.svg); +} +.flag-icon-pl.flag-icon-squared { + background-image: url(../flags/1x1/pl.svg); +} +.flag-icon-pm { + background-image: url(../flags/4x3/pm.svg); +} +.flag-icon-pm.flag-icon-squared { + background-image: url(../flags/1x1/pm.svg); +} +.flag-icon-pn { + background-image: url(../flags/4x3/pn.svg); +} +.flag-icon-pn.flag-icon-squared { + background-image: url(../flags/1x1/pn.svg); +} +.flag-icon-pr { + background-image: url(../flags/4x3/pr.svg); +} +.flag-icon-pr.flag-icon-squared { + background-image: url(../flags/1x1/pr.svg); +} +.flag-icon-ps { + background-image: url(../flags/4x3/ps.svg); +} +.flag-icon-ps.flag-icon-squared { + background-image: url(../flags/1x1/ps.svg); +} +.flag-icon-pt { + background-image: url(../flags/4x3/pt.svg); +} +.flag-icon-pt.flag-icon-squared { + background-image: url(../flags/1x1/pt.svg); +} +.flag-icon-pw { + background-image: url(../flags/4x3/pw.svg); +} +.flag-icon-pw.flag-icon-squared { + background-image: url(../flags/1x1/pw.svg); +} +.flag-icon-py { + background-image: url(../flags/4x3/py.svg); +} +.flag-icon-py.flag-icon-squared { + background-image: url(../flags/1x1/py.svg); +} +.flag-icon-qa { + background-image: url(../flags/4x3/qa.svg); +} +.flag-icon-qa.flag-icon-squared { + background-image: url(../flags/1x1/qa.svg); +} +.flag-icon-re { + background-image: url(../flags/4x3/re.svg); +} +.flag-icon-re.flag-icon-squared { + background-image: url(../flags/1x1/re.svg); +} +.flag-icon-ro { + background-image: url(../flags/4x3/ro.svg); +} +.flag-icon-ro.flag-icon-squared { + background-image: url(../flags/1x1/ro.svg); +} +.flag-icon-rs { + background-image: url(../flags/4x3/rs.svg); +} +.flag-icon-rs.flag-icon-squared { + background-image: url(../flags/1x1/rs.svg); +} +.flag-icon-ru { + background-image: url(../flags/4x3/ru.svg); +} +.flag-icon-ru.flag-icon-squared { + background-image: url(../flags/1x1/ru.svg); +} +.flag-icon-rw { + background-image: url(../flags/4x3/rw.svg); +} +.flag-icon-rw.flag-icon-squared { + background-image: url(../flags/1x1/rw.svg); +} +.flag-icon-sa { + background-image: url(../flags/4x3/sa.svg); +} +.flag-icon-sa.flag-icon-squared { + background-image: url(../flags/1x1/sa.svg); +} +.flag-icon-sb { + background-image: url(../flags/4x3/sb.svg); +} +.flag-icon-sb.flag-icon-squared { + background-image: url(../flags/1x1/sb.svg); +} +.flag-icon-sc { + background-image: url(../flags/4x3/sc.svg); +} +.flag-icon-sc.flag-icon-squared { + background-image: url(../flags/1x1/sc.svg); +} +.flag-icon-sd { + background-image: url(../flags/4x3/sd.svg); +} +.flag-icon-sd.flag-icon-squared { + background-image: url(../flags/1x1/sd.svg); +} +.flag-icon-se { + background-image: url(../flags/4x3/se.svg); +} +.flag-icon-se.flag-icon-squared { + background-image: url(../flags/1x1/se.svg); +} +.flag-icon-sg { + background-image: url(../flags/4x3/sg.svg); +} +.flag-icon-sg.flag-icon-squared { + background-image: url(../flags/1x1/sg.svg); +} +.flag-icon-sh { + background-image: url(../flags/4x3/sh.svg); +} +.flag-icon-sh.flag-icon-squared { + background-image: url(../flags/1x1/sh.svg); +} +.flag-icon-si { + background-image: url(../flags/4x3/si.svg); +} +.flag-icon-si.flag-icon-squared { + background-image: url(../flags/1x1/si.svg); +} +.flag-icon-sj { + background-image: url(../flags/4x3/sj.svg); +} +.flag-icon-sj.flag-icon-squared { + background-image: url(../flags/1x1/sj.svg); +} +.flag-icon-sk { + background-image: url(../flags/4x3/sk.svg); +} +.flag-icon-sk.flag-icon-squared { + background-image: url(../flags/1x1/sk.svg); +} +.flag-icon-sl { + background-image: url(../flags/4x3/sl.svg); +} +.flag-icon-sl.flag-icon-squared { + background-image: url(../flags/1x1/sl.svg); +} +.flag-icon-sm { + background-image: url(../flags/4x3/sm.svg); +} +.flag-icon-sm.flag-icon-squared { + background-image: url(../flags/1x1/sm.svg); +} +.flag-icon-sn { + background-image: url(../flags/4x3/sn.svg); +} +.flag-icon-sn.flag-icon-squared { + background-image: url(../flags/1x1/sn.svg); +} +.flag-icon-so { + background-image: url(../flags/4x3/so.svg); +} +.flag-icon-so.flag-icon-squared { + background-image: url(../flags/1x1/so.svg); +} +.flag-icon-sr { + background-image: url(../flags/4x3/sr.svg); +} +.flag-icon-sr.flag-icon-squared { + background-image: url(../flags/1x1/sr.svg); +} +.flag-icon-ss { + background-image: url(../flags/4x3/ss.svg); +} +.flag-icon-ss.flag-icon-squared { + background-image: url(../flags/1x1/ss.svg); +} +.flag-icon-st { + background-image: url(../flags/4x3/st.svg); +} +.flag-icon-st.flag-icon-squared { + background-image: url(../flags/1x1/st.svg); +} +.flag-icon-sv { + background-image: url(../flags/4x3/sv.svg); +} +.flag-icon-sv.flag-icon-squared { + background-image: url(../flags/1x1/sv.svg); +} +.flag-icon-sx { + background-image: url(../flags/4x3/sx.svg); +} +.flag-icon-sx.flag-icon-squared { + background-image: url(../flags/1x1/sx.svg); +} +.flag-icon-sy { + background-image: url(../flags/4x3/sy.svg); +} +.flag-icon-sy.flag-icon-squared { + background-image: url(../flags/1x1/sy.svg); +} +.flag-icon-sz { + background-image: url(../flags/4x3/sz.svg); +} +.flag-icon-sz.flag-icon-squared { + background-image: url(../flags/1x1/sz.svg); +} +.flag-icon-tc { + background-image: url(../flags/4x3/tc.svg); +} +.flag-icon-tc.flag-icon-squared { + background-image: url(../flags/1x1/tc.svg); +} +.flag-icon-td { + background-image: url(../flags/4x3/td.svg); +} +.flag-icon-td.flag-icon-squared { + background-image: url(../flags/1x1/td.svg); +} +.flag-icon-tf { + background-image: url(../flags/4x3/tf.svg); +} +.flag-icon-tf.flag-icon-squared { + background-image: url(../flags/1x1/tf.svg); +} +.flag-icon-tg { + background-image: url(../flags/4x3/tg.svg); +} +.flag-icon-tg.flag-icon-squared { + background-image: url(../flags/1x1/tg.svg); +} +.flag-icon-th { + background-image: url(../flags/4x3/th.svg); +} +.flag-icon-th.flag-icon-squared { + background-image: url(../flags/1x1/th.svg); +} +.flag-icon-tj { + background-image: url(../flags/4x3/tj.svg); +} +.flag-icon-tj.flag-icon-squared { + background-image: url(../flags/1x1/tj.svg); +} +.flag-icon-tk { + background-image: url(../flags/4x3/tk.svg); +} +.flag-icon-tk.flag-icon-squared { + background-image: url(../flags/1x1/tk.svg); +} +.flag-icon-tl { + background-image: url(../flags/4x3/tl.svg); +} +.flag-icon-tl.flag-icon-squared { + background-image: url(../flags/1x1/tl.svg); +} +.flag-icon-tm { + background-image: url(../flags/4x3/tm.svg); +} +.flag-icon-tm.flag-icon-squared { + background-image: url(../flags/1x1/tm.svg); +} +.flag-icon-tn { + background-image: url(../flags/4x3/tn.svg); +} +.flag-icon-tn.flag-icon-squared { + background-image: url(../flags/1x1/tn.svg); +} +.flag-icon-to { + background-image: url(../flags/4x3/to.svg); +} +.flag-icon-to.flag-icon-squared { + background-image: url(../flags/1x1/to.svg); +} +.flag-icon-tr { + background-image: url(../flags/4x3/tr.svg); +} +.flag-icon-tr.flag-icon-squared { + background-image: url(../flags/1x1/tr.svg); +} +.flag-icon-tt { + background-image: url(../flags/4x3/tt.svg); +} +.flag-icon-tt.flag-icon-squared { + background-image: url(../flags/1x1/tt.svg); +} +.flag-icon-tv { + background-image: url(../flags/4x3/tv.svg); +} +.flag-icon-tv.flag-icon-squared { + background-image: url(../flags/1x1/tv.svg); +} +.flag-icon-tw { + background-image: url(../flags/4x3/tw.svg); +} +.flag-icon-tw.flag-icon-squared { + background-image: url(../flags/1x1/tw.svg); +} +.flag-icon-tz { + background-image: url(../flags/4x3/tz.svg); +} +.flag-icon-tz.flag-icon-squared { + background-image: url(../flags/1x1/tz.svg); +} +.flag-icon-ua { + background-image: url(../flags/4x3/ua.svg); +} +.flag-icon-ua.flag-icon-squared { + background-image: url(../flags/1x1/ua.svg); +} +.flag-icon-ug { + background-image: url(../flags/4x3/ug.svg); +} +.flag-icon-ug.flag-icon-squared { + background-image: url(../flags/1x1/ug.svg); +} +.flag-icon-um { + background-image: url(../flags/4x3/um.svg); +} +.flag-icon-um.flag-icon-squared { + background-image: url(../flags/1x1/um.svg); +} +.flag-icon-us { + background-image: url(../flags/4x3/us.svg); +} +.flag-icon-us.flag-icon-squared { + background-image: url(../flags/1x1/us.svg); +} +.flag-icon-uy { + background-image: url(../flags/4x3/uy.svg); +} +.flag-icon-uy.flag-icon-squared { + background-image: url(../flags/1x1/uy.svg); +} +.flag-icon-uz { + background-image: url(../flags/4x3/uz.svg); +} +.flag-icon-uz.flag-icon-squared { + background-image: url(../flags/1x1/uz.svg); +} +.flag-icon-va { + background-image: url(../flags/4x3/va.svg); +} +.flag-icon-va.flag-icon-squared { + background-image: url(../flags/1x1/va.svg); +} +.flag-icon-vc { + background-image: url(../flags/4x3/vc.svg); +} +.flag-icon-vc.flag-icon-squared { + background-image: url(../flags/1x1/vc.svg); +} +.flag-icon-ve { + background-image: url(../flags/4x3/ve.svg); +} +.flag-icon-ve.flag-icon-squared { + background-image: url(../flags/1x1/ve.svg); +} +.flag-icon-vg { + background-image: url(../flags/4x3/vg.svg); +} +.flag-icon-vg.flag-icon-squared { + background-image: url(../flags/1x1/vg.svg); +} +.flag-icon-vi { + background-image: url(../flags/4x3/vi.svg); +} +.flag-icon-vi.flag-icon-squared { + background-image: url(../flags/1x1/vi.svg); +} +.flag-icon-vn { + background-image: url(../flags/4x3/vn.svg); +} +.flag-icon-vn.flag-icon-squared { + background-image: url(../flags/1x1/vn.svg); +} +.flag-icon-vu { + background-image: url(../flags/4x3/vu.svg); +} +.flag-icon-vu.flag-icon-squared { + background-image: url(../flags/1x1/vu.svg); +} +.flag-icon-wf { + background-image: url(../flags/4x3/wf.svg); +} +.flag-icon-wf.flag-icon-squared { + background-image: url(../flags/1x1/wf.svg); +} +.flag-icon-ws { + background-image: url(../flags/4x3/ws.svg); +} +.flag-icon-ws.flag-icon-squared { + background-image: url(../flags/1x1/ws.svg); +} +.flag-icon-ye { + background-image: url(../flags/4x3/ye.svg); +} +.flag-icon-ye.flag-icon-squared { + background-image: url(../flags/1x1/ye.svg); +} +.flag-icon-yt { + background-image: url(../flags/4x3/yt.svg); +} +.flag-icon-yt.flag-icon-squared { + background-image: url(../flags/1x1/yt.svg); +} +.flag-icon-za { + background-image: url(../flags/4x3/za.svg); +} +.flag-icon-za.flag-icon-squared { + background-image: url(../flags/1x1/za.svg); +} +.flag-icon-zm { + background-image: url(../flags/4x3/zm.svg); +} +.flag-icon-zm.flag-icon-squared { + background-image: url(../flags/1x1/zm.svg); +} +.flag-icon-zw { + background-image: url(../flags/4x3/zw.svg); +} +.flag-icon-zw.flag-icon-squared { + background-image: url(../flags/1x1/zw.svg); +} +.flag-icon-es-ca { + background-image: url(../flags/4x3/es-ca.svg); +} +.flag-icon-es-ca.flag-icon-squared { + background-image: url(../flags/1x1/es-ca.svg); +} +.flag-icon-es-ga { + background-image: url(../flags/4x3/es-ga.svg); +} +.flag-icon-es-ga.flag-icon-squared { + background-image: url(../flags/1x1/es-ga.svg); +} +.flag-icon-eu { + background-image: url(../flags/4x3/eu.svg); +} +.flag-icon-eu.flag-icon-squared { + background-image: url(../flags/1x1/eu.svg); +} +.flag-icon-gb-eng { + background-image: url(../flags/4x3/gb-eng.svg); +} +.flag-icon-gb-eng.flag-icon-squared { + background-image: url(../flags/1x1/gb-eng.svg); +} +.flag-icon-gb-nir { + background-image: url(../flags/4x3/gb-nir.svg); +} +.flag-icon-gb-nir.flag-icon-squared { + background-image: url(../flags/1x1/gb-nir.svg); +} +.flag-icon-gb-sct { + background-image: url(../flags/4x3/gb-sct.svg); +} +.flag-icon-gb-sct.flag-icon-squared { + background-image: url(../flags/1x1/gb-sct.svg); +} +.flag-icon-gb-wls { + background-image: url(../flags/4x3/gb-wls.svg); +} +.flag-icon-gb-wls.flag-icon-squared { + background-image: url(../flags/1x1/gb-wls.svg); +} +.flag-icon-un { + background-image: url(../flags/4x3/un.svg); +} +.flag-icon-un.flag-icon-squared { + background-image: url(../flags/1x1/un.svg); +} +.flag-icon-xk { + background-image: url(../flags/4x3/xk.svg); +} +.flag-icon-xk.flag-icon-squared { + background-image: url(../flags/1x1/xk.svg); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/css/flag-icon.min.css b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/css/flag-icon.min.css new file mode 100644 index 0000000000..3d049b3953 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/css/flag-icon.min.css @@ -0,0 +1 @@ +.flag-icon-background{background-size:contain;background-position:50%;background-repeat:no-repeat}.flag-icon{background-size:contain;background-position:50%;background-repeat:no-repeat;position:relative;display:inline-block;width:1.33333333em;line-height:1em}.flag-icon:before{content:'\00a0'}.flag-icon.flag-icon-squared{width:1em}.flag-icon-ad{background-image:url(../flags/4x3/ad.svg)}.flag-icon-ad.flag-icon-squared{background-image:url(../flags/1x1/ad.svg)}.flag-icon-ae{background-image:url(../flags/4x3/ae.svg)}.flag-icon-ae.flag-icon-squared{background-image:url(../flags/1x1/ae.svg)}.flag-icon-af{background-image:url(../flags/4x3/af.svg)}.flag-icon-af.flag-icon-squared{background-image:url(../flags/1x1/af.svg)}.flag-icon-ag{background-image:url(../flags/4x3/ag.svg)}.flag-icon-ag.flag-icon-squared{background-image:url(../flags/1x1/ag.svg)}.flag-icon-ai{background-image:url(../flags/4x3/ai.svg)}.flag-icon-ai.flag-icon-squared{background-image:url(../flags/1x1/ai.svg)}.flag-icon-al{background-image:url(../flags/4x3/al.svg)}.flag-icon-al.flag-icon-squared{background-image:url(../flags/1x1/al.svg)}.flag-icon-am{background-image:url(../flags/4x3/am.svg)}.flag-icon-am.flag-icon-squared{background-image:url(../flags/1x1/am.svg)}.flag-icon-ao{background-image:url(../flags/4x3/ao.svg)}.flag-icon-ao.flag-icon-squared{background-image:url(../flags/1x1/ao.svg)}.flag-icon-aq{background-image:url(../flags/4x3/aq.svg)}.flag-icon-aq.flag-icon-squared{background-image:url(../flags/1x1/aq.svg)}.flag-icon-ar{background-image:url(../flags/4x3/ar.svg)}.flag-icon-ar.flag-icon-squared{background-image:url(../flags/1x1/ar.svg)}.flag-icon-as{background-image:url(../flags/4x3/as.svg)}.flag-icon-as.flag-icon-squared{background-image:url(../flags/1x1/as.svg)}.flag-icon-at{background-image:url(../flags/4x3/at.svg)}.flag-icon-at.flag-icon-squared{background-image:url(../flags/1x1/at.svg)}.flag-icon-au{background-image:url(../flags/4x3/au.svg)}.flag-icon-au.flag-icon-squared{background-image:url(../flags/1x1/au.svg)}.flag-icon-aw{background-image:url(../flags/4x3/aw.svg)}.flag-icon-aw.flag-icon-squared{background-image:url(../flags/1x1/aw.svg)}.flag-icon-ax{background-image:url(../flags/4x3/ax.svg)}.flag-icon-ax.flag-icon-squared{background-image:url(../flags/1x1/ax.svg)}.flag-icon-az{background-image:url(../flags/4x3/az.svg)}.flag-icon-az.flag-icon-squared{background-image:url(../flags/1x1/az.svg)}.flag-icon-ba{background-image:url(../flags/4x3/ba.svg)}.flag-icon-ba.flag-icon-squared{background-image:url(../flags/1x1/ba.svg)}.flag-icon-bb{background-image:url(../flags/4x3/bb.svg)}.flag-icon-bb.flag-icon-squared{background-image:url(../flags/1x1/bb.svg)}.flag-icon-bd{background-image:url(../flags/4x3/bd.svg)}.flag-icon-bd.flag-icon-squared{background-image:url(../flags/1x1/bd.svg)}.flag-icon-be{background-image:url(../flags/4x3/be.svg)}.flag-icon-be.flag-icon-squared{background-image:url(../flags/1x1/be.svg)}.flag-icon-bf{background-image:url(../flags/4x3/bf.svg)}.flag-icon-bf.flag-icon-squared{background-image:url(../flags/1x1/bf.svg)}.flag-icon-bg{background-image:url(../flags/4x3/bg.svg)}.flag-icon-bg.flag-icon-squared{background-image:url(../flags/1x1/bg.svg)}.flag-icon-bh{background-image:url(../flags/4x3/bh.svg)}.flag-icon-bh.flag-icon-squared{background-image:url(../flags/1x1/bh.svg)}.flag-icon-bi{background-image:url(../flags/4x3/bi.svg)}.flag-icon-bi.flag-icon-squared{background-image:url(../flags/1x1/bi.svg)}.flag-icon-bj{background-image:url(../flags/4x3/bj.svg)}.flag-icon-bj.flag-icon-squared{background-image:url(../flags/1x1/bj.svg)}.flag-icon-bl{background-image:url(../flags/4x3/bl.svg)}.flag-icon-bl.flag-icon-squared{background-image:url(../flags/1x1/bl.svg)}.flag-icon-bm{background-image:url(../flags/4x3/bm.svg)}.flag-icon-bm.flag-icon-squared{background-image:url(../flags/1x1/bm.svg)}.flag-icon-bn{background-image:url(../flags/4x3/bn.svg)}.flag-icon-bn.flag-icon-squared{background-image:url(../flags/1x1/bn.svg)}.flag-icon-bo{background-image:url(../flags/4x3/bo.svg)}.flag-icon-bo.flag-icon-squared{background-image:url(../flags/1x1/bo.svg)}.flag-icon-bq{background-image:url(../flags/4x3/bq.svg)}.flag-icon-bq.flag-icon-squared{background-image:url(../flags/1x1/bq.svg)}.flag-icon-br{background-image:url(../flags/4x3/br.svg)}.flag-icon-br.flag-icon-squared{background-image:url(../flags/1x1/br.svg)}.flag-icon-bs{background-image:url(../flags/4x3/bs.svg)}.flag-icon-bs.flag-icon-squared{background-image:url(../flags/1x1/bs.svg)}.flag-icon-bt{background-image:url(../flags/4x3/bt.svg)}.flag-icon-bt.flag-icon-squared{background-image:url(../flags/1x1/bt.svg)}.flag-icon-bv{background-image:url(../flags/4x3/bv.svg)}.flag-icon-bv.flag-icon-squared{background-image:url(../flags/1x1/bv.svg)}.flag-icon-bw{background-image:url(../flags/4x3/bw.svg)}.flag-icon-bw.flag-icon-squared{background-image:url(../flags/1x1/bw.svg)}.flag-icon-by{background-image:url(../flags/4x3/by.svg)}.flag-icon-by.flag-icon-squared{background-image:url(../flags/1x1/by.svg)}.flag-icon-bz{background-image:url(../flags/4x3/bz.svg)}.flag-icon-bz.flag-icon-squared{background-image:url(../flags/1x1/bz.svg)}.flag-icon-ca{background-image:url(../flags/4x3/ca.svg)}.flag-icon-ca.flag-icon-squared{background-image:url(../flags/1x1/ca.svg)}.flag-icon-cc{background-image:url(../flags/4x3/cc.svg)}.flag-icon-cc.flag-icon-squared{background-image:url(../flags/1x1/cc.svg)}.flag-icon-cd{background-image:url(../flags/4x3/cd.svg)}.flag-icon-cd.flag-icon-squared{background-image:url(../flags/1x1/cd.svg)}.flag-icon-cf{background-image:url(../flags/4x3/cf.svg)}.flag-icon-cf.flag-icon-squared{background-image:url(../flags/1x1/cf.svg)}.flag-icon-cg{background-image:url(../flags/4x3/cg.svg)}.flag-icon-cg.flag-icon-squared{background-image:url(../flags/1x1/cg.svg)}.flag-icon-ch{background-image:url(../flags/4x3/ch.svg)}.flag-icon-ch.flag-icon-squared{background-image:url(../flags/1x1/ch.svg)}.flag-icon-ci{background-image:url(../flags/4x3/ci.svg)}.flag-icon-ci.flag-icon-squared{background-image:url(../flags/1x1/ci.svg)}.flag-icon-ck{background-image:url(../flags/4x3/ck.svg)}.flag-icon-ck.flag-icon-squared{background-image:url(../flags/1x1/ck.svg)}.flag-icon-cl{background-image:url(../flags/4x3/cl.svg)}.flag-icon-cl.flag-icon-squared{background-image:url(../flags/1x1/cl.svg)}.flag-icon-cm{background-image:url(../flags/4x3/cm.svg)}.flag-icon-cm.flag-icon-squared{background-image:url(../flags/1x1/cm.svg)}.flag-icon-cn{background-image:url(../flags/4x3/cn.svg)}.flag-icon-cn.flag-icon-squared{background-image:url(../flags/1x1/cn.svg)}.flag-icon-co{background-image:url(../flags/4x3/co.svg)}.flag-icon-co.flag-icon-squared{background-image:url(../flags/1x1/co.svg)}.flag-icon-cr{background-image:url(../flags/4x3/cr.svg)}.flag-icon-cr.flag-icon-squared{background-image:url(../flags/1x1/cr.svg)}.flag-icon-cu{background-image:url(../flags/4x3/cu.svg)}.flag-icon-cu.flag-icon-squared{background-image:url(../flags/1x1/cu.svg)}.flag-icon-cv{background-image:url(../flags/4x3/cv.svg)}.flag-icon-cv.flag-icon-squared{background-image:url(../flags/1x1/cv.svg)}.flag-icon-cw{background-image:url(../flags/4x3/cw.svg)}.flag-icon-cw.flag-icon-squared{background-image:url(../flags/1x1/cw.svg)}.flag-icon-cx{background-image:url(../flags/4x3/cx.svg)}.flag-icon-cx.flag-icon-squared{background-image:url(../flags/1x1/cx.svg)}.flag-icon-cy{background-image:url(../flags/4x3/cy.svg)}.flag-icon-cy.flag-icon-squared{background-image:url(../flags/1x1/cy.svg)}.flag-icon-cz{background-image:url(../flags/4x3/cz.svg)}.flag-icon-cz.flag-icon-squared{background-image:url(../flags/1x1/cz.svg)}.flag-icon-de{background-image:url(../flags/4x3/de.svg)}.flag-icon-de.flag-icon-squared{background-image:url(../flags/1x1/de.svg)}.flag-icon-dj{background-image:url(../flags/4x3/dj.svg)}.flag-icon-dj.flag-icon-squared{background-image:url(../flags/1x1/dj.svg)}.flag-icon-dk{background-image:url(../flags/4x3/dk.svg)}.flag-icon-dk.flag-icon-squared{background-image:url(../flags/1x1/dk.svg)}.flag-icon-dm{background-image:url(../flags/4x3/dm.svg)}.flag-icon-dm.flag-icon-squared{background-image:url(../flags/1x1/dm.svg)}.flag-icon-do{background-image:url(../flags/4x3/do.svg)}.flag-icon-do.flag-icon-squared{background-image:url(../flags/1x1/do.svg)}.flag-icon-dz{background-image:url(../flags/4x3/dz.svg)}.flag-icon-dz.flag-icon-squared{background-image:url(../flags/1x1/dz.svg)}.flag-icon-ec{background-image:url(../flags/4x3/ec.svg)}.flag-icon-ec.flag-icon-squared{background-image:url(../flags/1x1/ec.svg)}.flag-icon-ee{background-image:url(../flags/4x3/ee.svg)}.flag-icon-ee.flag-icon-squared{background-image:url(../flags/1x1/ee.svg)}.flag-icon-eg{background-image:url(../flags/4x3/eg.svg)}.flag-icon-eg.flag-icon-squared{background-image:url(../flags/1x1/eg.svg)}.flag-icon-eh{background-image:url(../flags/4x3/eh.svg)}.flag-icon-eh.flag-icon-squared{background-image:url(../flags/1x1/eh.svg)}.flag-icon-er{background-image:url(../flags/4x3/er.svg)}.flag-icon-er.flag-icon-squared{background-image:url(../flags/1x1/er.svg)}.flag-icon-es{background-image:url(../flags/4x3/es.svg)}.flag-icon-es.flag-icon-squared{background-image:url(../flags/1x1/es.svg)}.flag-icon-et{background-image:url(../flags/4x3/et.svg)}.flag-icon-et.flag-icon-squared{background-image:url(../flags/1x1/et.svg)}.flag-icon-fi{background-image:url(../flags/4x3/fi.svg)}.flag-icon-fi.flag-icon-squared{background-image:url(../flags/1x1/fi.svg)}.flag-icon-fj{background-image:url(../flags/4x3/fj.svg)}.flag-icon-fj.flag-icon-squared{background-image:url(../flags/1x1/fj.svg)}.flag-icon-fk{background-image:url(../flags/4x3/fk.svg)}.flag-icon-fk.flag-icon-squared{background-image:url(../flags/1x1/fk.svg)}.flag-icon-fm{background-image:url(../flags/4x3/fm.svg)}.flag-icon-fm.flag-icon-squared{background-image:url(../flags/1x1/fm.svg)}.flag-icon-fo{background-image:url(../flags/4x3/fo.svg)}.flag-icon-fo.flag-icon-squared{background-image:url(../flags/1x1/fo.svg)}.flag-icon-fr{background-image:url(../flags/4x3/fr.svg)}.flag-icon-fr.flag-icon-squared{background-image:url(../flags/1x1/fr.svg)}.flag-icon-ga{background-image:url(../flags/4x3/ga.svg)}.flag-icon-ga.flag-icon-squared{background-image:url(../flags/1x1/ga.svg)}.flag-icon-gb{background-image:url(../flags/4x3/gb.svg)}.flag-icon-gb.flag-icon-squared{background-image:url(../flags/1x1/gb.svg)}.flag-icon-gd{background-image:url(../flags/4x3/gd.svg)}.flag-icon-gd.flag-icon-squared{background-image:url(../flags/1x1/gd.svg)}.flag-icon-ge{background-image:url(../flags/4x3/ge.svg)}.flag-icon-ge.flag-icon-squared{background-image:url(../flags/1x1/ge.svg)}.flag-icon-gf{background-image:url(../flags/4x3/gf.svg)}.flag-icon-gf.flag-icon-squared{background-image:url(../flags/1x1/gf.svg)}.flag-icon-gg{background-image:url(../flags/4x3/gg.svg)}.flag-icon-gg.flag-icon-squared{background-image:url(../flags/1x1/gg.svg)}.flag-icon-gh{background-image:url(../flags/4x3/gh.svg)}.flag-icon-gh.flag-icon-squared{background-image:url(../flags/1x1/gh.svg)}.flag-icon-gi{background-image:url(../flags/4x3/gi.svg)}.flag-icon-gi.flag-icon-squared{background-image:url(../flags/1x1/gi.svg)}.flag-icon-gl{background-image:url(../flags/4x3/gl.svg)}.flag-icon-gl.flag-icon-squared{background-image:url(../flags/1x1/gl.svg)}.flag-icon-gm{background-image:url(../flags/4x3/gm.svg)}.flag-icon-gm.flag-icon-squared{background-image:url(../flags/1x1/gm.svg)}.flag-icon-gn{background-image:url(../flags/4x3/gn.svg)}.flag-icon-gn.flag-icon-squared{background-image:url(../flags/1x1/gn.svg)}.flag-icon-gp{background-image:url(../flags/4x3/gp.svg)}.flag-icon-gp.flag-icon-squared{background-image:url(../flags/1x1/gp.svg)}.flag-icon-gq{background-image:url(../flags/4x3/gq.svg)}.flag-icon-gq.flag-icon-squared{background-image:url(../flags/1x1/gq.svg)}.flag-icon-gr{background-image:url(../flags/4x3/gr.svg)}.flag-icon-gr.flag-icon-squared{background-image:url(../flags/1x1/gr.svg)}.flag-icon-gs{background-image:url(../flags/4x3/gs.svg)}.flag-icon-gs.flag-icon-squared{background-image:url(../flags/1x1/gs.svg)}.flag-icon-gt{background-image:url(../flags/4x3/gt.svg)}.flag-icon-gt.flag-icon-squared{background-image:url(../flags/1x1/gt.svg)}.flag-icon-gu{background-image:url(../flags/4x3/gu.svg)}.flag-icon-gu.flag-icon-squared{background-image:url(../flags/1x1/gu.svg)}.flag-icon-gw{background-image:url(../flags/4x3/gw.svg)}.flag-icon-gw.flag-icon-squared{background-image:url(../flags/1x1/gw.svg)}.flag-icon-gy{background-image:url(../flags/4x3/gy.svg)}.flag-icon-gy.flag-icon-squared{background-image:url(../flags/1x1/gy.svg)}.flag-icon-hk{background-image:url(../flags/4x3/hk.svg)}.flag-icon-hk.flag-icon-squared{background-image:url(../flags/1x1/hk.svg)}.flag-icon-hm{background-image:url(../flags/4x3/hm.svg)}.flag-icon-hm.flag-icon-squared{background-image:url(../flags/1x1/hm.svg)}.flag-icon-hn{background-image:url(../flags/4x3/hn.svg)}.flag-icon-hn.flag-icon-squared{background-image:url(../flags/1x1/hn.svg)}.flag-icon-hr{background-image:url(../flags/4x3/hr.svg)}.flag-icon-hr.flag-icon-squared{background-image:url(../flags/1x1/hr.svg)}.flag-icon-ht{background-image:url(../flags/4x3/ht.svg)}.flag-icon-ht.flag-icon-squared{background-image:url(../flags/1x1/ht.svg)}.flag-icon-hu{background-image:url(../flags/4x3/hu.svg)}.flag-icon-hu.flag-icon-squared{background-image:url(../flags/1x1/hu.svg)}.flag-icon-id{background-image:url(../flags/4x3/id.svg)}.flag-icon-id.flag-icon-squared{background-image:url(../flags/1x1/id.svg)}.flag-icon-ie{background-image:url(../flags/4x3/ie.svg)}.flag-icon-ie.flag-icon-squared{background-image:url(../flags/1x1/ie.svg)}.flag-icon-il{background-image:url(../flags/4x3/il.svg)}.flag-icon-il.flag-icon-squared{background-image:url(../flags/1x1/il.svg)}.flag-icon-im{background-image:url(../flags/4x3/im.svg)}.flag-icon-im.flag-icon-squared{background-image:url(../flags/1x1/im.svg)}.flag-icon-in{background-image:url(../flags/4x3/in.svg)}.flag-icon-in.flag-icon-squared{background-image:url(../flags/1x1/in.svg)}.flag-icon-io{background-image:url(../flags/4x3/io.svg)}.flag-icon-io.flag-icon-squared{background-image:url(../flags/1x1/io.svg)}.flag-icon-iq{background-image:url(../flags/4x3/iq.svg)}.flag-icon-iq.flag-icon-squared{background-image:url(../flags/1x1/iq.svg)}.flag-icon-ir{background-image:url(../flags/4x3/ir.svg)}.flag-icon-ir.flag-icon-squared{background-image:url(../flags/1x1/ir.svg)}.flag-icon-is{background-image:url(../flags/4x3/is.svg)}.flag-icon-is.flag-icon-squared{background-image:url(../flags/1x1/is.svg)}.flag-icon-it{background-image:url(../flags/4x3/it.svg)}.flag-icon-it.flag-icon-squared{background-image:url(../flags/1x1/it.svg)}.flag-icon-je{background-image:url(../flags/4x3/je.svg)}.flag-icon-je.flag-icon-squared{background-image:url(../flags/1x1/je.svg)}.flag-icon-jm{background-image:url(../flags/4x3/jm.svg)}.flag-icon-jm.flag-icon-squared{background-image:url(../flags/1x1/jm.svg)}.flag-icon-jo{background-image:url(../flags/4x3/jo.svg)}.flag-icon-jo.flag-icon-squared{background-image:url(../flags/1x1/jo.svg)}.flag-icon-jp{background-image:url(../flags/4x3/jp.svg)}.flag-icon-jp.flag-icon-squared{background-image:url(../flags/1x1/jp.svg)}.flag-icon-ke{background-image:url(../flags/4x3/ke.svg)}.flag-icon-ke.flag-icon-squared{background-image:url(../flags/1x1/ke.svg)}.flag-icon-kg{background-image:url(../flags/4x3/kg.svg)}.flag-icon-kg.flag-icon-squared{background-image:url(../flags/1x1/kg.svg)}.flag-icon-kh{background-image:url(../flags/4x3/kh.svg)}.flag-icon-kh.flag-icon-squared{background-image:url(../flags/1x1/kh.svg)}.flag-icon-ki{background-image:url(../flags/4x3/ki.svg)}.flag-icon-ki.flag-icon-squared{background-image:url(../flags/1x1/ki.svg)}.flag-icon-km{background-image:url(../flags/4x3/km.svg)}.flag-icon-km.flag-icon-squared{background-image:url(../flags/1x1/km.svg)}.flag-icon-kn{background-image:url(../flags/4x3/kn.svg)}.flag-icon-kn.flag-icon-squared{background-image:url(../flags/1x1/kn.svg)}.flag-icon-kp{background-image:url(../flags/4x3/kp.svg)}.flag-icon-kp.flag-icon-squared{background-image:url(../flags/1x1/kp.svg)}.flag-icon-kr{background-image:url(../flags/4x3/kr.svg)}.flag-icon-kr.flag-icon-squared{background-image:url(../flags/1x1/kr.svg)}.flag-icon-kw{background-image:url(../flags/4x3/kw.svg)}.flag-icon-kw.flag-icon-squared{background-image:url(../flags/1x1/kw.svg)}.flag-icon-ky{background-image:url(../flags/4x3/ky.svg)}.flag-icon-ky.flag-icon-squared{background-image:url(../flags/1x1/ky.svg)}.flag-icon-kz{background-image:url(../flags/4x3/kz.svg)}.flag-icon-kz.flag-icon-squared{background-image:url(../flags/1x1/kz.svg)}.flag-icon-la{background-image:url(../flags/4x3/la.svg)}.flag-icon-la.flag-icon-squared{background-image:url(../flags/1x1/la.svg)}.flag-icon-lb{background-image:url(../flags/4x3/lb.svg)}.flag-icon-lb.flag-icon-squared{background-image:url(../flags/1x1/lb.svg)}.flag-icon-lc{background-image:url(../flags/4x3/lc.svg)}.flag-icon-lc.flag-icon-squared{background-image:url(../flags/1x1/lc.svg)}.flag-icon-li{background-image:url(../flags/4x3/li.svg)}.flag-icon-li.flag-icon-squared{background-image:url(../flags/1x1/li.svg)}.flag-icon-lk{background-image:url(../flags/4x3/lk.svg)}.flag-icon-lk.flag-icon-squared{background-image:url(../flags/1x1/lk.svg)}.flag-icon-lr{background-image:url(../flags/4x3/lr.svg)}.flag-icon-lr.flag-icon-squared{background-image:url(../flags/1x1/lr.svg)}.flag-icon-ls{background-image:url(../flags/4x3/ls.svg)}.flag-icon-ls.flag-icon-squared{background-image:url(../flags/1x1/ls.svg)}.flag-icon-lt{background-image:url(../flags/4x3/lt.svg)}.flag-icon-lt.flag-icon-squared{background-image:url(../flags/1x1/lt.svg)}.flag-icon-lu{background-image:url(../flags/4x3/lu.svg)}.flag-icon-lu.flag-icon-squared{background-image:url(../flags/1x1/lu.svg)}.flag-icon-lv{background-image:url(../flags/4x3/lv.svg)}.flag-icon-lv.flag-icon-squared{background-image:url(../flags/1x1/lv.svg)}.flag-icon-ly{background-image:url(../flags/4x3/ly.svg)}.flag-icon-ly.flag-icon-squared{background-image:url(../flags/1x1/ly.svg)}.flag-icon-ma{background-image:url(../flags/4x3/ma.svg)}.flag-icon-ma.flag-icon-squared{background-image:url(../flags/1x1/ma.svg)}.flag-icon-mc{background-image:url(../flags/4x3/mc.svg)}.flag-icon-mc.flag-icon-squared{background-image:url(../flags/1x1/mc.svg)}.flag-icon-md{background-image:url(../flags/4x3/md.svg)}.flag-icon-md.flag-icon-squared{background-image:url(../flags/1x1/md.svg)}.flag-icon-me{background-image:url(../flags/4x3/me.svg)}.flag-icon-me.flag-icon-squared{background-image:url(../flags/1x1/me.svg)}.flag-icon-mf{background-image:url(../flags/4x3/mf.svg)}.flag-icon-mf.flag-icon-squared{background-image:url(../flags/1x1/mf.svg)}.flag-icon-mg{background-image:url(../flags/4x3/mg.svg)}.flag-icon-mg.flag-icon-squared{background-image:url(../flags/1x1/mg.svg)}.flag-icon-mh{background-image:url(../flags/4x3/mh.svg)}.flag-icon-mh.flag-icon-squared{background-image:url(../flags/1x1/mh.svg)}.flag-icon-mk{background-image:url(../flags/4x3/mk.svg)}.flag-icon-mk.flag-icon-squared{background-image:url(../flags/1x1/mk.svg)}.flag-icon-ml{background-image:url(../flags/4x3/ml.svg)}.flag-icon-ml.flag-icon-squared{background-image:url(../flags/1x1/ml.svg)}.flag-icon-mm{background-image:url(../flags/4x3/mm.svg)}.flag-icon-mm.flag-icon-squared{background-image:url(../flags/1x1/mm.svg)}.flag-icon-mn{background-image:url(../flags/4x3/mn.svg)}.flag-icon-mn.flag-icon-squared{background-image:url(../flags/1x1/mn.svg)}.flag-icon-mo{background-image:url(../flags/4x3/mo.svg)}.flag-icon-mo.flag-icon-squared{background-image:url(../flags/1x1/mo.svg)}.flag-icon-mp{background-image:url(../flags/4x3/mp.svg)}.flag-icon-mp.flag-icon-squared{background-image:url(../flags/1x1/mp.svg)}.flag-icon-mq{background-image:url(../flags/4x3/mq.svg)}.flag-icon-mq.flag-icon-squared{background-image:url(../flags/1x1/mq.svg)}.flag-icon-mr{background-image:url(../flags/4x3/mr.svg)}.flag-icon-mr.flag-icon-squared{background-image:url(../flags/1x1/mr.svg)}.flag-icon-ms{background-image:url(../flags/4x3/ms.svg)}.flag-icon-ms.flag-icon-squared{background-image:url(../flags/1x1/ms.svg)}.flag-icon-mt{background-image:url(../flags/4x3/mt.svg)}.flag-icon-mt.flag-icon-squared{background-image:url(../flags/1x1/mt.svg)}.flag-icon-mu{background-image:url(../flags/4x3/mu.svg)}.flag-icon-mu.flag-icon-squared{background-image:url(../flags/1x1/mu.svg)}.flag-icon-mv{background-image:url(../flags/4x3/mv.svg)}.flag-icon-mv.flag-icon-squared{background-image:url(../flags/1x1/mv.svg)}.flag-icon-mw{background-image:url(../flags/4x3/mw.svg)}.flag-icon-mw.flag-icon-squared{background-image:url(../flags/1x1/mw.svg)}.flag-icon-mx{background-image:url(../flags/4x3/mx.svg)}.flag-icon-mx.flag-icon-squared{background-image:url(../flags/1x1/mx.svg)}.flag-icon-my{background-image:url(../flags/4x3/my.svg)}.flag-icon-my.flag-icon-squared{background-image:url(../flags/1x1/my.svg)}.flag-icon-mz{background-image:url(../flags/4x3/mz.svg)}.flag-icon-mz.flag-icon-squared{background-image:url(../flags/1x1/mz.svg)}.flag-icon-na{background-image:url(../flags/4x3/na.svg)}.flag-icon-na.flag-icon-squared{background-image:url(../flags/1x1/na.svg)}.flag-icon-nc{background-image:url(../flags/4x3/nc.svg)}.flag-icon-nc.flag-icon-squared{background-image:url(../flags/1x1/nc.svg)}.flag-icon-ne{background-image:url(../flags/4x3/ne.svg)}.flag-icon-ne.flag-icon-squared{background-image:url(../flags/1x1/ne.svg)}.flag-icon-nf{background-image:url(../flags/4x3/nf.svg)}.flag-icon-nf.flag-icon-squared{background-image:url(../flags/1x1/nf.svg)}.flag-icon-ng{background-image:url(../flags/4x3/ng.svg)}.flag-icon-ng.flag-icon-squared{background-image:url(../flags/1x1/ng.svg)}.flag-icon-ni{background-image:url(../flags/4x3/ni.svg)}.flag-icon-ni.flag-icon-squared{background-image:url(../flags/1x1/ni.svg)}.flag-icon-nl{background-image:url(../flags/4x3/nl.svg)}.flag-icon-nl.flag-icon-squared{background-image:url(../flags/1x1/nl.svg)}.flag-icon-no{background-image:url(../flags/4x3/no.svg)}.flag-icon-no.flag-icon-squared{background-image:url(../flags/1x1/no.svg)}.flag-icon-np{background-image:url(../flags/4x3/np.svg)}.flag-icon-np.flag-icon-squared{background-image:url(../flags/1x1/np.svg)}.flag-icon-nr{background-image:url(../flags/4x3/nr.svg)}.flag-icon-nr.flag-icon-squared{background-image:url(../flags/1x1/nr.svg)}.flag-icon-nu{background-image:url(../flags/4x3/nu.svg)}.flag-icon-nu.flag-icon-squared{background-image:url(../flags/1x1/nu.svg)}.flag-icon-nz{background-image:url(../flags/4x3/nz.svg)}.flag-icon-nz.flag-icon-squared{background-image:url(../flags/1x1/nz.svg)}.flag-icon-om{background-image:url(../flags/4x3/om.svg)}.flag-icon-om.flag-icon-squared{background-image:url(../flags/1x1/om.svg)}.flag-icon-pa{background-image:url(../flags/4x3/pa.svg)}.flag-icon-pa.flag-icon-squared{background-image:url(../flags/1x1/pa.svg)}.flag-icon-pe{background-image:url(../flags/4x3/pe.svg)}.flag-icon-pe.flag-icon-squared{background-image:url(../flags/1x1/pe.svg)}.flag-icon-pf{background-image:url(../flags/4x3/pf.svg)}.flag-icon-pf.flag-icon-squared{background-image:url(../flags/1x1/pf.svg)}.flag-icon-pg{background-image:url(../flags/4x3/pg.svg)}.flag-icon-pg.flag-icon-squared{background-image:url(../flags/1x1/pg.svg)}.flag-icon-ph{background-image:url(../flags/4x3/ph.svg)}.flag-icon-ph.flag-icon-squared{background-image:url(../flags/1x1/ph.svg)}.flag-icon-pk{background-image:url(../flags/4x3/pk.svg)}.flag-icon-pk.flag-icon-squared{background-image:url(../flags/1x1/pk.svg)}.flag-icon-pl{background-image:url(../flags/4x3/pl.svg)}.flag-icon-pl.flag-icon-squared{background-image:url(../flags/1x1/pl.svg)}.flag-icon-pm{background-image:url(../flags/4x3/pm.svg)}.flag-icon-pm.flag-icon-squared{background-image:url(../flags/1x1/pm.svg)}.flag-icon-pn{background-image:url(../flags/4x3/pn.svg)}.flag-icon-pn.flag-icon-squared{background-image:url(../flags/1x1/pn.svg)}.flag-icon-pr{background-image:url(../flags/4x3/pr.svg)}.flag-icon-pr.flag-icon-squared{background-image:url(../flags/1x1/pr.svg)}.flag-icon-ps{background-image:url(../flags/4x3/ps.svg)}.flag-icon-ps.flag-icon-squared{background-image:url(../flags/1x1/ps.svg)}.flag-icon-pt{background-image:url(../flags/4x3/pt.svg)}.flag-icon-pt.flag-icon-squared{background-image:url(../flags/1x1/pt.svg)}.flag-icon-pw{background-image:url(../flags/4x3/pw.svg)}.flag-icon-pw.flag-icon-squared{background-image:url(../flags/1x1/pw.svg)}.flag-icon-py{background-image:url(../flags/4x3/py.svg)}.flag-icon-py.flag-icon-squared{background-image:url(../flags/1x1/py.svg)}.flag-icon-qa{background-image:url(../flags/4x3/qa.svg)}.flag-icon-qa.flag-icon-squared{background-image:url(../flags/1x1/qa.svg)}.flag-icon-re{background-image:url(../flags/4x3/re.svg)}.flag-icon-re.flag-icon-squared{background-image:url(../flags/1x1/re.svg)}.flag-icon-ro{background-image:url(../flags/4x3/ro.svg)}.flag-icon-ro.flag-icon-squared{background-image:url(../flags/1x1/ro.svg)}.flag-icon-rs{background-image:url(../flags/4x3/rs.svg)}.flag-icon-rs.flag-icon-squared{background-image:url(../flags/1x1/rs.svg)}.flag-icon-ru{background-image:url(../flags/4x3/ru.svg)}.flag-icon-ru.flag-icon-squared{background-image:url(../flags/1x1/ru.svg)}.flag-icon-rw{background-image:url(../flags/4x3/rw.svg)}.flag-icon-rw.flag-icon-squared{background-image:url(../flags/1x1/rw.svg)}.flag-icon-sa{background-image:url(../flags/4x3/sa.svg)}.flag-icon-sa.flag-icon-squared{background-image:url(../flags/1x1/sa.svg)}.flag-icon-sb{background-image:url(../flags/4x3/sb.svg)}.flag-icon-sb.flag-icon-squared{background-image:url(../flags/1x1/sb.svg)}.flag-icon-sc{background-image:url(../flags/4x3/sc.svg)}.flag-icon-sc.flag-icon-squared{background-image:url(../flags/1x1/sc.svg)}.flag-icon-sd{background-image:url(../flags/4x3/sd.svg)}.flag-icon-sd.flag-icon-squared{background-image:url(../flags/1x1/sd.svg)}.flag-icon-se{background-image:url(../flags/4x3/se.svg)}.flag-icon-se.flag-icon-squared{background-image:url(../flags/1x1/se.svg)}.flag-icon-sg{background-image:url(../flags/4x3/sg.svg)}.flag-icon-sg.flag-icon-squared{background-image:url(../flags/1x1/sg.svg)}.flag-icon-sh{background-image:url(../flags/4x3/sh.svg)}.flag-icon-sh.flag-icon-squared{background-image:url(../flags/1x1/sh.svg)}.flag-icon-si{background-image:url(../flags/4x3/si.svg)}.flag-icon-si.flag-icon-squared{background-image:url(../flags/1x1/si.svg)}.flag-icon-sj{background-image:url(../flags/4x3/sj.svg)}.flag-icon-sj.flag-icon-squared{background-image:url(../flags/1x1/sj.svg)}.flag-icon-sk{background-image:url(../flags/4x3/sk.svg)}.flag-icon-sk.flag-icon-squared{background-image:url(../flags/1x1/sk.svg)}.flag-icon-sl{background-image:url(../flags/4x3/sl.svg)}.flag-icon-sl.flag-icon-squared{background-image:url(../flags/1x1/sl.svg)}.flag-icon-sm{background-image:url(../flags/4x3/sm.svg)}.flag-icon-sm.flag-icon-squared{background-image:url(../flags/1x1/sm.svg)}.flag-icon-sn{background-image:url(../flags/4x3/sn.svg)}.flag-icon-sn.flag-icon-squared{background-image:url(../flags/1x1/sn.svg)}.flag-icon-so{background-image:url(../flags/4x3/so.svg)}.flag-icon-so.flag-icon-squared{background-image:url(../flags/1x1/so.svg)}.flag-icon-sr{background-image:url(../flags/4x3/sr.svg)}.flag-icon-sr.flag-icon-squared{background-image:url(../flags/1x1/sr.svg)}.flag-icon-ss{background-image:url(../flags/4x3/ss.svg)}.flag-icon-ss.flag-icon-squared{background-image:url(../flags/1x1/ss.svg)}.flag-icon-st{background-image:url(../flags/4x3/st.svg)}.flag-icon-st.flag-icon-squared{background-image:url(../flags/1x1/st.svg)}.flag-icon-sv{background-image:url(../flags/4x3/sv.svg)}.flag-icon-sv.flag-icon-squared{background-image:url(../flags/1x1/sv.svg)}.flag-icon-sx{background-image:url(../flags/4x3/sx.svg)}.flag-icon-sx.flag-icon-squared{background-image:url(../flags/1x1/sx.svg)}.flag-icon-sy{background-image:url(../flags/4x3/sy.svg)}.flag-icon-sy.flag-icon-squared{background-image:url(../flags/1x1/sy.svg)}.flag-icon-sz{background-image:url(../flags/4x3/sz.svg)}.flag-icon-sz.flag-icon-squared{background-image:url(../flags/1x1/sz.svg)}.flag-icon-tc{background-image:url(../flags/4x3/tc.svg)}.flag-icon-tc.flag-icon-squared{background-image:url(../flags/1x1/tc.svg)}.flag-icon-td{background-image:url(../flags/4x3/td.svg)}.flag-icon-td.flag-icon-squared{background-image:url(../flags/1x1/td.svg)}.flag-icon-tf{background-image:url(../flags/4x3/tf.svg)}.flag-icon-tf.flag-icon-squared{background-image:url(../flags/1x1/tf.svg)}.flag-icon-tg{background-image:url(../flags/4x3/tg.svg)}.flag-icon-tg.flag-icon-squared{background-image:url(../flags/1x1/tg.svg)}.flag-icon-th{background-image:url(../flags/4x3/th.svg)}.flag-icon-th.flag-icon-squared{background-image:url(../flags/1x1/th.svg)}.flag-icon-tj{background-image:url(../flags/4x3/tj.svg)}.flag-icon-tj.flag-icon-squared{background-image:url(../flags/1x1/tj.svg)}.flag-icon-tk{background-image:url(../flags/4x3/tk.svg)}.flag-icon-tk.flag-icon-squared{background-image:url(../flags/1x1/tk.svg)}.flag-icon-tl{background-image:url(../flags/4x3/tl.svg)}.flag-icon-tl.flag-icon-squared{background-image:url(../flags/1x1/tl.svg)}.flag-icon-tm{background-image:url(../flags/4x3/tm.svg)}.flag-icon-tm.flag-icon-squared{background-image:url(../flags/1x1/tm.svg)}.flag-icon-tn{background-image:url(../flags/4x3/tn.svg)}.flag-icon-tn.flag-icon-squared{background-image:url(../flags/1x1/tn.svg)}.flag-icon-to{background-image:url(../flags/4x3/to.svg)}.flag-icon-to.flag-icon-squared{background-image:url(../flags/1x1/to.svg)}.flag-icon-tr{background-image:url(../flags/4x3/tr.svg)}.flag-icon-tr.flag-icon-squared{background-image:url(../flags/1x1/tr.svg)}.flag-icon-tt{background-image:url(../flags/4x3/tt.svg)}.flag-icon-tt.flag-icon-squared{background-image:url(../flags/1x1/tt.svg)}.flag-icon-tv{background-image:url(../flags/4x3/tv.svg)}.flag-icon-tv.flag-icon-squared{background-image:url(../flags/1x1/tv.svg)}.flag-icon-tw{background-image:url(../flags/4x3/tw.svg)}.flag-icon-tw.flag-icon-squared{background-image:url(../flags/1x1/tw.svg)}.flag-icon-tz{background-image:url(../flags/4x3/tz.svg)}.flag-icon-tz.flag-icon-squared{background-image:url(../flags/1x1/tz.svg)}.flag-icon-ua{background-image:url(../flags/4x3/ua.svg)}.flag-icon-ua.flag-icon-squared{background-image:url(../flags/1x1/ua.svg)}.flag-icon-ug{background-image:url(../flags/4x3/ug.svg)}.flag-icon-ug.flag-icon-squared{background-image:url(../flags/1x1/ug.svg)}.flag-icon-um{background-image:url(../flags/4x3/um.svg)}.flag-icon-um.flag-icon-squared{background-image:url(../flags/1x1/um.svg)}.flag-icon-us{background-image:url(../flags/4x3/us.svg)}.flag-icon-us.flag-icon-squared{background-image:url(../flags/1x1/us.svg)}.flag-icon-uy{background-image:url(../flags/4x3/uy.svg)}.flag-icon-uy.flag-icon-squared{background-image:url(../flags/1x1/uy.svg)}.flag-icon-uz{background-image:url(../flags/4x3/uz.svg)}.flag-icon-uz.flag-icon-squared{background-image:url(../flags/1x1/uz.svg)}.flag-icon-va{background-image:url(../flags/4x3/va.svg)}.flag-icon-va.flag-icon-squared{background-image:url(../flags/1x1/va.svg)}.flag-icon-vc{background-image:url(../flags/4x3/vc.svg)}.flag-icon-vc.flag-icon-squared{background-image:url(../flags/1x1/vc.svg)}.flag-icon-ve{background-image:url(../flags/4x3/ve.svg)}.flag-icon-ve.flag-icon-squared{background-image:url(../flags/1x1/ve.svg)}.flag-icon-vg{background-image:url(../flags/4x3/vg.svg)}.flag-icon-vg.flag-icon-squared{background-image:url(../flags/1x1/vg.svg)}.flag-icon-vi{background-image:url(../flags/4x3/vi.svg)}.flag-icon-vi.flag-icon-squared{background-image:url(../flags/1x1/vi.svg)}.flag-icon-vn{background-image:url(../flags/4x3/vn.svg)}.flag-icon-vn.flag-icon-squared{background-image:url(../flags/1x1/vn.svg)}.flag-icon-vu{background-image:url(../flags/4x3/vu.svg)}.flag-icon-vu.flag-icon-squared{background-image:url(../flags/1x1/vu.svg)}.flag-icon-wf{background-image:url(../flags/4x3/wf.svg)}.flag-icon-wf.flag-icon-squared{background-image:url(../flags/1x1/wf.svg)}.flag-icon-ws{background-image:url(../flags/4x3/ws.svg)}.flag-icon-ws.flag-icon-squared{background-image:url(../flags/1x1/ws.svg)}.flag-icon-ye{background-image:url(../flags/4x3/ye.svg)}.flag-icon-ye.flag-icon-squared{background-image:url(../flags/1x1/ye.svg)}.flag-icon-yt{background-image:url(../flags/4x3/yt.svg)}.flag-icon-yt.flag-icon-squared{background-image:url(../flags/1x1/yt.svg)}.flag-icon-za{background-image:url(../flags/4x3/za.svg)}.flag-icon-za.flag-icon-squared{background-image:url(../flags/1x1/za.svg)}.flag-icon-zm{background-image:url(../flags/4x3/zm.svg)}.flag-icon-zm.flag-icon-squared{background-image:url(../flags/1x1/zm.svg)}.flag-icon-zw{background-image:url(../flags/4x3/zw.svg)}.flag-icon-zw.flag-icon-squared{background-image:url(../flags/1x1/zw.svg)}.flag-icon-es-ca{background-image:url(../flags/4x3/es-ca.svg)}.flag-icon-es-ca.flag-icon-squared{background-image:url(../flags/1x1/es-ca.svg)}.flag-icon-es-ga{background-image:url(../flags/4x3/es-ga.svg)}.flag-icon-es-ga.flag-icon-squared{background-image:url(../flags/1x1/es-ga.svg)}.flag-icon-eu{background-image:url(../flags/4x3/eu.svg)}.flag-icon-eu.flag-icon-squared{background-image:url(../flags/1x1/eu.svg)}.flag-icon-gb-eng{background-image:url(../flags/4x3/gb-eng.svg)}.flag-icon-gb-eng.flag-icon-squared{background-image:url(../flags/1x1/gb-eng.svg)}.flag-icon-gb-nir{background-image:url(../flags/4x3/gb-nir.svg)}.flag-icon-gb-nir.flag-icon-squared{background-image:url(../flags/1x1/gb-nir.svg)}.flag-icon-gb-sct{background-image:url(../flags/4x3/gb-sct.svg)}.flag-icon-gb-sct.flag-icon-squared{background-image:url(../flags/1x1/gb-sct.svg)}.flag-icon-gb-wls{background-image:url(../flags/4x3/gb-wls.svg)}.flag-icon-gb-wls.flag-icon-squared{background-image:url(../flags/1x1/gb-wls.svg)}.flag-icon-un{background-image:url(../flags/4x3/un.svg)}.flag-icon-un.flag-icon-squared{background-image:url(../flags/1x1/un.svg)}.flag-icon-xk{background-image:url(../flags/4x3/xk.svg)}.flag-icon-xk.flag-icon-squared{background-image:url(../flags/1x1/xk.svg)} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ad.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ad.svg new file mode 100644 index 0000000000..e7fc56abbc --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ad.svg @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ae.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ae.svg new file mode 100644 index 0000000000..739c5d4646 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ae.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/af.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/af.svg new file mode 100644 index 0000000000..90c34b8d5a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/af.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ag.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ag.svg new file mode 100644 index 0000000000..d0b2a83cbe --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ag.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ai.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ai.svg new file mode 100644 index 0000000000..472be200e7 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ai.svg @@ -0,0 +1,763 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/al.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/al.svg new file mode 100644 index 0000000000..75995ecde6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/al.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/am.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/am.svg new file mode 100644 index 0000000000..1198be035f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/am.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ao.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ao.svg new file mode 100644 index 0000000000..a5a25bf107 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ao.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/aq.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/aq.svg new file mode 100644 index 0000000000..80e682abd6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/aq.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ar.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ar.svg new file mode 100644 index 0000000000..1730ecac9e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ar.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/as.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/as.svg new file mode 100644 index 0000000000..b8d8162d0a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/as.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/at.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/at.svg new file mode 100644 index 0000000000..649d6efe0a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/at.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/au.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/au.svg new file mode 100644 index 0000000000..ca5d607ea0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/au.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/aw.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/aw.svg new file mode 100644 index 0000000000..248a08d69b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/aw.svg @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ax.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ax.svg new file mode 100644 index 0000000000..cdeb07e273 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ax.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/az.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/az.svg new file mode 100644 index 0000000000..0119e1ab4e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/az.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ba.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ba.svg new file mode 100644 index 0000000000..5b92b0ab54 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ba.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bb.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bb.svg new file mode 100644 index 0000000000..9d627842f1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bb.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bd.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bd.svg new file mode 100644 index 0000000000..4cb38cf503 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bd.svg @@ -0,0 +1,4 @@ + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/be.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/be.svg new file mode 100644 index 0000000000..01496c3cac --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/be.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bf.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bf.svg new file mode 100644 index 0000000000..a3c7c44a4f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bf.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bg.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bg.svg new file mode 100644 index 0000000000..5abe67f642 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bg.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bh.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bh.svg new file mode 100644 index 0000000000..22fba621a9 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bh.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bi.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bi.svg new file mode 100644 index 0000000000..cc11dcff2c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bi.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bj.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bj.svg new file mode 100644 index 0000000000..07c4c117ca --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bj.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bl.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bl.svg new file mode 100644 index 0000000000..0fa74e1c3c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bl.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bm.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bm.svg new file mode 100644 index 0000000000..a7057d24c0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bm.svg @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bn.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bn.svg new file mode 100644 index 0000000000..2e93aea326 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bn.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bo.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bo.svg new file mode 100644 index 0000000000..52a534fe2b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bo.svg @@ -0,0 +1,678 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bq.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bq.svg new file mode 100644 index 0000000000..cc872ef1b8 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bq.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/br.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/br.svg new file mode 100644 index 0000000000..8353e823fc --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/br.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bs.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bs.svg new file mode 100644 index 0000000000..decdebb1ef --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bs.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bt.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bt.svg new file mode 100644 index 0000000000..3bbbfaac74 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bt.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bv.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bv.svg new file mode 100644 index 0000000000..01c9ee147d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bv.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bw.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bw.svg new file mode 100644 index 0000000000..0bc5d3d34f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bw.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/by.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/by.svg new file mode 100644 index 0000000000..73e14f7a23 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/by.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bz.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bz.svg new file mode 100644 index 0000000000..0e9a27ca6a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bz.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ca.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ca.svg new file mode 100644 index 0000000000..6882f6da65 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ca.svg @@ -0,0 +1,4 @@ + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cc.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cc.svg new file mode 100644 index 0000000000..dd6e20003e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cc.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cd.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cd.svg new file mode 100644 index 0000000000..5da2a96892 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cd.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cf.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cf.svg new file mode 100644 index 0000000000..e924621528 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cf.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cg.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cg.svg new file mode 100644 index 0000000000..a52ba7e211 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cg.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ch.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ch.svg new file mode 100644 index 0000000000..773cdc8a3e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ch.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ci.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ci.svg new file mode 100644 index 0000000000..bd1e3f4141 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ci.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ck.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ck.svg new file mode 100644 index 0000000000..f2df0dbe34 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ck.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cl.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cl.svg new file mode 100644 index 0000000000..b808896721 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cl.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cm.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cm.svg new file mode 100644 index 0000000000..08b710bb87 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cm.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cn.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cn.svg new file mode 100644 index 0000000000..7873c1b4fa --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cn.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/co.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/co.svg new file mode 100644 index 0000000000..18d1c5f450 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/co.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cr.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cr.svg new file mode 100644 index 0000000000..a60a6dd614 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cr.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cu.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cu.svg new file mode 100644 index 0000000000..396817620d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cu.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cv.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cv.svg new file mode 100644 index 0000000000..a8311b2f83 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cv.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cw.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cw.svg new file mode 100644 index 0000000000..d7ba21865b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cw.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cx.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cx.svg new file mode 100644 index 0000000000..ef82c45363 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cx.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cy.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cy.svg new file mode 100644 index 0000000000..ba2b0f8908 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cy.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cz.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cz.svg new file mode 100644 index 0000000000..9557b6e88d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cz.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/de.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/de.svg new file mode 100644 index 0000000000..b9ea8a6134 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/de.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dj.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dj.svg new file mode 100644 index 0000000000..3f6b2e4f35 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dj.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dk.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dk.svg new file mode 100644 index 0000000000..51ff69feb0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dk.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dm.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dm.svg new file mode 100644 index 0000000000..405a4b6b96 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dm.svg @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/do.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/do.svg new file mode 100644 index 0000000000..03d3f3547b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/do.svg @@ -0,0 +1,6745 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dz.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dz.svg new file mode 100644 index 0000000000..37df0c8bba --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dz.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ec.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ec.svg new file mode 100644 index 0000000000..65fd0bad2e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ec.svg @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ee.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ee.svg new file mode 100644 index 0000000000..fbc9e33943 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ee.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/eg.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/eg.svg new file mode 100644 index 0000000000..2965b6afa0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/eg.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/eh.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/eh.svg new file mode 100644 index 0000000000..4c3feba1b3 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/eh.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/er.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/er.svg new file mode 100644 index 0000000000..86343349e3 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/er.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/es-ca.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/es-ca.svg new file mode 100644 index 0000000000..2a50685ddb --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/es-ca.svg @@ -0,0 +1,4 @@ + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/es-ga.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/es-ga.svg new file mode 100644 index 0000000000..5c55ff855b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/es-ga.svg @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/es.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/es.svg new file mode 100644 index 0000000000..d7030eb292 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/es.svg @@ -0,0 +1,547 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/et.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/et.svg new file mode 100644 index 0000000000..8b02f6b7ca --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/et.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/eu.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/eu.svg new file mode 100644 index 0000000000..b031d2d336 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/eu.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fi.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fi.svg new file mode 100644 index 0000000000..aff1304c6b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fi.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fj.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fj.svg new file mode 100644 index 0000000000..9d9c3029e1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fj.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fk.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fk.svg new file mode 100644 index 0000000000..12a34c46dc --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fk.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fm.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fm.svg new file mode 100644 index 0000000000..791fde9929 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fm.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fo.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fo.svg new file mode 100644 index 0000000000..b28915c013 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fr.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fr.svg new file mode 100644 index 0000000000..f8e3ca0d57 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fr.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ga.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ga.svg new file mode 100644 index 0000000000..16c81b3029 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ga.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-eng.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-eng.svg new file mode 100644 index 0000000000..18026d2941 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-eng.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-nir.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-nir.svg new file mode 100644 index 0000000000..6d8a3a3974 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-nir.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-sct.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-sct.svg new file mode 100644 index 0000000000..6987b0886b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-sct.svg @@ -0,0 +1,4 @@ + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-wls.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-wls.svg new file mode 100644 index 0000000000..3931a17954 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-wls.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb.svg new file mode 100644 index 0000000000..ef048dc2a1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gd.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gd.svg new file mode 100644 index 0000000000..cca37ba483 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gd.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ge.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ge.svg new file mode 100644 index 0000000000..ac1d87bb49 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ge.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gf.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gf.svg new file mode 100644 index 0000000000..c00a576342 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gf.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gg.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gg.svg new file mode 100644 index 0000000000..2d06a9f86f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gg.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gh.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gh.svg new file mode 100644 index 0000000000..4b6446d645 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gh.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gi.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gi.svg new file mode 100644 index 0000000000..39f5b2770c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gi.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gl.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gl.svg new file mode 100644 index 0000000000..7a026d995f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gl.svg @@ -0,0 +1,4 @@ + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gm.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gm.svg new file mode 100644 index 0000000000..b06ab6cb58 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gm.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gn.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gn.svg new file mode 100644 index 0000000000..8f8855da4b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gn.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gp.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gp.svg new file mode 100644 index 0000000000..0a5bdb0056 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gp.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gq.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gq.svg new file mode 100644 index 0000000000..8149406de4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gq.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gr.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gr.svg new file mode 100644 index 0000000000..4bc68fc1fa --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gr.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gs.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gs.svg new file mode 100644 index 0000000000..48f68b8b67 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gs.svg @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + L + + + E + + + O + + + T + + + E + + + R + + + R + + + R + + + R + + + R + + + E + + + O + + + O + + + A + + + A + + + A + + + M + + + P + + + P + + + P + + + I + + + T + + + T + + + M + + + G + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gt.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gt.svg new file mode 100644 index 0000000000..761801ca47 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gt.svg @@ -0,0 +1,204 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gu.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gu.svg new file mode 100644 index 0000000000..d6f5d535ca --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gu.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + G + + + U + + + A + + + M + + + + + + + + G + + + U + + + A + + + M + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gw.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gw.svg new file mode 100644 index 0000000000..064a593473 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gw.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gy.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gy.svg new file mode 100644 index 0000000000..57eb520903 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gy.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hk.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hk.svg new file mode 100644 index 0000000000..024c070555 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hk.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hm.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hm.svg new file mode 100644 index 0000000000..7e1f7e7e64 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hm.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hn.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hn.svg new file mode 100644 index 0000000000..c7c4c4acfd --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hn.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hr.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hr.svg new file mode 100644 index 0000000000..7ea00410d5 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hr.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ht.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ht.svg new file mode 100644 index 0000000000..920833a66e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ht.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hu.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hu.svg new file mode 100644 index 0000000000..94bc29f134 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hu.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/id.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/id.svg new file mode 100644 index 0000000000..6d2cf0941e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/id.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ie.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ie.svg new file mode 100644 index 0000000000..60448a9ded --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ie.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/il.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/il.svg new file mode 100644 index 0000000000..6cb4b1c1f7 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/il.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/im.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/im.svg new file mode 100644 index 0000000000..0f487f67d7 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/im.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/in.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/in.svg new file mode 100644 index 0000000000..e6557cd01a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/in.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/io.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/io.svg new file mode 100644 index 0000000000..4d809e03e6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/io.svg @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/iq.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/iq.svg new file mode 100644 index 0000000000..6b96774d97 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/iq.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ir.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ir.svg new file mode 100644 index 0000000000..79f66324f1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ir.svg @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/is.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/is.svg new file mode 100644 index 0000000000..08d1e68383 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/is.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/it.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/it.svg new file mode 100644 index 0000000000..615c58fb8f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/it.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/je.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/je.svg new file mode 100644 index 0000000000..c63ccb29ec --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/je.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/jm.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/jm.svg new file mode 100644 index 0000000000..c261da0971 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/jm.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/jo.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/jo.svg new file mode 100644 index 0000000000..ab1c62aaa1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/jo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/jp.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/jp.svg new file mode 100644 index 0000000000..dc7a64a5c7 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/jp.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ke.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ke.svg new file mode 100644 index 0000000000..0b82f3a6c3 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ke.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kg.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kg.svg new file mode 100644 index 0000000000..71ee7b8df5 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kg.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kh.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kh.svg new file mode 100644 index 0000000000..8c888f1681 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kh.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ki.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ki.svg new file mode 100644 index 0000000000..bfc5ccab46 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ki.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/km.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/km.svg new file mode 100644 index 0000000000..8f842ea8c0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/km.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kn.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kn.svg new file mode 100644 index 0000000000..4b2a2488be --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kn.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kp.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kp.svg new file mode 100644 index 0000000000..8eda6be874 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kp.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kr.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kr.svg new file mode 100644 index 0000000000..2db51b02a6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kr.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kw.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kw.svg new file mode 100644 index 0000000000..3d4047fff5 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kw.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ky.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ky.svg new file mode 100644 index 0000000000..b4ae00aa86 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ky.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kz.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kz.svg new file mode 100644 index 0000000000..f17bd6e0f8 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kz.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/la.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/la.svg new file mode 100644 index 0000000000..1e7686a935 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/la.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lb.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lb.svg new file mode 100644 index 0000000000..a047b0b948 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lb.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lc.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lc.svg new file mode 100644 index 0000000000..b13b8852aa --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lc.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/li.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/li.svg new file mode 100644 index 0000000000..cbed5cc84c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/li.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lk.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lk.svg new file mode 100644 index 0000000000..2b112155f6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lk.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lr.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lr.svg new file mode 100644 index 0000000000..0ae34e7282 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lr.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ls.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ls.svg new file mode 100644 index 0000000000..e71bb5bb70 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ls.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lt.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lt.svg new file mode 100644 index 0000000000..aa96cf3236 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lt.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lu.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lu.svg new file mode 100644 index 0000000000..6293671602 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lu.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lv.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lv.svg new file mode 100644 index 0000000000..5556de1a3d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lv.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ly.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ly.svg new file mode 100644 index 0000000000..fe0ed81b60 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ly.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ma.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ma.svg new file mode 100644 index 0000000000..85c99b3b9e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ma.svg @@ -0,0 +1,4 @@ + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mc.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mc.svg new file mode 100644 index 0000000000..d38822ddac --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mc.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/md.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/md.svg new file mode 100644 index 0000000000..86b2a96149 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/md.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/me.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/me.svg new file mode 100644 index 0000000000..56a19ed0a0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/me.svg @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mf.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mf.svg new file mode 100644 index 0000000000..310afce407 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mf.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mg.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mg.svg new file mode 100644 index 0000000000..f0375cc6be --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mg.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mh.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mh.svg new file mode 100644 index 0000000000..97f34631af --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mh.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mk.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mk.svg new file mode 100644 index 0000000000..da2e9a4c40 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mk.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ml.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ml.svg new file mode 100644 index 0000000000..1e4d98900c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ml.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mm.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mm.svg new file mode 100644 index 0000000000..5076184c32 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mm.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mn.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mn.svg new file mode 100644 index 0000000000..568fda088a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mn.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mo.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mo.svg new file mode 100644 index 0000000000..83d04ea28a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mp.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mp.svg new file mode 100644 index 0000000000..54a0ede9f2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mp.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mq.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mq.svg new file mode 100644 index 0000000000..7a69fb5842 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mq.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mr.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mr.svg new file mode 100644 index 0000000000..7da23e5f4e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mr.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ms.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ms.svg new file mode 100644 index 0000000000..d7d910d020 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ms.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mt.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mt.svg new file mode 100644 index 0000000000..96acc15e73 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mt.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mu.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mu.svg new file mode 100644 index 0000000000..773d3d59ee --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mu.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mv.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mv.svg new file mode 100644 index 0000000000..aa5ed5333e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mv.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mw.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mw.svg new file mode 100644 index 0000000000..a9521a08c1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mw.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mx.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mx.svg new file mode 100644 index 0000000000..a440656807 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mx.svg @@ -0,0 +1,378 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/my.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/my.svg new file mode 100644 index 0000000000..7ebe064b74 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/my.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mz.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mz.svg new file mode 100644 index 0000000000..113a205792 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mz.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/na.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/na.svg new file mode 100644 index 0000000000..b934fc157c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/na.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nc.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nc.svg new file mode 100644 index 0000000000..2bdf6ee5b5 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nc.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ne.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ne.svg new file mode 100644 index 0000000000..e76e44c0bb --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ne.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nf.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nf.svg new file mode 100644 index 0000000000..21495222f6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nf.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ng.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ng.svg new file mode 100644 index 0000000000..57d65d380a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ng.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ni.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ni.svg new file mode 100644 index 0000000000..8f68d42255 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ni.svg @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nl.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nl.svg new file mode 100644 index 0000000000..9db233dd4b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nl.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/no.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/no.svg new file mode 100644 index 0000000000..08ea5728d2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/no.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/np.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/np.svg new file mode 100644 index 0000000000..f34ee8c638 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/np.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nr.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nr.svg new file mode 100644 index 0000000000..282d80fa14 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nr.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nu.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nu.svg new file mode 100644 index 0000000000..aced440dc1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nu.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nz.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nz.svg new file mode 100644 index 0000000000..5283a96ed4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nz.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/om.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/om.svg new file mode 100644 index 0000000000..055d1e6986 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/om.svg @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pa.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pa.svg new file mode 100644 index 0000000000..57965b9ea2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pa.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pe.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pe.svg new file mode 100644 index 0000000000..40b87badbb --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pe.svg @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pf.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pf.svg new file mode 100644 index 0000000000..94ff90cb2c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pf.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pg.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pg.svg new file mode 100644 index 0000000000..7397754182 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pg.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ph.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ph.svg new file mode 100644 index 0000000000..681cf23ff5 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ph.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pk.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pk.svg new file mode 100644 index 0000000000..06b6022f0c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pk.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pl.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pl.svg new file mode 100644 index 0000000000..f7c12a1843 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pl.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pm.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pm.svg new file mode 100644 index 0000000000..3d4014a414 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pm.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pn.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pn.svg new file mode 100644 index 0000000000..47b0749a0a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pn.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pr.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pr.svg new file mode 100644 index 0000000000..79cf4c0991 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pr.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ps.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ps.svg new file mode 100644 index 0000000000..7c1ea3f9a6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ps.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pt.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pt.svg new file mode 100644 index 0000000000..425515054a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pt.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pw.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pw.svg new file mode 100644 index 0000000000..83bc3f71d5 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pw.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/py.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/py.svg new file mode 100644 index 0000000000..88f55e6f02 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/py.svg @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/qa.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/qa.svg new file mode 100644 index 0000000000..0bf30ea37f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/qa.svg @@ -0,0 +1,4 @@ + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/re.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/re.svg new file mode 100644 index 0000000000..027c9f32ce --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/re.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ro.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ro.svg new file mode 100644 index 0000000000..994992ab66 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ro.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/rs.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/rs.svg new file mode 100644 index 0000000000..3270f304c4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/rs.svg @@ -0,0 +1,296 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ru.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ru.svg new file mode 100644 index 0000000000..d643087430 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ru.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/rw.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/rw.svg new file mode 100644 index 0000000000..26e41f6ad7 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/rw.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sa.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sa.svg new file mode 100644 index 0000000000..1cc41b80bf --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sa.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sb.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sb.svg new file mode 100644 index 0000000000..f61bafd6a9 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sb.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sc.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sc.svg new file mode 100644 index 0000000000..65d8943d64 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sc.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sd.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sd.svg new file mode 100644 index 0000000000..72a729708b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sd.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/se.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/se.svg new file mode 100644 index 0000000000..e824395ebb --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/se.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sg.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sg.svg new file mode 100644 index 0000000000..1444e5b6ab --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sg.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sh.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sh.svg new file mode 100644 index 0000000000..599a09d73b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sh.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/si.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/si.svg new file mode 100644 index 0000000000..6de77ef473 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/si.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sj.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sj.svg new file mode 100644 index 0000000000..f4e5829735 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sj.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sk.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sk.svg new file mode 100644 index 0000000000..fd728680b0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sk.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sl.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sl.svg new file mode 100644 index 0000000000..18c76d781b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sl.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sm.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sm.svg new file mode 100644 index 0000000000..1ed18eed6c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sm.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + L + + + I + + + B + + + E + + + R + + + T + + + A + + + S + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sn.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sn.svg new file mode 100644 index 0000000000..fbbad5e026 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sn.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/so.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/so.svg new file mode 100644 index 0000000000..96d88a992a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/so.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sr.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sr.svg new file mode 100644 index 0000000000..a0ca03e367 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sr.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ss.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ss.svg new file mode 100644 index 0000000000..7e6a47ffd9 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ss.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/st.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/st.svg new file mode 100644 index 0000000000..da5df2990d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/st.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sv.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sv.svg new file mode 100644 index 0000000000..65b44baeec --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sv.svg @@ -0,0 +1,596 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sx.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sx.svg new file mode 100644 index 0000000000..5d9fadfa2e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sx.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sy.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sy.svg new file mode 100644 index 0000000000..904dc1f65c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sy.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sz.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sz.svg new file mode 100644 index 0000000000..b3009e4d92 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sz.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tc.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tc.svg new file mode 100644 index 0000000000..1029615f55 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tc.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/td.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/td.svg new file mode 100644 index 0000000000..e3e81ce2fd --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/td.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tf.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tf.svg new file mode 100644 index 0000000000..2061867c96 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tf.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tg.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tg.svg new file mode 100644 index 0000000000..2c1fd98fd4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tg.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/th.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/th.svg new file mode 100644 index 0000000000..86850f5fbf --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/th.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tj.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tj.svg new file mode 100644 index 0000000000..853a4a4555 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tj.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tk.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tk.svg new file mode 100644 index 0000000000..c5ff6b4cb4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tk.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tl.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tl.svg new file mode 100644 index 0000000000..ec6d44bd13 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tl.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tm.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tm.svg new file mode 100644 index 0000000000..d856424fe6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tm.svg @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tn.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tn.svg new file mode 100644 index 0000000000..d46a1cd9aa --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tn.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/to.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/to.svg new file mode 100644 index 0000000000..201d6bc39d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/to.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tr.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tr.svg new file mode 100644 index 0000000000..861d4ea580 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tr.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tt.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tt.svg new file mode 100644 index 0000000000..87e439a07d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tt.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tv.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tv.svg new file mode 100644 index 0000000000..f30c8f3710 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tv.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tw.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tw.svg new file mode 100644 index 0000000000..5f284fc612 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tw.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tz.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tz.svg new file mode 100644 index 0000000000..f993ff15d6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tz.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ua.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ua.svg new file mode 100644 index 0000000000..18ebe0d49d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ua.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ug.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ug.svg new file mode 100644 index 0000000000..d9be945945 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ug.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/um.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/um.svg new file mode 100644 index 0000000000..25b08ce6ab --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/um.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/un.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/un.svg new file mode 100644 index 0000000000..1d50ea9250 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/un.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/us.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/us.svg new file mode 100644 index 0000000000..31f90c6c3a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/us.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/uy.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/uy.svg new file mode 100644 index 0000000000..0194a7cb0b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/uy.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/uz.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/uz.svg new file mode 100644 index 0000000000..641af1b826 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/uz.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/va.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/va.svg new file mode 100644 index 0000000000..7da6d3e739 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/va.svg @@ -0,0 +1,479 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vc.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vc.svg new file mode 100644 index 0000000000..ee72f781ac --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vc.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ve.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ve.svg new file mode 100644 index 0000000000..205fe848a3 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ve.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vg.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vg.svg new file mode 100644 index 0000000000..9572de3480 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vg.svg @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vi.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vi.svg new file mode 100644 index 0000000000..2740f24ffa --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vi.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vn.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vn.svg new file mode 100644 index 0000000000..6b158145ad --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vn.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vu.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vu.svg new file mode 100644 index 0000000000..397d30e60b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vu.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/wf.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/wf.svg new file mode 100644 index 0000000000..bb726a7c2e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/wf.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ws.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ws.svg new file mode 100644 index 0000000000..155ad7b558 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ws.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/xk.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/xk.svg new file mode 100644 index 0000000000..69146ca449 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/xk.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ye.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ye.svg new file mode 100644 index 0000000000..d49d2c414e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ye.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/yt.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/yt.svg new file mode 100644 index 0000000000..7bf3837379 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/yt.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/za.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/za.svg new file mode 100644 index 0000000000..9bae96fe3e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/za.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/zm.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/zm.svg new file mode 100644 index 0000000000..105f1076ad --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/zm.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/zw.svg b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/zw.svg new file mode 100644 index 0000000000..ca5b7a20f9 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/zw.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/css/all.css b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/css/all.css new file mode 100644 index 0000000000..1893247a7d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/css/all.css @@ -0,0 +1,8003 @@ +/*! + * Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2023 Fonticons, Inc. + */ +.fa { + font-family: var(--fa-style-family, "Font Awesome 6 Free"); + font-weight: var(--fa-style, 900); } + +.fa, +.fa-classic, +.fa-sharp, +.fas, +.fa-solid, +.far, +.fa-regular, +.fab, +.fa-brands { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: var(--fa-display, inline-block); + font-style: normal; + font-variant: normal; + line-height: 1; + text-rendering: auto; } + +.fas, +.fa-classic, +.fa-solid, +.far, +.fa-regular { + font-family: 'Font Awesome 6 Free'; } + +.fab, +.fa-brands { + font-family: 'Font Awesome 6 Brands'; } + +.fa-1x { + font-size: 1em; } + +.fa-2x { + font-size: 2em; } + +.fa-3x { + font-size: 3em; } + +.fa-4x { + font-size: 4em; } + +.fa-5x { + font-size: 5em; } + +.fa-6x { + font-size: 6em; } + +.fa-7x { + font-size: 7em; } + +.fa-8x { + font-size: 8em; } + +.fa-9x { + font-size: 9em; } + +.fa-10x { + font-size: 10em; } + +.fa-2xs { + font-size: 0.625em; + line-height: 0.1em; + vertical-align: 0.225em; } + +.fa-xs { + font-size: 0.75em; + line-height: 0.08333em; + vertical-align: 0.125em; } + +.fa-sm { + font-size: 0.875em; + line-height: 0.07143em; + vertical-align: 0.05357em; } + +.fa-lg { + font-size: 1.25em; + line-height: 0.05em; + vertical-align: -0.075em; } + +.fa-xl { + font-size: 1.5em; + line-height: 0.04167em; + vertical-align: -0.125em; } + +.fa-2xl { + font-size: 2em; + line-height: 0.03125em; + vertical-align: -0.1875em; } + +.fa-fw { + text-align: center; + width: 1.25em; } + +.fa-ul { + list-style-type: none; + margin-left: var(--fa-li-margin, 2.5em); + padding-left: 0; } + .fa-ul > li { + position: relative; } + +.fa-li { + left: calc(var(--fa-li-width, 2em) * -1); + position: absolute; + text-align: center; + width: var(--fa-li-width, 2em); + line-height: inherit; } + +.fa-border { + border-color: var(--fa-border-color, #eee); + border-radius: var(--fa-border-radius, 0.1em); + border-style: var(--fa-border-style, solid); + border-width: var(--fa-border-width, 0.08em); + padding: var(--fa-border-padding, 0.2em 0.25em 0.15em); } + +.fa-pull-left { + float: left; + margin-right: var(--fa-pull-margin, 0.3em); } + +.fa-pull-right { + float: right; + margin-left: var(--fa-pull-margin, 0.3em); } + +.fa-beat { + -webkit-animation-name: fa-beat; + animation-name: fa-beat; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, ease-in-out); + animation-timing-function: var(--fa-animation-timing, ease-in-out); } + +.fa-bounce { + -webkit-animation-name: fa-bounce; + animation-name: fa-bounce; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); + animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); } + +.fa-fade { + -webkit-animation-name: fa-fade; + animation-name: fa-fade; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); + animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); } + +.fa-beat-fade { + -webkit-animation-name: fa-beat-fade; + animation-name: fa-beat-fade; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); + animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); } + +.fa-flip { + -webkit-animation-name: fa-flip; + animation-name: fa-flip; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, ease-in-out); + animation-timing-function: var(--fa-animation-timing, ease-in-out); } + +.fa-shake { + -webkit-animation-name: fa-shake; + animation-name: fa-shake; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, linear); + animation-timing-function: var(--fa-animation-timing, linear); } + +.fa-spin { + -webkit-animation-name: fa-spin; + animation-name: fa-spin; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 2s); + animation-duration: var(--fa-animation-duration, 2s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, linear); + animation-timing-function: var(--fa-animation-timing, linear); } + +.fa-spin-reverse { + --fa-animation-direction: reverse; } + +.fa-pulse, +.fa-spin-pulse { + -webkit-animation-name: fa-spin; + animation-name: fa-spin; + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, steps(8)); + animation-timing-function: var(--fa-animation-timing, steps(8)); } + +@media (prefers-reduced-motion: reduce) { + .fa-beat, + .fa-bounce, + .fa-fade, + .fa-beat-fade, + .fa-flip, + .fa-pulse, + .fa-shake, + .fa-spin, + .fa-spin-pulse { + -webkit-animation-delay: -1ms; + animation-delay: -1ms; + -webkit-animation-duration: 1ms; + animation-duration: 1ms; + -webkit-animation-iteration-count: 1; + animation-iteration-count: 1; + -webkit-transition-delay: 0s; + transition-delay: 0s; + -webkit-transition-duration: 0s; + transition-duration: 0s; } } + +@-webkit-keyframes fa-beat { + 0%, 90% { + -webkit-transform: scale(1); + transform: scale(1); } + 45% { + -webkit-transform: scale(var(--fa-beat-scale, 1.25)); + transform: scale(var(--fa-beat-scale, 1.25)); } } + +@keyframes fa-beat { + 0%, 90% { + -webkit-transform: scale(1); + transform: scale(1); } + 45% { + -webkit-transform: scale(var(--fa-beat-scale, 1.25)); + transform: scale(var(--fa-beat-scale, 1.25)); } } + +@-webkit-keyframes fa-bounce { + 0% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); } + 10% { + -webkit-transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); + transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); } + 30% { + -webkit-transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); + transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); } + 50% { + -webkit-transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); + transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); } + 57% { + -webkit-transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); + transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); } + 64% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); } + 100% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); } } + +@keyframes fa-bounce { + 0% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); } + 10% { + -webkit-transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); + transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); } + 30% { + -webkit-transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); + transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); } + 50% { + -webkit-transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); + transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); } + 57% { + -webkit-transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); + transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); } + 64% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); } + 100% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); } } + +@-webkit-keyframes fa-fade { + 50% { + opacity: var(--fa-fade-opacity, 0.4); } } + +@keyframes fa-fade { + 50% { + opacity: var(--fa-fade-opacity, 0.4); } } + +@-webkit-keyframes fa-beat-fade { + 0%, 100% { + opacity: var(--fa-beat-fade-opacity, 0.4); + -webkit-transform: scale(1); + transform: scale(1); } + 50% { + opacity: 1; + -webkit-transform: scale(var(--fa-beat-fade-scale, 1.125)); + transform: scale(var(--fa-beat-fade-scale, 1.125)); } } + +@keyframes fa-beat-fade { + 0%, 100% { + opacity: var(--fa-beat-fade-opacity, 0.4); + -webkit-transform: scale(1); + transform: scale(1); } + 50% { + opacity: 1; + -webkit-transform: scale(var(--fa-beat-fade-scale, 1.125)); + transform: scale(var(--fa-beat-fade-scale, 1.125)); } } + +@-webkit-keyframes fa-flip { + 50% { + -webkit-transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); + transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } } + +@keyframes fa-flip { + 50% { + -webkit-transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); + transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } } + +@-webkit-keyframes fa-shake { + 0% { + -webkit-transform: rotate(-15deg); + transform: rotate(-15deg); } + 4% { + -webkit-transform: rotate(15deg); + transform: rotate(15deg); } + 8%, 24% { + -webkit-transform: rotate(-18deg); + transform: rotate(-18deg); } + 12%, 28% { + -webkit-transform: rotate(18deg); + transform: rotate(18deg); } + 16% { + -webkit-transform: rotate(-22deg); + transform: rotate(-22deg); } + 20% { + -webkit-transform: rotate(22deg); + transform: rotate(22deg); } + 32% { + -webkit-transform: rotate(-12deg); + transform: rotate(-12deg); } + 36% { + -webkit-transform: rotate(12deg); + transform: rotate(12deg); } + 40%, 100% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } } + +@keyframes fa-shake { + 0% { + -webkit-transform: rotate(-15deg); + transform: rotate(-15deg); } + 4% { + -webkit-transform: rotate(15deg); + transform: rotate(15deg); } + 8%, 24% { + -webkit-transform: rotate(-18deg); + transform: rotate(-18deg); } + 12%, 28% { + -webkit-transform: rotate(18deg); + transform: rotate(18deg); } + 16% { + -webkit-transform: rotate(-22deg); + transform: rotate(-22deg); } + 20% { + -webkit-transform: rotate(22deg); + transform: rotate(22deg); } + 32% { + -webkit-transform: rotate(-12deg); + transform: rotate(-12deg); } + 36% { + -webkit-transform: rotate(12deg); + transform: rotate(12deg); } + 40%, 100% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } } + +@-webkit-keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + +@keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + +.fa-rotate-90 { + -webkit-transform: rotate(90deg); + transform: rotate(90deg); } + +.fa-rotate-180 { + -webkit-transform: rotate(180deg); + transform: rotate(180deg); } + +.fa-rotate-270 { + -webkit-transform: rotate(270deg); + transform: rotate(270deg); } + +.fa-flip-horizontal { + -webkit-transform: scale(-1, 1); + transform: scale(-1, 1); } + +.fa-flip-vertical { + -webkit-transform: scale(1, -1); + transform: scale(1, -1); } + +.fa-flip-both, +.fa-flip-horizontal.fa-flip-vertical { + -webkit-transform: scale(-1, -1); + transform: scale(-1, -1); } + +.fa-rotate-by { + -webkit-transform: rotate(var(--fa-rotate-angle, none)); + transform: rotate(var(--fa-rotate-angle, none)); } + +.fa-stack { + display: inline-block; + height: 2em; + line-height: 2em; + position: relative; + vertical-align: middle; + width: 2.5em; } + +.fa-stack-1x, +.fa-stack-2x { + left: 0; + position: absolute; + text-align: center; + width: 100%; + z-index: var(--fa-stack-z-index, auto); } + +.fa-stack-1x { + line-height: inherit; } + +.fa-stack-2x { + font-size: 2em; } + +.fa-inverse { + color: var(--fa-inverse, #fff); } + +/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen +readers do not read off random characters that represent icons */ + +.fa-0::before { + content: "\30"; } + +.fa-1::before { + content: "\31"; } + +.fa-2::before { + content: "\32"; } + +.fa-3::before { + content: "\33"; } + +.fa-4::before { + content: "\34"; } + +.fa-5::before { + content: "\35"; } + +.fa-6::before { + content: "\36"; } + +.fa-7::before { + content: "\37"; } + +.fa-8::before { + content: "\38"; } + +.fa-9::before { + content: "\39"; } + +.fa-fill-drip::before { + content: "\f576"; } + +.fa-arrows-to-circle::before { + content: "\e4bd"; } + +.fa-circle-chevron-right::before { + content: "\f138"; } + +.fa-chevron-circle-right::before { + content: "\f138"; } + +.fa-at::before { + content: "\40"; } + +.fa-trash-can::before { + content: "\f2ed"; } + +.fa-trash-alt::before { + content: "\f2ed"; } + +.fa-text-height::before { + content: "\f034"; } + +.fa-user-xmark::before { + content: "\f235"; } + +.fa-user-times::before { + content: "\f235"; } + +.fa-stethoscope::before { + content: "\f0f1"; } + +.fa-message::before { + content: "\f27a"; } + +.fa-comment-alt::before { + content: "\f27a"; } + +.fa-info::before { + content: "\f129"; } + +.fa-down-left-and-up-right-to-center::before { + content: "\f422"; } + +.fa-compress-alt::before { + content: "\f422"; } + +.fa-explosion::before { + content: "\e4e9"; } + +.fa-file-lines::before { + content: "\f15c"; } + +.fa-file-alt::before { + content: "\f15c"; } + +.fa-file-text::before { + content: "\f15c"; } + +.fa-wave-square::before { + content: "\f83e"; } + +.fa-ring::before { + content: "\f70b"; } + +.fa-building-un::before { + content: "\e4d9"; } + +.fa-dice-three::before { + content: "\f527"; } + +.fa-calendar-days::before { + content: "\f073"; } + +.fa-calendar-alt::before { + content: "\f073"; } + +.fa-anchor-circle-check::before { + content: "\e4aa"; } + +.fa-building-circle-arrow-right::before { + content: "\e4d1"; } + +.fa-volleyball::before { + content: "\f45f"; } + +.fa-volleyball-ball::before { + content: "\f45f"; } + +.fa-arrows-up-to-line::before { + content: "\e4c2"; } + +.fa-sort-down::before { + content: "\f0dd"; } + +.fa-sort-desc::before { + content: "\f0dd"; } + +.fa-circle-minus::before { + content: "\f056"; } + +.fa-minus-circle::before { + content: "\f056"; } + +.fa-door-open::before { + content: "\f52b"; } + +.fa-right-from-bracket::before { + content: "\f2f5"; } + +.fa-sign-out-alt::before { + content: "\f2f5"; } + +.fa-atom::before { + content: "\f5d2"; } + +.fa-soap::before { + content: "\e06e"; } + +.fa-icons::before { + content: "\f86d"; } + +.fa-heart-music-camera-bolt::before { + content: "\f86d"; } + +.fa-microphone-lines-slash::before { + content: "\f539"; } + +.fa-microphone-alt-slash::before { + content: "\f539"; } + +.fa-bridge-circle-check::before { + content: "\e4c9"; } + +.fa-pump-medical::before { + content: "\e06a"; } + +.fa-fingerprint::before { + content: "\f577"; } + +.fa-hand-point-right::before { + content: "\f0a4"; } + +.fa-magnifying-glass-location::before { + content: "\f689"; } + +.fa-search-location::before { + content: "\f689"; } + +.fa-forward-step::before { + content: "\f051"; } + +.fa-step-forward::before { + content: "\f051"; } + +.fa-face-smile-beam::before { + content: "\f5b8"; } + +.fa-smile-beam::before { + content: "\f5b8"; } + +.fa-flag-checkered::before { + content: "\f11e"; } + +.fa-football::before { + content: "\f44e"; } + +.fa-football-ball::before { + content: "\f44e"; } + +.fa-school-circle-exclamation::before { + content: "\e56c"; } + +.fa-crop::before { + content: "\f125"; } + +.fa-angles-down::before { + content: "\f103"; } + +.fa-angle-double-down::before { + content: "\f103"; } + +.fa-users-rectangle::before { + content: "\e594"; } + +.fa-people-roof::before { + content: "\e537"; } + +.fa-people-line::before { + content: "\e534"; } + +.fa-beer-mug-empty::before { + content: "\f0fc"; } + +.fa-beer::before { + content: "\f0fc"; } + +.fa-diagram-predecessor::before { + content: "\e477"; } + +.fa-arrow-up-long::before { + content: "\f176"; } + +.fa-long-arrow-up::before { + content: "\f176"; } + +.fa-fire-flame-simple::before { + content: "\f46a"; } + +.fa-burn::before { + content: "\f46a"; } + +.fa-person::before { + content: "\f183"; } + +.fa-male::before { + content: "\f183"; } + +.fa-laptop::before { + content: "\f109"; } + +.fa-file-csv::before { + content: "\f6dd"; } + +.fa-menorah::before { + content: "\f676"; } + +.fa-truck-plane::before { + content: "\e58f"; } + +.fa-record-vinyl::before { + content: "\f8d9"; } + +.fa-face-grin-stars::before { + content: "\f587"; } + +.fa-grin-stars::before { + content: "\f587"; } + +.fa-bong::before { + content: "\f55c"; } + +.fa-spaghetti-monster-flying::before { + content: "\f67b"; } + +.fa-pastafarianism::before { + content: "\f67b"; } + +.fa-arrow-down-up-across-line::before { + content: "\e4af"; } + +.fa-spoon::before { + content: "\f2e5"; } + +.fa-utensil-spoon::before { + content: "\f2e5"; } + +.fa-jar-wheat::before { + content: "\e517"; } + +.fa-envelopes-bulk::before { + content: "\f674"; } + +.fa-mail-bulk::before { + content: "\f674"; } + +.fa-file-circle-exclamation::before { + content: "\e4eb"; } + +.fa-circle-h::before { + content: "\f47e"; } + +.fa-hospital-symbol::before { + content: "\f47e"; } + +.fa-pager::before { + content: "\f815"; } + +.fa-address-book::before { + content: "\f2b9"; } + +.fa-contact-book::before { + content: "\f2b9"; } + +.fa-strikethrough::before { + content: "\f0cc"; } + +.fa-k::before { + content: "\4b"; } + +.fa-landmark-flag::before { + content: "\e51c"; } + +.fa-pencil::before { + content: "\f303"; } + +.fa-pencil-alt::before { + content: "\f303"; } + +.fa-backward::before { + content: "\f04a"; } + +.fa-caret-right::before { + content: "\f0da"; } + +.fa-comments::before { + content: "\f086"; } + +.fa-paste::before { + content: "\f0ea"; } + +.fa-file-clipboard::before { + content: "\f0ea"; } + +.fa-code-pull-request::before { + content: "\e13c"; } + +.fa-clipboard-list::before { + content: "\f46d"; } + +.fa-truck-ramp-box::before { + content: "\f4de"; } + +.fa-truck-loading::before { + content: "\f4de"; } + +.fa-user-check::before { + content: "\f4fc"; } + +.fa-vial-virus::before { + content: "\e597"; } + +.fa-sheet-plastic::before { + content: "\e571"; } + +.fa-blog::before { + content: "\f781"; } + +.fa-user-ninja::before { + content: "\f504"; } + +.fa-person-arrow-up-from-line::before { + content: "\e539"; } + +.fa-scroll-torah::before { + content: "\f6a0"; } + +.fa-torah::before { + content: "\f6a0"; } + +.fa-broom-ball::before { + content: "\f458"; } + +.fa-quidditch::before { + content: "\f458"; } + +.fa-quidditch-broom-ball::before { + content: "\f458"; } + +.fa-toggle-off::before { + content: "\f204"; } + +.fa-box-archive::before { + content: "\f187"; } + +.fa-archive::before { + content: "\f187"; } + +.fa-person-drowning::before { + content: "\e545"; } + +.fa-arrow-down-9-1::before { + content: "\f886"; } + +.fa-sort-numeric-desc::before { + content: "\f886"; } + +.fa-sort-numeric-down-alt::before { + content: "\f886"; } + +.fa-face-grin-tongue-squint::before { + content: "\f58a"; } + +.fa-grin-tongue-squint::before { + content: "\f58a"; } + +.fa-spray-can::before { + content: "\f5bd"; } + +.fa-truck-monster::before { + content: "\f63b"; } + +.fa-w::before { + content: "\57"; } + +.fa-earth-africa::before { + content: "\f57c"; } + +.fa-globe-africa::before { + content: "\f57c"; } + +.fa-rainbow::before { + content: "\f75b"; } + +.fa-circle-notch::before { + content: "\f1ce"; } + +.fa-tablet-screen-button::before { + content: "\f3fa"; } + +.fa-tablet-alt::before { + content: "\f3fa"; } + +.fa-paw::before { + content: "\f1b0"; } + +.fa-cloud::before { + content: "\f0c2"; } + +.fa-trowel-bricks::before { + content: "\e58a"; } + +.fa-face-flushed::before { + content: "\f579"; } + +.fa-flushed::before { + content: "\f579"; } + +.fa-hospital-user::before { + content: "\f80d"; } + +.fa-tent-arrow-left-right::before { + content: "\e57f"; } + +.fa-gavel::before { + content: "\f0e3"; } + +.fa-legal::before { + content: "\f0e3"; } + +.fa-binoculars::before { + content: "\f1e5"; } + +.fa-microphone-slash::before { + content: "\f131"; } + +.fa-box-tissue::before { + content: "\e05b"; } + +.fa-motorcycle::before { + content: "\f21c"; } + +.fa-bell-concierge::before { + content: "\f562"; } + +.fa-concierge-bell::before { + content: "\f562"; } + +.fa-pen-ruler::before { + content: "\f5ae"; } + +.fa-pencil-ruler::before { + content: "\f5ae"; } + +.fa-people-arrows::before { + content: "\e068"; } + +.fa-people-arrows-left-right::before { + content: "\e068"; } + +.fa-mars-and-venus-burst::before { + content: "\e523"; } + +.fa-square-caret-right::before { + content: "\f152"; } + +.fa-caret-square-right::before { + content: "\f152"; } + +.fa-scissors::before { + content: "\f0c4"; } + +.fa-cut::before { + content: "\f0c4"; } + +.fa-sun-plant-wilt::before { + content: "\e57a"; } + +.fa-toilets-portable::before { + content: "\e584"; } + +.fa-hockey-puck::before { + content: "\f453"; } + +.fa-table::before { + content: "\f0ce"; } + +.fa-magnifying-glass-arrow-right::before { + content: "\e521"; } + +.fa-tachograph-digital::before { + content: "\f566"; } + +.fa-digital-tachograph::before { + content: "\f566"; } + +.fa-users-slash::before { + content: "\e073"; } + +.fa-clover::before { + content: "\e139"; } + +.fa-reply::before { + content: "\f3e5"; } + +.fa-mail-reply::before { + content: "\f3e5"; } + +.fa-star-and-crescent::before { + content: "\f699"; } + +.fa-house-fire::before { + content: "\e50c"; } + +.fa-square-minus::before { + content: "\f146"; } + +.fa-minus-square::before { + content: "\f146"; } + +.fa-helicopter::before { + content: "\f533"; } + +.fa-compass::before { + content: "\f14e"; } + +.fa-square-caret-down::before { + content: "\f150"; } + +.fa-caret-square-down::before { + content: "\f150"; } + +.fa-file-circle-question::before { + content: "\e4ef"; } + +.fa-laptop-code::before { + content: "\f5fc"; } + +.fa-swatchbook::before { + content: "\f5c3"; } + +.fa-prescription-bottle::before { + content: "\f485"; } + +.fa-bars::before { + content: "\f0c9"; } + +.fa-navicon::before { + content: "\f0c9"; } + +.fa-people-group::before { + content: "\e533"; } + +.fa-hourglass-end::before { + content: "\f253"; } + +.fa-hourglass-3::before { + content: "\f253"; } + +.fa-heart-crack::before { + content: "\f7a9"; } + +.fa-heart-broken::before { + content: "\f7a9"; } + +.fa-square-up-right::before { + content: "\f360"; } + +.fa-external-link-square-alt::before { + content: "\f360"; } + +.fa-face-kiss-beam::before { + content: "\f597"; } + +.fa-kiss-beam::before { + content: "\f597"; } + +.fa-film::before { + content: "\f008"; } + +.fa-ruler-horizontal::before { + content: "\f547"; } + +.fa-people-robbery::before { + content: "\e536"; } + +.fa-lightbulb::before { + content: "\f0eb"; } + +.fa-caret-left::before { + content: "\f0d9"; } + +.fa-circle-exclamation::before { + content: "\f06a"; } + +.fa-exclamation-circle::before { + content: "\f06a"; } + +.fa-school-circle-xmark::before { + content: "\e56d"; } + +.fa-arrow-right-from-bracket::before { + content: "\f08b"; } + +.fa-sign-out::before { + content: "\f08b"; } + +.fa-circle-chevron-down::before { + content: "\f13a"; } + +.fa-chevron-circle-down::before { + content: "\f13a"; } + +.fa-unlock-keyhole::before { + content: "\f13e"; } + +.fa-unlock-alt::before { + content: "\f13e"; } + +.fa-cloud-showers-heavy::before { + content: "\f740"; } + +.fa-headphones-simple::before { + content: "\f58f"; } + +.fa-headphones-alt::before { + content: "\f58f"; } + +.fa-sitemap::before { + content: "\f0e8"; } + +.fa-circle-dollar-to-slot::before { + content: "\f4b9"; } + +.fa-donate::before { + content: "\f4b9"; } + +.fa-memory::before { + content: "\f538"; } + +.fa-road-spikes::before { + content: "\e568"; } + +.fa-fire-burner::before { + content: "\e4f1"; } + +.fa-flag::before { + content: "\f024"; } + +.fa-hanukiah::before { + content: "\f6e6"; } + +.fa-feather::before { + content: "\f52d"; } + +.fa-volume-low::before { + content: "\f027"; } + +.fa-volume-down::before { + content: "\f027"; } + +.fa-comment-slash::before { + content: "\f4b3"; } + +.fa-cloud-sun-rain::before { + content: "\f743"; } + +.fa-compress::before { + content: "\f066"; } + +.fa-wheat-awn::before { + content: "\e2cd"; } + +.fa-wheat-alt::before { + content: "\e2cd"; } + +.fa-ankh::before { + content: "\f644"; } + +.fa-hands-holding-child::before { + content: "\e4fa"; } + +.fa-asterisk::before { + content: "\2a"; } + +.fa-square-check::before { + content: "\f14a"; } + +.fa-check-square::before { + content: "\f14a"; } + +.fa-peseta-sign::before { + content: "\e221"; } + +.fa-heading::before { + content: "\f1dc"; } + +.fa-header::before { + content: "\f1dc"; } + +.fa-ghost::before { + content: "\f6e2"; } + +.fa-list::before { + content: "\f03a"; } + +.fa-list-squares::before { + content: "\f03a"; } + +.fa-square-phone-flip::before { + content: "\f87b"; } + +.fa-phone-square-alt::before { + content: "\f87b"; } + +.fa-cart-plus::before { + content: "\f217"; } + +.fa-gamepad::before { + content: "\f11b"; } + +.fa-circle-dot::before { + content: "\f192"; } + +.fa-dot-circle::before { + content: "\f192"; } + +.fa-face-dizzy::before { + content: "\f567"; } + +.fa-dizzy::before { + content: "\f567"; } + +.fa-egg::before { + content: "\f7fb"; } + +.fa-house-medical-circle-xmark::before { + content: "\e513"; } + +.fa-campground::before { + content: "\f6bb"; } + +.fa-folder-plus::before { + content: "\f65e"; } + +.fa-futbol::before { + content: "\f1e3"; } + +.fa-futbol-ball::before { + content: "\f1e3"; } + +.fa-soccer-ball::before { + content: "\f1e3"; } + +.fa-paintbrush::before { + content: "\f1fc"; } + +.fa-paint-brush::before { + content: "\f1fc"; } + +.fa-lock::before { + content: "\f023"; } + +.fa-gas-pump::before { + content: "\f52f"; } + +.fa-hot-tub-person::before { + content: "\f593"; } + +.fa-hot-tub::before { + content: "\f593"; } + +.fa-map-location::before { + content: "\f59f"; } + +.fa-map-marked::before { + content: "\f59f"; } + +.fa-house-flood-water::before { + content: "\e50e"; } + +.fa-tree::before { + content: "\f1bb"; } + +.fa-bridge-lock::before { + content: "\e4cc"; } + +.fa-sack-dollar::before { + content: "\f81d"; } + +.fa-pen-to-square::before { + content: "\f044"; } + +.fa-edit::before { + content: "\f044"; } + +.fa-car-side::before { + content: "\f5e4"; } + +.fa-share-nodes::before { + content: "\f1e0"; } + +.fa-share-alt::before { + content: "\f1e0"; } + +.fa-heart-circle-minus::before { + content: "\e4ff"; } + +.fa-hourglass-half::before { + content: "\f252"; } + +.fa-hourglass-2::before { + content: "\f252"; } + +.fa-microscope::before { + content: "\f610"; } + +.fa-sink::before { + content: "\e06d"; } + +.fa-bag-shopping::before { + content: "\f290"; } + +.fa-shopping-bag::before { + content: "\f290"; } + +.fa-arrow-down-z-a::before { + content: "\f881"; } + +.fa-sort-alpha-desc::before { + content: "\f881"; } + +.fa-sort-alpha-down-alt::before { + content: "\f881"; } + +.fa-mitten::before { + content: "\f7b5"; } + +.fa-person-rays::before { + content: "\e54d"; } + +.fa-users::before { + content: "\f0c0"; } + +.fa-eye-slash::before { + content: "\f070"; } + +.fa-flask-vial::before { + content: "\e4f3"; } + +.fa-hand::before { + content: "\f256"; } + +.fa-hand-paper::before { + content: "\f256"; } + +.fa-om::before { + content: "\f679"; } + +.fa-worm::before { + content: "\e599"; } + +.fa-house-circle-xmark::before { + content: "\e50b"; } + +.fa-plug::before { + content: "\f1e6"; } + +.fa-chevron-up::before { + content: "\f077"; } + +.fa-hand-spock::before { + content: "\f259"; } + +.fa-stopwatch::before { + content: "\f2f2"; } + +.fa-face-kiss::before { + content: "\f596"; } + +.fa-kiss::before { + content: "\f596"; } + +.fa-bridge-circle-xmark::before { + content: "\e4cb"; } + +.fa-face-grin-tongue::before { + content: "\f589"; } + +.fa-grin-tongue::before { + content: "\f589"; } + +.fa-chess-bishop::before { + content: "\f43a"; } + +.fa-face-grin-wink::before { + content: "\f58c"; } + +.fa-grin-wink::before { + content: "\f58c"; } + +.fa-ear-deaf::before { + content: "\f2a4"; } + +.fa-deaf::before { + content: "\f2a4"; } + +.fa-deafness::before { + content: "\f2a4"; } + +.fa-hard-of-hearing::before { + content: "\f2a4"; } + +.fa-road-circle-check::before { + content: "\e564"; } + +.fa-dice-five::before { + content: "\f523"; } + +.fa-square-rss::before { + content: "\f143"; } + +.fa-rss-square::before { + content: "\f143"; } + +.fa-land-mine-on::before { + content: "\e51b"; } + +.fa-i-cursor::before { + content: "\f246"; } + +.fa-stamp::before { + content: "\f5bf"; } + +.fa-stairs::before { + content: "\e289"; } + +.fa-i::before { + content: "\49"; } + +.fa-hryvnia-sign::before { + content: "\f6f2"; } + +.fa-hryvnia::before { + content: "\f6f2"; } + +.fa-pills::before { + content: "\f484"; } + +.fa-face-grin-wide::before { + content: "\f581"; } + +.fa-grin-alt::before { + content: "\f581"; } + +.fa-tooth::before { + content: "\f5c9"; } + +.fa-v::before { + content: "\56"; } + +.fa-bangladeshi-taka-sign::before { + content: "\e2e6"; } + +.fa-bicycle::before { + content: "\f206"; } + +.fa-staff-snake::before { + content: "\e579"; } + +.fa-rod-asclepius::before { + content: "\e579"; } + +.fa-rod-snake::before { + content: "\e579"; } + +.fa-staff-aesculapius::before { + content: "\e579"; } + +.fa-head-side-cough-slash::before { + content: "\e062"; } + +.fa-truck-medical::before { + content: "\f0f9"; } + +.fa-ambulance::before { + content: "\f0f9"; } + +.fa-wheat-awn-circle-exclamation::before { + content: "\e598"; } + +.fa-snowman::before { + content: "\f7d0"; } + +.fa-mortar-pestle::before { + content: "\f5a7"; } + +.fa-road-barrier::before { + content: "\e562"; } + +.fa-school::before { + content: "\f549"; } + +.fa-igloo::before { + content: "\f7ae"; } + +.fa-joint::before { + content: "\f595"; } + +.fa-angle-right::before { + content: "\f105"; } + +.fa-horse::before { + content: "\f6f0"; } + +.fa-q::before { + content: "\51"; } + +.fa-g::before { + content: "\47"; } + +.fa-notes-medical::before { + content: "\f481"; } + +.fa-temperature-half::before { + content: "\f2c9"; } + +.fa-temperature-2::before { + content: "\f2c9"; } + +.fa-thermometer-2::before { + content: "\f2c9"; } + +.fa-thermometer-half::before { + content: "\f2c9"; } + +.fa-dong-sign::before { + content: "\e169"; } + +.fa-capsules::before { + content: "\f46b"; } + +.fa-poo-storm::before { + content: "\f75a"; } + +.fa-poo-bolt::before { + content: "\f75a"; } + +.fa-face-frown-open::before { + content: "\f57a"; } + +.fa-frown-open::before { + content: "\f57a"; } + +.fa-hand-point-up::before { + content: "\f0a6"; } + +.fa-money-bill::before { + content: "\f0d6"; } + +.fa-bookmark::before { + content: "\f02e"; } + +.fa-align-justify::before { + content: "\f039"; } + +.fa-umbrella-beach::before { + content: "\f5ca"; } + +.fa-helmet-un::before { + content: "\e503"; } + +.fa-bullseye::before { + content: "\f140"; } + +.fa-bacon::before { + content: "\f7e5"; } + +.fa-hand-point-down::before { + content: "\f0a7"; } + +.fa-arrow-up-from-bracket::before { + content: "\e09a"; } + +.fa-folder::before { + content: "\f07b"; } + +.fa-folder-blank::before { + content: "\f07b"; } + +.fa-file-waveform::before { + content: "\f478"; } + +.fa-file-medical-alt::before { + content: "\f478"; } + +.fa-radiation::before { + content: "\f7b9"; } + +.fa-chart-simple::before { + content: "\e473"; } + +.fa-mars-stroke::before { + content: "\f229"; } + +.fa-vial::before { + content: "\f492"; } + +.fa-gauge::before { + content: "\f624"; } + +.fa-dashboard::before { + content: "\f624"; } + +.fa-gauge-med::before { + content: "\f624"; } + +.fa-tachometer-alt-average::before { + content: "\f624"; } + +.fa-wand-magic-sparkles::before { + content: "\e2ca"; } + +.fa-magic-wand-sparkles::before { + content: "\e2ca"; } + +.fa-e::before { + content: "\45"; } + +.fa-pen-clip::before { + content: "\f305"; } + +.fa-pen-alt::before { + content: "\f305"; } + +.fa-bridge-circle-exclamation::before { + content: "\e4ca"; } + +.fa-user::before { + content: "\f007"; } + +.fa-school-circle-check::before { + content: "\e56b"; } + +.fa-dumpster::before { + content: "\f793"; } + +.fa-van-shuttle::before { + content: "\f5b6"; } + +.fa-shuttle-van::before { + content: "\f5b6"; } + +.fa-building-user::before { + content: "\e4da"; } + +.fa-square-caret-left::before { + content: "\f191"; } + +.fa-caret-square-left::before { + content: "\f191"; } + +.fa-highlighter::before { + content: "\f591"; } + +.fa-key::before { + content: "\f084"; } + +.fa-bullhorn::before { + content: "\f0a1"; } + +.fa-globe::before { + content: "\f0ac"; } + +.fa-synagogue::before { + content: "\f69b"; } + +.fa-person-half-dress::before { + content: "\e548"; } + +.fa-road-bridge::before { + content: "\e563"; } + +.fa-location-arrow::before { + content: "\f124"; } + +.fa-c::before { + content: "\43"; } + +.fa-tablet-button::before { + content: "\f10a"; } + +.fa-building-lock::before { + content: "\e4d6"; } + +.fa-pizza-slice::before { + content: "\f818"; } + +.fa-money-bill-wave::before { + content: "\f53a"; } + +.fa-chart-area::before { + content: "\f1fe"; } + +.fa-area-chart::before { + content: "\f1fe"; } + +.fa-house-flag::before { + content: "\e50d"; } + +.fa-person-circle-minus::before { + content: "\e540"; } + +.fa-ban::before { + content: "\f05e"; } + +.fa-cancel::before { + content: "\f05e"; } + +.fa-camera-rotate::before { + content: "\e0d8"; } + +.fa-spray-can-sparkles::before { + content: "\f5d0"; } + +.fa-air-freshener::before { + content: "\f5d0"; } + +.fa-star::before { + content: "\f005"; } + +.fa-repeat::before { + content: "\f363"; } + +.fa-cross::before { + content: "\f654"; } + +.fa-box::before { + content: "\f466"; } + +.fa-venus-mars::before { + content: "\f228"; } + +.fa-arrow-pointer::before { + content: "\f245"; } + +.fa-mouse-pointer::before { + content: "\f245"; } + +.fa-maximize::before { + content: "\f31e"; } + +.fa-expand-arrows-alt::before { + content: "\f31e"; } + +.fa-charging-station::before { + content: "\f5e7"; } + +.fa-shapes::before { + content: "\f61f"; } + +.fa-triangle-circle-square::before { + content: "\f61f"; } + +.fa-shuffle::before { + content: "\f074"; } + +.fa-random::before { + content: "\f074"; } + +.fa-person-running::before { + content: "\f70c"; } + +.fa-running::before { + content: "\f70c"; } + +.fa-mobile-retro::before { + content: "\e527"; } + +.fa-grip-lines-vertical::before { + content: "\f7a5"; } + +.fa-spider::before { + content: "\f717"; } + +.fa-hands-bound::before { + content: "\e4f9"; } + +.fa-file-invoice-dollar::before { + content: "\f571"; } + +.fa-plane-circle-exclamation::before { + content: "\e556"; } + +.fa-x-ray::before { + content: "\f497"; } + +.fa-spell-check::before { + content: "\f891"; } + +.fa-slash::before { + content: "\f715"; } + +.fa-computer-mouse::before { + content: "\f8cc"; } + +.fa-mouse::before { + content: "\f8cc"; } + +.fa-arrow-right-to-bracket::before { + content: "\f090"; } + +.fa-sign-in::before { + content: "\f090"; } + +.fa-shop-slash::before { + content: "\e070"; } + +.fa-store-alt-slash::before { + content: "\e070"; } + +.fa-server::before { + content: "\f233"; } + +.fa-virus-covid-slash::before { + content: "\e4a9"; } + +.fa-shop-lock::before { + content: "\e4a5"; } + +.fa-hourglass-start::before { + content: "\f251"; } + +.fa-hourglass-1::before { + content: "\f251"; } + +.fa-blender-phone::before { + content: "\f6b6"; } + +.fa-building-wheat::before { + content: "\e4db"; } + +.fa-person-breastfeeding::before { + content: "\e53a"; } + +.fa-right-to-bracket::before { + content: "\f2f6"; } + +.fa-sign-in-alt::before { + content: "\f2f6"; } + +.fa-venus::before { + content: "\f221"; } + +.fa-passport::before { + content: "\f5ab"; } + +.fa-heart-pulse::before { + content: "\f21e"; } + +.fa-heartbeat::before { + content: "\f21e"; } + +.fa-people-carry-box::before { + content: "\f4ce"; } + +.fa-people-carry::before { + content: "\f4ce"; } + +.fa-temperature-high::before { + content: "\f769"; } + +.fa-microchip::before { + content: "\f2db"; } + +.fa-crown::before { + content: "\f521"; } + +.fa-weight-hanging::before { + content: "\f5cd"; } + +.fa-xmarks-lines::before { + content: "\e59a"; } + +.fa-file-prescription::before { + content: "\f572"; } + +.fa-weight-scale::before { + content: "\f496"; } + +.fa-weight::before { + content: "\f496"; } + +.fa-user-group::before { + content: "\f500"; } + +.fa-user-friends::before { + content: "\f500"; } + +.fa-arrow-up-a-z::before { + content: "\f15e"; } + +.fa-sort-alpha-up::before { + content: "\f15e"; } + +.fa-chess-knight::before { + content: "\f441"; } + +.fa-face-laugh-squint::before { + content: "\f59b"; } + +.fa-laugh-squint::before { + content: "\f59b"; } + +.fa-wheelchair::before { + content: "\f193"; } + +.fa-circle-arrow-up::before { + content: "\f0aa"; } + +.fa-arrow-circle-up::before { + content: "\f0aa"; } + +.fa-toggle-on::before { + content: "\f205"; } + +.fa-person-walking::before { + content: "\f554"; } + +.fa-walking::before { + content: "\f554"; } + +.fa-l::before { + content: "\4c"; } + +.fa-fire::before { + content: "\f06d"; } + +.fa-bed-pulse::before { + content: "\f487"; } + +.fa-procedures::before { + content: "\f487"; } + +.fa-shuttle-space::before { + content: "\f197"; } + +.fa-space-shuttle::before { + content: "\f197"; } + +.fa-face-laugh::before { + content: "\f599"; } + +.fa-laugh::before { + content: "\f599"; } + +.fa-folder-open::before { + content: "\f07c"; } + +.fa-heart-circle-plus::before { + content: "\e500"; } + +.fa-code-fork::before { + content: "\e13b"; } + +.fa-city::before { + content: "\f64f"; } + +.fa-microphone-lines::before { + content: "\f3c9"; } + +.fa-microphone-alt::before { + content: "\f3c9"; } + +.fa-pepper-hot::before { + content: "\f816"; } + +.fa-unlock::before { + content: "\f09c"; } + +.fa-colon-sign::before { + content: "\e140"; } + +.fa-headset::before { + content: "\f590"; } + +.fa-store-slash::before { + content: "\e071"; } + +.fa-road-circle-xmark::before { + content: "\e566"; } + +.fa-user-minus::before { + content: "\f503"; } + +.fa-mars-stroke-up::before { + content: "\f22a"; } + +.fa-mars-stroke-v::before { + content: "\f22a"; } + +.fa-champagne-glasses::before { + content: "\f79f"; } + +.fa-glass-cheers::before { + content: "\f79f"; } + +.fa-clipboard::before { + content: "\f328"; } + +.fa-house-circle-exclamation::before { + content: "\e50a"; } + +.fa-file-arrow-up::before { + content: "\f574"; } + +.fa-file-upload::before { + content: "\f574"; } + +.fa-wifi::before { + content: "\f1eb"; } + +.fa-wifi-3::before { + content: "\f1eb"; } + +.fa-wifi-strong::before { + content: "\f1eb"; } + +.fa-bath::before { + content: "\f2cd"; } + +.fa-bathtub::before { + content: "\f2cd"; } + +.fa-underline::before { + content: "\f0cd"; } + +.fa-user-pen::before { + content: "\f4ff"; } + +.fa-user-edit::before { + content: "\f4ff"; } + +.fa-signature::before { + content: "\f5b7"; } + +.fa-stroopwafel::before { + content: "\f551"; } + +.fa-bold::before { + content: "\f032"; } + +.fa-anchor-lock::before { + content: "\e4ad"; } + +.fa-building-ngo::before { + content: "\e4d7"; } + +.fa-manat-sign::before { + content: "\e1d5"; } + +.fa-not-equal::before { + content: "\f53e"; } + +.fa-border-top-left::before { + content: "\f853"; } + +.fa-border-style::before { + content: "\f853"; } + +.fa-map-location-dot::before { + content: "\f5a0"; } + +.fa-map-marked-alt::before { + content: "\f5a0"; } + +.fa-jedi::before { + content: "\f669"; } + +.fa-square-poll-vertical::before { + content: "\f681"; } + +.fa-poll::before { + content: "\f681"; } + +.fa-mug-hot::before { + content: "\f7b6"; } + +.fa-car-battery::before { + content: "\f5df"; } + +.fa-battery-car::before { + content: "\f5df"; } + +.fa-gift::before { + content: "\f06b"; } + +.fa-dice-two::before { + content: "\f528"; } + +.fa-chess-queen::before { + content: "\f445"; } + +.fa-glasses::before { + content: "\f530"; } + +.fa-chess-board::before { + content: "\f43c"; } + +.fa-building-circle-check::before { + content: "\e4d2"; } + +.fa-person-chalkboard::before { + content: "\e53d"; } + +.fa-mars-stroke-right::before { + content: "\f22b"; } + +.fa-mars-stroke-h::before { + content: "\f22b"; } + +.fa-hand-back-fist::before { + content: "\f255"; } + +.fa-hand-rock::before { + content: "\f255"; } + +.fa-square-caret-up::before { + content: "\f151"; } + +.fa-caret-square-up::before { + content: "\f151"; } + +.fa-cloud-showers-water::before { + content: "\e4e4"; } + +.fa-chart-bar::before { + content: "\f080"; } + +.fa-bar-chart::before { + content: "\f080"; } + +.fa-hands-bubbles::before { + content: "\e05e"; } + +.fa-hands-wash::before { + content: "\e05e"; } + +.fa-less-than-equal::before { + content: "\f537"; } + +.fa-train::before { + content: "\f238"; } + +.fa-eye-low-vision::before { + content: "\f2a8"; } + +.fa-low-vision::before { + content: "\f2a8"; } + +.fa-crow::before { + content: "\f520"; } + +.fa-sailboat::before { + content: "\e445"; } + +.fa-window-restore::before { + content: "\f2d2"; } + +.fa-square-plus::before { + content: "\f0fe"; } + +.fa-plus-square::before { + content: "\f0fe"; } + +.fa-torii-gate::before { + content: "\f6a1"; } + +.fa-frog::before { + content: "\f52e"; } + +.fa-bucket::before { + content: "\e4cf"; } + +.fa-image::before { + content: "\f03e"; } + +.fa-microphone::before { + content: "\f130"; } + +.fa-cow::before { + content: "\f6c8"; } + +.fa-caret-up::before { + content: "\f0d8"; } + +.fa-screwdriver::before { + content: "\f54a"; } + +.fa-folder-closed::before { + content: "\e185"; } + +.fa-house-tsunami::before { + content: "\e515"; } + +.fa-square-nfi::before { + content: "\e576"; } + +.fa-arrow-up-from-ground-water::before { + content: "\e4b5"; } + +.fa-martini-glass::before { + content: "\f57b"; } + +.fa-glass-martini-alt::before { + content: "\f57b"; } + +.fa-rotate-left::before { + content: "\f2ea"; } + +.fa-rotate-back::before { + content: "\f2ea"; } + +.fa-rotate-backward::before { + content: "\f2ea"; } + +.fa-undo-alt::before { + content: "\f2ea"; } + +.fa-table-columns::before { + content: "\f0db"; } + +.fa-columns::before { + content: "\f0db"; } + +.fa-lemon::before { + content: "\f094"; } + +.fa-head-side-mask::before { + content: "\e063"; } + +.fa-handshake::before { + content: "\f2b5"; } + +.fa-gem::before { + content: "\f3a5"; } + +.fa-dolly::before { + content: "\f472"; } + +.fa-dolly-box::before { + content: "\f472"; } + +.fa-smoking::before { + content: "\f48d"; } + +.fa-minimize::before { + content: "\f78c"; } + +.fa-compress-arrows-alt::before { + content: "\f78c"; } + +.fa-monument::before { + content: "\f5a6"; } + +.fa-snowplow::before { + content: "\f7d2"; } + +.fa-angles-right::before { + content: "\f101"; } + +.fa-angle-double-right::before { + content: "\f101"; } + +.fa-cannabis::before { + content: "\f55f"; } + +.fa-circle-play::before { + content: "\f144"; } + +.fa-play-circle::before { + content: "\f144"; } + +.fa-tablets::before { + content: "\f490"; } + +.fa-ethernet::before { + content: "\f796"; } + +.fa-euro-sign::before { + content: "\f153"; } + +.fa-eur::before { + content: "\f153"; } + +.fa-euro::before { + content: "\f153"; } + +.fa-chair::before { + content: "\f6c0"; } + +.fa-circle-check::before { + content: "\f058"; } + +.fa-check-circle::before { + content: "\f058"; } + +.fa-circle-stop::before { + content: "\f28d"; } + +.fa-stop-circle::before { + content: "\f28d"; } + +.fa-compass-drafting::before { + content: "\f568"; } + +.fa-drafting-compass::before { + content: "\f568"; } + +.fa-plate-wheat::before { + content: "\e55a"; } + +.fa-icicles::before { + content: "\f7ad"; } + +.fa-person-shelter::before { + content: "\e54f"; } + +.fa-neuter::before { + content: "\f22c"; } + +.fa-id-badge::before { + content: "\f2c1"; } + +.fa-marker::before { + content: "\f5a1"; } + +.fa-face-laugh-beam::before { + content: "\f59a"; } + +.fa-laugh-beam::before { + content: "\f59a"; } + +.fa-helicopter-symbol::before { + content: "\e502"; } + +.fa-universal-access::before { + content: "\f29a"; } + +.fa-circle-chevron-up::before { + content: "\f139"; } + +.fa-chevron-circle-up::before { + content: "\f139"; } + +.fa-lari-sign::before { + content: "\e1c8"; } + +.fa-volcano::before { + content: "\f770"; } + +.fa-person-walking-dashed-line-arrow-right::before { + content: "\e553"; } + +.fa-sterling-sign::before { + content: "\f154"; } + +.fa-gbp::before { + content: "\f154"; } + +.fa-pound-sign::before { + content: "\f154"; } + +.fa-viruses::before { + content: "\e076"; } + +.fa-square-person-confined::before { + content: "\e577"; } + +.fa-user-tie::before { + content: "\f508"; } + +.fa-arrow-down-long::before { + content: "\f175"; } + +.fa-long-arrow-down::before { + content: "\f175"; } + +.fa-tent-arrow-down-to-line::before { + content: "\e57e"; } + +.fa-certificate::before { + content: "\f0a3"; } + +.fa-reply-all::before { + content: "\f122"; } + +.fa-mail-reply-all::before { + content: "\f122"; } + +.fa-suitcase::before { + content: "\f0f2"; } + +.fa-person-skating::before { + content: "\f7c5"; } + +.fa-skating::before { + content: "\f7c5"; } + +.fa-filter-circle-dollar::before { + content: "\f662"; } + +.fa-funnel-dollar::before { + content: "\f662"; } + +.fa-camera-retro::before { + content: "\f083"; } + +.fa-circle-arrow-down::before { + content: "\f0ab"; } + +.fa-arrow-circle-down::before { + content: "\f0ab"; } + +.fa-file-import::before { + content: "\f56f"; } + +.fa-arrow-right-to-file::before { + content: "\f56f"; } + +.fa-square-arrow-up-right::before { + content: "\f14c"; } + +.fa-external-link-square::before { + content: "\f14c"; } + +.fa-box-open::before { + content: "\f49e"; } + +.fa-scroll::before { + content: "\f70e"; } + +.fa-spa::before { + content: "\f5bb"; } + +.fa-location-pin-lock::before { + content: "\e51f"; } + +.fa-pause::before { + content: "\f04c"; } + +.fa-hill-avalanche::before { + content: "\e507"; } + +.fa-temperature-empty::before { + content: "\f2cb"; } + +.fa-temperature-0::before { + content: "\f2cb"; } + +.fa-thermometer-0::before { + content: "\f2cb"; } + +.fa-thermometer-empty::before { + content: "\f2cb"; } + +.fa-bomb::before { + content: "\f1e2"; } + +.fa-registered::before { + content: "\f25d"; } + +.fa-address-card::before { + content: "\f2bb"; } + +.fa-contact-card::before { + content: "\f2bb"; } + +.fa-vcard::before { + content: "\f2bb"; } + +.fa-scale-unbalanced-flip::before { + content: "\f516"; } + +.fa-balance-scale-right::before { + content: "\f516"; } + +.fa-subscript::before { + content: "\f12c"; } + +.fa-diamond-turn-right::before { + content: "\f5eb"; } + +.fa-directions::before { + content: "\f5eb"; } + +.fa-burst::before { + content: "\e4dc"; } + +.fa-house-laptop::before { + content: "\e066"; } + +.fa-laptop-house::before { + content: "\e066"; } + +.fa-face-tired::before { + content: "\f5c8"; } + +.fa-tired::before { + content: "\f5c8"; } + +.fa-money-bills::before { + content: "\e1f3"; } + +.fa-smog::before { + content: "\f75f"; } + +.fa-crutch::before { + content: "\f7f7"; } + +.fa-cloud-arrow-up::before { + content: "\f0ee"; } + +.fa-cloud-upload::before { + content: "\f0ee"; } + +.fa-cloud-upload-alt::before { + content: "\f0ee"; } + +.fa-palette::before { + content: "\f53f"; } + +.fa-arrows-turn-right::before { + content: "\e4c0"; } + +.fa-vest::before { + content: "\e085"; } + +.fa-ferry::before { + content: "\e4ea"; } + +.fa-arrows-down-to-people::before { + content: "\e4b9"; } + +.fa-seedling::before { + content: "\f4d8"; } + +.fa-sprout::before { + content: "\f4d8"; } + +.fa-left-right::before { + content: "\f337"; } + +.fa-arrows-alt-h::before { + content: "\f337"; } + +.fa-boxes-packing::before { + content: "\e4c7"; } + +.fa-circle-arrow-left::before { + content: "\f0a8"; } + +.fa-arrow-circle-left::before { + content: "\f0a8"; } + +.fa-group-arrows-rotate::before { + content: "\e4f6"; } + +.fa-bowl-food::before { + content: "\e4c6"; } + +.fa-candy-cane::before { + content: "\f786"; } + +.fa-arrow-down-wide-short::before { + content: "\f160"; } + +.fa-sort-amount-asc::before { + content: "\f160"; } + +.fa-sort-amount-down::before { + content: "\f160"; } + +.fa-cloud-bolt::before { + content: "\f76c"; } + +.fa-thunderstorm::before { + content: "\f76c"; } + +.fa-text-slash::before { + content: "\f87d"; } + +.fa-remove-format::before { + content: "\f87d"; } + +.fa-face-smile-wink::before { + content: "\f4da"; } + +.fa-smile-wink::before { + content: "\f4da"; } + +.fa-file-word::before { + content: "\f1c2"; } + +.fa-file-powerpoint::before { + content: "\f1c4"; } + +.fa-arrows-left-right::before { + content: "\f07e"; } + +.fa-arrows-h::before { + content: "\f07e"; } + +.fa-house-lock::before { + content: "\e510"; } + +.fa-cloud-arrow-down::before { + content: "\f0ed"; } + +.fa-cloud-download::before { + content: "\f0ed"; } + +.fa-cloud-download-alt::before { + content: "\f0ed"; } + +.fa-children::before { + content: "\e4e1"; } + +.fa-chalkboard::before { + content: "\f51b"; } + +.fa-blackboard::before { + content: "\f51b"; } + +.fa-user-large-slash::before { + content: "\f4fa"; } + +.fa-user-alt-slash::before { + content: "\f4fa"; } + +.fa-envelope-open::before { + content: "\f2b6"; } + +.fa-handshake-simple-slash::before { + content: "\e05f"; } + +.fa-handshake-alt-slash::before { + content: "\e05f"; } + +.fa-mattress-pillow::before { + content: "\e525"; } + +.fa-guarani-sign::before { + content: "\e19a"; } + +.fa-arrows-rotate::before { + content: "\f021"; } + +.fa-refresh::before { + content: "\f021"; } + +.fa-sync::before { + content: "\f021"; } + +.fa-fire-extinguisher::before { + content: "\f134"; } + +.fa-cruzeiro-sign::before { + content: "\e152"; } + +.fa-greater-than-equal::before { + content: "\f532"; } + +.fa-shield-halved::before { + content: "\f3ed"; } + +.fa-shield-alt::before { + content: "\f3ed"; } + +.fa-book-atlas::before { + content: "\f558"; } + +.fa-atlas::before { + content: "\f558"; } + +.fa-virus::before { + content: "\e074"; } + +.fa-envelope-circle-check::before { + content: "\e4e8"; } + +.fa-layer-group::before { + content: "\f5fd"; } + +.fa-arrows-to-dot::before { + content: "\e4be"; } + +.fa-archway::before { + content: "\f557"; } + +.fa-heart-circle-check::before { + content: "\e4fd"; } + +.fa-house-chimney-crack::before { + content: "\f6f1"; } + +.fa-house-damage::before { + content: "\f6f1"; } + +.fa-file-zipper::before { + content: "\f1c6"; } + +.fa-file-archive::before { + content: "\f1c6"; } + +.fa-square::before { + content: "\f0c8"; } + +.fa-martini-glass-empty::before { + content: "\f000"; } + +.fa-glass-martini::before { + content: "\f000"; } + +.fa-couch::before { + content: "\f4b8"; } + +.fa-cedi-sign::before { + content: "\e0df"; } + +.fa-italic::before { + content: "\f033"; } + +.fa-church::before { + content: "\f51d"; } + +.fa-comments-dollar::before { + content: "\f653"; } + +.fa-democrat::before { + content: "\f747"; } + +.fa-z::before { + content: "\5a"; } + +.fa-person-skiing::before { + content: "\f7c9"; } + +.fa-skiing::before { + content: "\f7c9"; } + +.fa-road-lock::before { + content: "\e567"; } + +.fa-a::before { + content: "\41"; } + +.fa-temperature-arrow-down::before { + content: "\e03f"; } + +.fa-temperature-down::before { + content: "\e03f"; } + +.fa-feather-pointed::before { + content: "\f56b"; } + +.fa-feather-alt::before { + content: "\f56b"; } + +.fa-p::before { + content: "\50"; } + +.fa-snowflake::before { + content: "\f2dc"; } + +.fa-newspaper::before { + content: "\f1ea"; } + +.fa-rectangle-ad::before { + content: "\f641"; } + +.fa-ad::before { + content: "\f641"; } + +.fa-circle-arrow-right::before { + content: "\f0a9"; } + +.fa-arrow-circle-right::before { + content: "\f0a9"; } + +.fa-filter-circle-xmark::before { + content: "\e17b"; } + +.fa-locust::before { + content: "\e520"; } + +.fa-sort::before { + content: "\f0dc"; } + +.fa-unsorted::before { + content: "\f0dc"; } + +.fa-list-ol::before { + content: "\f0cb"; } + +.fa-list-1-2::before { + content: "\f0cb"; } + +.fa-list-numeric::before { + content: "\f0cb"; } + +.fa-person-dress-burst::before { + content: "\e544"; } + +.fa-money-check-dollar::before { + content: "\f53d"; } + +.fa-money-check-alt::before { + content: "\f53d"; } + +.fa-vector-square::before { + content: "\f5cb"; } + +.fa-bread-slice::before { + content: "\f7ec"; } + +.fa-language::before { + content: "\f1ab"; } + +.fa-face-kiss-wink-heart::before { + content: "\f598"; } + +.fa-kiss-wink-heart::before { + content: "\f598"; } + +.fa-filter::before { + content: "\f0b0"; } + +.fa-question::before { + content: "\3f"; } + +.fa-file-signature::before { + content: "\f573"; } + +.fa-up-down-left-right::before { + content: "\f0b2"; } + +.fa-arrows-alt::before { + content: "\f0b2"; } + +.fa-house-chimney-user::before { + content: "\e065"; } + +.fa-hand-holding-heart::before { + content: "\f4be"; } + +.fa-puzzle-piece::before { + content: "\f12e"; } + +.fa-money-check::before { + content: "\f53c"; } + +.fa-star-half-stroke::before { + content: "\f5c0"; } + +.fa-star-half-alt::before { + content: "\f5c0"; } + +.fa-code::before { + content: "\f121"; } + +.fa-whiskey-glass::before { + content: "\f7a0"; } + +.fa-glass-whiskey::before { + content: "\f7a0"; } + +.fa-building-circle-exclamation::before { + content: "\e4d3"; } + +.fa-magnifying-glass-chart::before { + content: "\e522"; } + +.fa-arrow-up-right-from-square::before { + content: "\f08e"; } + +.fa-external-link::before { + content: "\f08e"; } + +.fa-cubes-stacked::before { + content: "\e4e6"; } + +.fa-won-sign::before { + content: "\f159"; } + +.fa-krw::before { + content: "\f159"; } + +.fa-won::before { + content: "\f159"; } + +.fa-virus-covid::before { + content: "\e4a8"; } + +.fa-austral-sign::before { + content: "\e0a9"; } + +.fa-f::before { + content: "\46"; } + +.fa-leaf::before { + content: "\f06c"; } + +.fa-road::before { + content: "\f018"; } + +.fa-taxi::before { + content: "\f1ba"; } + +.fa-cab::before { + content: "\f1ba"; } + +.fa-person-circle-plus::before { + content: "\e541"; } + +.fa-chart-pie::before { + content: "\f200"; } + +.fa-pie-chart::before { + content: "\f200"; } + +.fa-bolt-lightning::before { + content: "\e0b7"; } + +.fa-sack-xmark::before { + content: "\e56a"; } + +.fa-file-excel::before { + content: "\f1c3"; } + +.fa-file-contract::before { + content: "\f56c"; } + +.fa-fish-fins::before { + content: "\e4f2"; } + +.fa-building-flag::before { + content: "\e4d5"; } + +.fa-face-grin-beam::before { + content: "\f582"; } + +.fa-grin-beam::before { + content: "\f582"; } + +.fa-object-ungroup::before { + content: "\f248"; } + +.fa-poop::before { + content: "\f619"; } + +.fa-location-pin::before { + content: "\f041"; } + +.fa-map-marker::before { + content: "\f041"; } + +.fa-kaaba::before { + content: "\f66b"; } + +.fa-toilet-paper::before { + content: "\f71e"; } + +.fa-helmet-safety::before { + content: "\f807"; } + +.fa-hard-hat::before { + content: "\f807"; } + +.fa-hat-hard::before { + content: "\f807"; } + +.fa-eject::before { + content: "\f052"; } + +.fa-circle-right::before { + content: "\f35a"; } + +.fa-arrow-alt-circle-right::before { + content: "\f35a"; } + +.fa-plane-circle-check::before { + content: "\e555"; } + +.fa-face-rolling-eyes::before { + content: "\f5a5"; } + +.fa-meh-rolling-eyes::before { + content: "\f5a5"; } + +.fa-object-group::before { + content: "\f247"; } + +.fa-chart-line::before { + content: "\f201"; } + +.fa-line-chart::before { + content: "\f201"; } + +.fa-mask-ventilator::before { + content: "\e524"; } + +.fa-arrow-right::before { + content: "\f061"; } + +.fa-signs-post::before { + content: "\f277"; } + +.fa-map-signs::before { + content: "\f277"; } + +.fa-cash-register::before { + content: "\f788"; } + +.fa-person-circle-question::before { + content: "\e542"; } + +.fa-h::before { + content: "\48"; } + +.fa-tarp::before { + content: "\e57b"; } + +.fa-screwdriver-wrench::before { + content: "\f7d9"; } + +.fa-tools::before { + content: "\f7d9"; } + +.fa-arrows-to-eye::before { + content: "\e4bf"; } + +.fa-plug-circle-bolt::before { + content: "\e55b"; } + +.fa-heart::before { + content: "\f004"; } + +.fa-mars-and-venus::before { + content: "\f224"; } + +.fa-house-user::before { + content: "\e1b0"; } + +.fa-home-user::before { + content: "\e1b0"; } + +.fa-dumpster-fire::before { + content: "\f794"; } + +.fa-house-crack::before { + content: "\e3b1"; } + +.fa-martini-glass-citrus::before { + content: "\f561"; } + +.fa-cocktail::before { + content: "\f561"; } + +.fa-face-surprise::before { + content: "\f5c2"; } + +.fa-surprise::before { + content: "\f5c2"; } + +.fa-bottle-water::before { + content: "\e4c5"; } + +.fa-circle-pause::before { + content: "\f28b"; } + +.fa-pause-circle::before { + content: "\f28b"; } + +.fa-toilet-paper-slash::before { + content: "\e072"; } + +.fa-apple-whole::before { + content: "\f5d1"; } + +.fa-apple-alt::before { + content: "\f5d1"; } + +.fa-kitchen-set::before { + content: "\e51a"; } + +.fa-r::before { + content: "\52"; } + +.fa-temperature-quarter::before { + content: "\f2ca"; } + +.fa-temperature-1::before { + content: "\f2ca"; } + +.fa-thermometer-1::before { + content: "\f2ca"; } + +.fa-thermometer-quarter::before { + content: "\f2ca"; } + +.fa-cube::before { + content: "\f1b2"; } + +.fa-bitcoin-sign::before { + content: "\e0b4"; } + +.fa-shield-dog::before { + content: "\e573"; } + +.fa-solar-panel::before { + content: "\f5ba"; } + +.fa-lock-open::before { + content: "\f3c1"; } + +.fa-elevator::before { + content: "\e16d"; } + +.fa-money-bill-transfer::before { + content: "\e528"; } + +.fa-money-bill-trend-up::before { + content: "\e529"; } + +.fa-house-flood-water-circle-arrow-right::before { + content: "\e50f"; } + +.fa-square-poll-horizontal::before { + content: "\f682"; } + +.fa-poll-h::before { + content: "\f682"; } + +.fa-circle::before { + content: "\f111"; } + +.fa-backward-fast::before { + content: "\f049"; } + +.fa-fast-backward::before { + content: "\f049"; } + +.fa-recycle::before { + content: "\f1b8"; } + +.fa-user-astronaut::before { + content: "\f4fb"; } + +.fa-plane-slash::before { + content: "\e069"; } + +.fa-trademark::before { + content: "\f25c"; } + +.fa-basketball::before { + content: "\f434"; } + +.fa-basketball-ball::before { + content: "\f434"; } + +.fa-satellite-dish::before { + content: "\f7c0"; } + +.fa-circle-up::before { + content: "\f35b"; } + +.fa-arrow-alt-circle-up::before { + content: "\f35b"; } + +.fa-mobile-screen-button::before { + content: "\f3cd"; } + +.fa-mobile-alt::before { + content: "\f3cd"; } + +.fa-volume-high::before { + content: "\f028"; } + +.fa-volume-up::before { + content: "\f028"; } + +.fa-users-rays::before { + content: "\e593"; } + +.fa-wallet::before { + content: "\f555"; } + +.fa-clipboard-check::before { + content: "\f46c"; } + +.fa-file-audio::before { + content: "\f1c7"; } + +.fa-burger::before { + content: "\f805"; } + +.fa-hamburger::before { + content: "\f805"; } + +.fa-wrench::before { + content: "\f0ad"; } + +.fa-bugs::before { + content: "\e4d0"; } + +.fa-rupee-sign::before { + content: "\f156"; } + +.fa-rupee::before { + content: "\f156"; } + +.fa-file-image::before { + content: "\f1c5"; } + +.fa-circle-question::before { + content: "\f059"; } + +.fa-question-circle::before { + content: "\f059"; } + +.fa-plane-departure::before { + content: "\f5b0"; } + +.fa-handshake-slash::before { + content: "\e060"; } + +.fa-book-bookmark::before { + content: "\e0bb"; } + +.fa-code-branch::before { + content: "\f126"; } + +.fa-hat-cowboy::before { + content: "\f8c0"; } + +.fa-bridge::before { + content: "\e4c8"; } + +.fa-phone-flip::before { + content: "\f879"; } + +.fa-phone-alt::before { + content: "\f879"; } + +.fa-truck-front::before { + content: "\e2b7"; } + +.fa-cat::before { + content: "\f6be"; } + +.fa-anchor-circle-exclamation::before { + content: "\e4ab"; } + +.fa-truck-field::before { + content: "\e58d"; } + +.fa-route::before { + content: "\f4d7"; } + +.fa-clipboard-question::before { + content: "\e4e3"; } + +.fa-panorama::before { + content: "\e209"; } + +.fa-comment-medical::before { + content: "\f7f5"; } + +.fa-teeth-open::before { + content: "\f62f"; } + +.fa-file-circle-minus::before { + content: "\e4ed"; } + +.fa-tags::before { + content: "\f02c"; } + +.fa-wine-glass::before { + content: "\f4e3"; } + +.fa-forward-fast::before { + content: "\f050"; } + +.fa-fast-forward::before { + content: "\f050"; } + +.fa-face-meh-blank::before { + content: "\f5a4"; } + +.fa-meh-blank::before { + content: "\f5a4"; } + +.fa-square-parking::before { + content: "\f540"; } + +.fa-parking::before { + content: "\f540"; } + +.fa-house-signal::before { + content: "\e012"; } + +.fa-bars-progress::before { + content: "\f828"; } + +.fa-tasks-alt::before { + content: "\f828"; } + +.fa-faucet-drip::before { + content: "\e006"; } + +.fa-cart-flatbed::before { + content: "\f474"; } + +.fa-dolly-flatbed::before { + content: "\f474"; } + +.fa-ban-smoking::before { + content: "\f54d"; } + +.fa-smoking-ban::before { + content: "\f54d"; } + +.fa-terminal::before { + content: "\f120"; } + +.fa-mobile-button::before { + content: "\f10b"; } + +.fa-house-medical-flag::before { + content: "\e514"; } + +.fa-basket-shopping::before { + content: "\f291"; } + +.fa-shopping-basket::before { + content: "\f291"; } + +.fa-tape::before { + content: "\f4db"; } + +.fa-bus-simple::before { + content: "\f55e"; } + +.fa-bus-alt::before { + content: "\f55e"; } + +.fa-eye::before { + content: "\f06e"; } + +.fa-face-sad-cry::before { + content: "\f5b3"; } + +.fa-sad-cry::before { + content: "\f5b3"; } + +.fa-audio-description::before { + content: "\f29e"; } + +.fa-person-military-to-person::before { + content: "\e54c"; } + +.fa-file-shield::before { + content: "\e4f0"; } + +.fa-user-slash::before { + content: "\f506"; } + +.fa-pen::before { + content: "\f304"; } + +.fa-tower-observation::before { + content: "\e586"; } + +.fa-file-code::before { + content: "\f1c9"; } + +.fa-signal::before { + content: "\f012"; } + +.fa-signal-5::before { + content: "\f012"; } + +.fa-signal-perfect::before { + content: "\f012"; } + +.fa-bus::before { + content: "\f207"; } + +.fa-heart-circle-xmark::before { + content: "\e501"; } + +.fa-house-chimney::before { + content: "\e3af"; } + +.fa-home-lg::before { + content: "\e3af"; } + +.fa-window-maximize::before { + content: "\f2d0"; } + +.fa-face-frown::before { + content: "\f119"; } + +.fa-frown::before { + content: "\f119"; } + +.fa-prescription::before { + content: "\f5b1"; } + +.fa-shop::before { + content: "\f54f"; } + +.fa-store-alt::before { + content: "\f54f"; } + +.fa-floppy-disk::before { + content: "\f0c7"; } + +.fa-save::before { + content: "\f0c7"; } + +.fa-vihara::before { + content: "\f6a7"; } + +.fa-scale-unbalanced::before { + content: "\f515"; } + +.fa-balance-scale-left::before { + content: "\f515"; } + +.fa-sort-up::before { + content: "\f0de"; } + +.fa-sort-asc::before { + content: "\f0de"; } + +.fa-comment-dots::before { + content: "\f4ad"; } + +.fa-commenting::before { + content: "\f4ad"; } + +.fa-plant-wilt::before { + content: "\e5aa"; } + +.fa-diamond::before { + content: "\f219"; } + +.fa-face-grin-squint::before { + content: "\f585"; } + +.fa-grin-squint::before { + content: "\f585"; } + +.fa-hand-holding-dollar::before { + content: "\f4c0"; } + +.fa-hand-holding-usd::before { + content: "\f4c0"; } + +.fa-bacterium::before { + content: "\e05a"; } + +.fa-hand-pointer::before { + content: "\f25a"; } + +.fa-drum-steelpan::before { + content: "\f56a"; } + +.fa-hand-scissors::before { + content: "\f257"; } + +.fa-hands-praying::before { + content: "\f684"; } + +.fa-praying-hands::before { + content: "\f684"; } + +.fa-arrow-rotate-right::before { + content: "\f01e"; } + +.fa-arrow-right-rotate::before { + content: "\f01e"; } + +.fa-arrow-rotate-forward::before { + content: "\f01e"; } + +.fa-redo::before { + content: "\f01e"; } + +.fa-biohazard::before { + content: "\f780"; } + +.fa-location-crosshairs::before { + content: "\f601"; } + +.fa-location::before { + content: "\f601"; } + +.fa-mars-double::before { + content: "\f227"; } + +.fa-child-dress::before { + content: "\e59c"; } + +.fa-users-between-lines::before { + content: "\e591"; } + +.fa-lungs-virus::before { + content: "\e067"; } + +.fa-face-grin-tears::before { + content: "\f588"; } + +.fa-grin-tears::before { + content: "\f588"; } + +.fa-phone::before { + content: "\f095"; } + +.fa-calendar-xmark::before { + content: "\f273"; } + +.fa-calendar-times::before { + content: "\f273"; } + +.fa-child-reaching::before { + content: "\e59d"; } + +.fa-head-side-virus::before { + content: "\e064"; } + +.fa-user-gear::before { + content: "\f4fe"; } + +.fa-user-cog::before { + content: "\f4fe"; } + +.fa-arrow-up-1-9::before { + content: "\f163"; } + +.fa-sort-numeric-up::before { + content: "\f163"; } + +.fa-door-closed::before { + content: "\f52a"; } + +.fa-shield-virus::before { + content: "\e06c"; } + +.fa-dice-six::before { + content: "\f526"; } + +.fa-mosquito-net::before { + content: "\e52c"; } + +.fa-bridge-water::before { + content: "\e4ce"; } + +.fa-person-booth::before { + content: "\f756"; } + +.fa-text-width::before { + content: "\f035"; } + +.fa-hat-wizard::before { + content: "\f6e8"; } + +.fa-pen-fancy::before { + content: "\f5ac"; } + +.fa-person-digging::before { + content: "\f85e"; } + +.fa-digging::before { + content: "\f85e"; } + +.fa-trash::before { + content: "\f1f8"; } + +.fa-gauge-simple::before { + content: "\f629"; } + +.fa-gauge-simple-med::before { + content: "\f629"; } + +.fa-tachometer-average::before { + content: "\f629"; } + +.fa-book-medical::before { + content: "\f7e6"; } + +.fa-poo::before { + content: "\f2fe"; } + +.fa-quote-right::before { + content: "\f10e"; } + +.fa-quote-right-alt::before { + content: "\f10e"; } + +.fa-shirt::before { + content: "\f553"; } + +.fa-t-shirt::before { + content: "\f553"; } + +.fa-tshirt::before { + content: "\f553"; } + +.fa-cubes::before { + content: "\f1b3"; } + +.fa-divide::before { + content: "\f529"; } + +.fa-tenge-sign::before { + content: "\f7d7"; } + +.fa-tenge::before { + content: "\f7d7"; } + +.fa-headphones::before { + content: "\f025"; } + +.fa-hands-holding::before { + content: "\f4c2"; } + +.fa-hands-clapping::before { + content: "\e1a8"; } + +.fa-republican::before { + content: "\f75e"; } + +.fa-arrow-left::before { + content: "\f060"; } + +.fa-person-circle-xmark::before { + content: "\e543"; } + +.fa-ruler::before { + content: "\f545"; } + +.fa-align-left::before { + content: "\f036"; } + +.fa-dice-d6::before { + content: "\f6d1"; } + +.fa-restroom::before { + content: "\f7bd"; } + +.fa-j::before { + content: "\4a"; } + +.fa-users-viewfinder::before { + content: "\e595"; } + +.fa-file-video::before { + content: "\f1c8"; } + +.fa-up-right-from-square::before { + content: "\f35d"; } + +.fa-external-link-alt::before { + content: "\f35d"; } + +.fa-table-cells::before { + content: "\f00a"; } + +.fa-th::before { + content: "\f00a"; } + +.fa-file-pdf::before { + content: "\f1c1"; } + +.fa-book-bible::before { + content: "\f647"; } + +.fa-bible::before { + content: "\f647"; } + +.fa-o::before { + content: "\4f"; } + +.fa-suitcase-medical::before { + content: "\f0fa"; } + +.fa-medkit::before { + content: "\f0fa"; } + +.fa-user-secret::before { + content: "\f21b"; } + +.fa-otter::before { + content: "\f700"; } + +.fa-person-dress::before { + content: "\f182"; } + +.fa-female::before { + content: "\f182"; } + +.fa-comment-dollar::before { + content: "\f651"; } + +.fa-business-time::before { + content: "\f64a"; } + +.fa-briefcase-clock::before { + content: "\f64a"; } + +.fa-table-cells-large::before { + content: "\f009"; } + +.fa-th-large::before { + content: "\f009"; } + +.fa-book-tanakh::before { + content: "\f827"; } + +.fa-tanakh::before { + content: "\f827"; } + +.fa-phone-volume::before { + content: "\f2a0"; } + +.fa-volume-control-phone::before { + content: "\f2a0"; } + +.fa-hat-cowboy-side::before { + content: "\f8c1"; } + +.fa-clipboard-user::before { + content: "\f7f3"; } + +.fa-child::before { + content: "\f1ae"; } + +.fa-lira-sign::before { + content: "\f195"; } + +.fa-satellite::before { + content: "\f7bf"; } + +.fa-plane-lock::before { + content: "\e558"; } + +.fa-tag::before { + content: "\f02b"; } + +.fa-comment::before { + content: "\f075"; } + +.fa-cake-candles::before { + content: "\f1fd"; } + +.fa-birthday-cake::before { + content: "\f1fd"; } + +.fa-cake::before { + content: "\f1fd"; } + +.fa-envelope::before { + content: "\f0e0"; } + +.fa-angles-up::before { + content: "\f102"; } + +.fa-angle-double-up::before { + content: "\f102"; } + +.fa-paperclip::before { + content: "\f0c6"; } + +.fa-arrow-right-to-city::before { + content: "\e4b3"; } + +.fa-ribbon::before { + content: "\f4d6"; } + +.fa-lungs::before { + content: "\f604"; } + +.fa-arrow-up-9-1::before { + content: "\f887"; } + +.fa-sort-numeric-up-alt::before { + content: "\f887"; } + +.fa-litecoin-sign::before { + content: "\e1d3"; } + +.fa-border-none::before { + content: "\f850"; } + +.fa-circle-nodes::before { + content: "\e4e2"; } + +.fa-parachute-box::before { + content: "\f4cd"; } + +.fa-indent::before { + content: "\f03c"; } + +.fa-truck-field-un::before { + content: "\e58e"; } + +.fa-hourglass::before { + content: "\f254"; } + +.fa-hourglass-empty::before { + content: "\f254"; } + +.fa-mountain::before { + content: "\f6fc"; } + +.fa-user-doctor::before { + content: "\f0f0"; } + +.fa-user-md::before { + content: "\f0f0"; } + +.fa-circle-info::before { + content: "\f05a"; } + +.fa-info-circle::before { + content: "\f05a"; } + +.fa-cloud-meatball::before { + content: "\f73b"; } + +.fa-camera::before { + content: "\f030"; } + +.fa-camera-alt::before { + content: "\f030"; } + +.fa-square-virus::before { + content: "\e578"; } + +.fa-meteor::before { + content: "\f753"; } + +.fa-car-on::before { + content: "\e4dd"; } + +.fa-sleigh::before { + content: "\f7cc"; } + +.fa-arrow-down-1-9::before { + content: "\f162"; } + +.fa-sort-numeric-asc::before { + content: "\f162"; } + +.fa-sort-numeric-down::before { + content: "\f162"; } + +.fa-hand-holding-droplet::before { + content: "\f4c1"; } + +.fa-hand-holding-water::before { + content: "\f4c1"; } + +.fa-water::before { + content: "\f773"; } + +.fa-calendar-check::before { + content: "\f274"; } + +.fa-braille::before { + content: "\f2a1"; } + +.fa-prescription-bottle-medical::before { + content: "\f486"; } + +.fa-prescription-bottle-alt::before { + content: "\f486"; } + +.fa-landmark::before { + content: "\f66f"; } + +.fa-truck::before { + content: "\f0d1"; } + +.fa-crosshairs::before { + content: "\f05b"; } + +.fa-person-cane::before { + content: "\e53c"; } + +.fa-tent::before { + content: "\e57d"; } + +.fa-vest-patches::before { + content: "\e086"; } + +.fa-check-double::before { + content: "\f560"; } + +.fa-arrow-down-a-z::before { + content: "\f15d"; } + +.fa-sort-alpha-asc::before { + content: "\f15d"; } + +.fa-sort-alpha-down::before { + content: "\f15d"; } + +.fa-money-bill-wheat::before { + content: "\e52a"; } + +.fa-cookie::before { + content: "\f563"; } + +.fa-arrow-rotate-left::before { + content: "\f0e2"; } + +.fa-arrow-left-rotate::before { + content: "\f0e2"; } + +.fa-arrow-rotate-back::before { + content: "\f0e2"; } + +.fa-arrow-rotate-backward::before { + content: "\f0e2"; } + +.fa-undo::before { + content: "\f0e2"; } + +.fa-hard-drive::before { + content: "\f0a0"; } + +.fa-hdd::before { + content: "\f0a0"; } + +.fa-face-grin-squint-tears::before { + content: "\f586"; } + +.fa-grin-squint-tears::before { + content: "\f586"; } + +.fa-dumbbell::before { + content: "\f44b"; } + +.fa-rectangle-list::before { + content: "\f022"; } + +.fa-list-alt::before { + content: "\f022"; } + +.fa-tarp-droplet::before { + content: "\e57c"; } + +.fa-house-medical-circle-check::before { + content: "\e511"; } + +.fa-person-skiing-nordic::before { + content: "\f7ca"; } + +.fa-skiing-nordic::before { + content: "\f7ca"; } + +.fa-calendar-plus::before { + content: "\f271"; } + +.fa-plane-arrival::before { + content: "\f5af"; } + +.fa-circle-left::before { + content: "\f359"; } + +.fa-arrow-alt-circle-left::before { + content: "\f359"; } + +.fa-train-subway::before { + content: "\f239"; } + +.fa-subway::before { + content: "\f239"; } + +.fa-chart-gantt::before { + content: "\e0e4"; } + +.fa-indian-rupee-sign::before { + content: "\e1bc"; } + +.fa-indian-rupee::before { + content: "\e1bc"; } + +.fa-inr::before { + content: "\e1bc"; } + +.fa-crop-simple::before { + content: "\f565"; } + +.fa-crop-alt::before { + content: "\f565"; } + +.fa-money-bill-1::before { + content: "\f3d1"; } + +.fa-money-bill-alt::before { + content: "\f3d1"; } + +.fa-left-long::before { + content: "\f30a"; } + +.fa-long-arrow-alt-left::before { + content: "\f30a"; } + +.fa-dna::before { + content: "\f471"; } + +.fa-virus-slash::before { + content: "\e075"; } + +.fa-minus::before { + content: "\f068"; } + +.fa-subtract::before { + content: "\f068"; } + +.fa-chess::before { + content: "\f439"; } + +.fa-arrow-left-long::before { + content: "\f177"; } + +.fa-long-arrow-left::before { + content: "\f177"; } + +.fa-plug-circle-check::before { + content: "\e55c"; } + +.fa-street-view::before { + content: "\f21d"; } + +.fa-franc-sign::before { + content: "\e18f"; } + +.fa-volume-off::before { + content: "\f026"; } + +.fa-hands-asl-interpreting::before { + content: "\f2a3"; } + +.fa-american-sign-language-interpreting::before { + content: "\f2a3"; } + +.fa-asl-interpreting::before { + content: "\f2a3"; } + +.fa-hands-american-sign-language-interpreting::before { + content: "\f2a3"; } + +.fa-gear::before { + content: "\f013"; } + +.fa-cog::before { + content: "\f013"; } + +.fa-droplet-slash::before { + content: "\f5c7"; } + +.fa-tint-slash::before { + content: "\f5c7"; } + +.fa-mosque::before { + content: "\f678"; } + +.fa-mosquito::before { + content: "\e52b"; } + +.fa-star-of-david::before { + content: "\f69a"; } + +.fa-person-military-rifle::before { + content: "\e54b"; } + +.fa-cart-shopping::before { + content: "\f07a"; } + +.fa-shopping-cart::before { + content: "\f07a"; } + +.fa-vials::before { + content: "\f493"; } + +.fa-plug-circle-plus::before { + content: "\e55f"; } + +.fa-place-of-worship::before { + content: "\f67f"; } + +.fa-grip-vertical::before { + content: "\f58e"; } + +.fa-arrow-turn-up::before { + content: "\f148"; } + +.fa-level-up::before { + content: "\f148"; } + +.fa-u::before { + content: "\55"; } + +.fa-square-root-variable::before { + content: "\f698"; } + +.fa-square-root-alt::before { + content: "\f698"; } + +.fa-clock::before { + content: "\f017"; } + +.fa-clock-four::before { + content: "\f017"; } + +.fa-backward-step::before { + content: "\f048"; } + +.fa-step-backward::before { + content: "\f048"; } + +.fa-pallet::before { + content: "\f482"; } + +.fa-faucet::before { + content: "\e005"; } + +.fa-baseball-bat-ball::before { + content: "\f432"; } + +.fa-s::before { + content: "\53"; } + +.fa-timeline::before { + content: "\e29c"; } + +.fa-keyboard::before { + content: "\f11c"; } + +.fa-caret-down::before { + content: "\f0d7"; } + +.fa-house-chimney-medical::before { + content: "\f7f2"; } + +.fa-clinic-medical::before { + content: "\f7f2"; } + +.fa-temperature-three-quarters::before { + content: "\f2c8"; } + +.fa-temperature-3::before { + content: "\f2c8"; } + +.fa-thermometer-3::before { + content: "\f2c8"; } + +.fa-thermometer-three-quarters::before { + content: "\f2c8"; } + +.fa-mobile-screen::before { + content: "\f3cf"; } + +.fa-mobile-android-alt::before { + content: "\f3cf"; } + +.fa-plane-up::before { + content: "\e22d"; } + +.fa-piggy-bank::before { + content: "\f4d3"; } + +.fa-battery-half::before { + content: "\f242"; } + +.fa-battery-3::before { + content: "\f242"; } + +.fa-mountain-city::before { + content: "\e52e"; } + +.fa-coins::before { + content: "\f51e"; } + +.fa-khanda::before { + content: "\f66d"; } + +.fa-sliders::before { + content: "\f1de"; } + +.fa-sliders-h::before { + content: "\f1de"; } + +.fa-folder-tree::before { + content: "\f802"; } + +.fa-network-wired::before { + content: "\f6ff"; } + +.fa-map-pin::before { + content: "\f276"; } + +.fa-hamsa::before { + content: "\f665"; } + +.fa-cent-sign::before { + content: "\e3f5"; } + +.fa-flask::before { + content: "\f0c3"; } + +.fa-person-pregnant::before { + content: "\e31e"; } + +.fa-wand-sparkles::before { + content: "\f72b"; } + +.fa-ellipsis-vertical::before { + content: "\f142"; } + +.fa-ellipsis-v::before { + content: "\f142"; } + +.fa-ticket::before { + content: "\f145"; } + +.fa-power-off::before { + content: "\f011"; } + +.fa-right-long::before { + content: "\f30b"; } + +.fa-long-arrow-alt-right::before { + content: "\f30b"; } + +.fa-flag-usa::before { + content: "\f74d"; } + +.fa-laptop-file::before { + content: "\e51d"; } + +.fa-tty::before { + content: "\f1e4"; } + +.fa-teletype::before { + content: "\f1e4"; } + +.fa-diagram-next::before { + content: "\e476"; } + +.fa-person-rifle::before { + content: "\e54e"; } + +.fa-house-medical-circle-exclamation::before { + content: "\e512"; } + +.fa-closed-captioning::before { + content: "\f20a"; } + +.fa-person-hiking::before { + content: "\f6ec"; } + +.fa-hiking::before { + content: "\f6ec"; } + +.fa-venus-double::before { + content: "\f226"; } + +.fa-images::before { + content: "\f302"; } + +.fa-calculator::before { + content: "\f1ec"; } + +.fa-people-pulling::before { + content: "\e535"; } + +.fa-n::before { + content: "\4e"; } + +.fa-cable-car::before { + content: "\f7da"; } + +.fa-tram::before { + content: "\f7da"; } + +.fa-cloud-rain::before { + content: "\f73d"; } + +.fa-building-circle-xmark::before { + content: "\e4d4"; } + +.fa-ship::before { + content: "\f21a"; } + +.fa-arrows-down-to-line::before { + content: "\e4b8"; } + +.fa-download::before { + content: "\f019"; } + +.fa-face-grin::before { + content: "\f580"; } + +.fa-grin::before { + content: "\f580"; } + +.fa-delete-left::before { + content: "\f55a"; } + +.fa-backspace::before { + content: "\f55a"; } + +.fa-eye-dropper::before { + content: "\f1fb"; } + +.fa-eye-dropper-empty::before { + content: "\f1fb"; } + +.fa-eyedropper::before { + content: "\f1fb"; } + +.fa-file-circle-check::before { + content: "\e5a0"; } + +.fa-forward::before { + content: "\f04e"; } + +.fa-mobile::before { + content: "\f3ce"; } + +.fa-mobile-android::before { + content: "\f3ce"; } + +.fa-mobile-phone::before { + content: "\f3ce"; } + +.fa-face-meh::before { + content: "\f11a"; } + +.fa-meh::before { + content: "\f11a"; } + +.fa-align-center::before { + content: "\f037"; } + +.fa-book-skull::before { + content: "\f6b7"; } + +.fa-book-dead::before { + content: "\f6b7"; } + +.fa-id-card::before { + content: "\f2c2"; } + +.fa-drivers-license::before { + content: "\f2c2"; } + +.fa-outdent::before { + content: "\f03b"; } + +.fa-dedent::before { + content: "\f03b"; } + +.fa-heart-circle-exclamation::before { + content: "\e4fe"; } + +.fa-house::before { + content: "\f015"; } + +.fa-home::before { + content: "\f015"; } + +.fa-home-alt::before { + content: "\f015"; } + +.fa-home-lg-alt::before { + content: "\f015"; } + +.fa-calendar-week::before { + content: "\f784"; } + +.fa-laptop-medical::before { + content: "\f812"; } + +.fa-b::before { + content: "\42"; } + +.fa-file-medical::before { + content: "\f477"; } + +.fa-dice-one::before { + content: "\f525"; } + +.fa-kiwi-bird::before { + content: "\f535"; } + +.fa-arrow-right-arrow-left::before { + content: "\f0ec"; } + +.fa-exchange::before { + content: "\f0ec"; } + +.fa-rotate-right::before { + content: "\f2f9"; } + +.fa-redo-alt::before { + content: "\f2f9"; } + +.fa-rotate-forward::before { + content: "\f2f9"; } + +.fa-utensils::before { + content: "\f2e7"; } + +.fa-cutlery::before { + content: "\f2e7"; } + +.fa-arrow-up-wide-short::before { + content: "\f161"; } + +.fa-sort-amount-up::before { + content: "\f161"; } + +.fa-mill-sign::before { + content: "\e1ed"; } + +.fa-bowl-rice::before { + content: "\e2eb"; } + +.fa-skull::before { + content: "\f54c"; } + +.fa-tower-broadcast::before { + content: "\f519"; } + +.fa-broadcast-tower::before { + content: "\f519"; } + +.fa-truck-pickup::before { + content: "\f63c"; } + +.fa-up-long::before { + content: "\f30c"; } + +.fa-long-arrow-alt-up::before { + content: "\f30c"; } + +.fa-stop::before { + content: "\f04d"; } + +.fa-code-merge::before { + content: "\f387"; } + +.fa-upload::before { + content: "\f093"; } + +.fa-hurricane::before { + content: "\f751"; } + +.fa-mound::before { + content: "\e52d"; } + +.fa-toilet-portable::before { + content: "\e583"; } + +.fa-compact-disc::before { + content: "\f51f"; } + +.fa-file-arrow-down::before { + content: "\f56d"; } + +.fa-file-download::before { + content: "\f56d"; } + +.fa-caravan::before { + content: "\f8ff"; } + +.fa-shield-cat::before { + content: "\e572"; } + +.fa-bolt::before { + content: "\f0e7"; } + +.fa-zap::before { + content: "\f0e7"; } + +.fa-glass-water::before { + content: "\e4f4"; } + +.fa-oil-well::before { + content: "\e532"; } + +.fa-vault::before { + content: "\e2c5"; } + +.fa-mars::before { + content: "\f222"; } + +.fa-toilet::before { + content: "\f7d8"; } + +.fa-plane-circle-xmark::before { + content: "\e557"; } + +.fa-yen-sign::before { + content: "\f157"; } + +.fa-cny::before { + content: "\f157"; } + +.fa-jpy::before { + content: "\f157"; } + +.fa-rmb::before { + content: "\f157"; } + +.fa-yen::before { + content: "\f157"; } + +.fa-ruble-sign::before { + content: "\f158"; } + +.fa-rouble::before { + content: "\f158"; } + +.fa-rub::before { + content: "\f158"; } + +.fa-ruble::before { + content: "\f158"; } + +.fa-sun::before { + content: "\f185"; } + +.fa-guitar::before { + content: "\f7a6"; } + +.fa-face-laugh-wink::before { + content: "\f59c"; } + +.fa-laugh-wink::before { + content: "\f59c"; } + +.fa-horse-head::before { + content: "\f7ab"; } + +.fa-bore-hole::before { + content: "\e4c3"; } + +.fa-industry::before { + content: "\f275"; } + +.fa-circle-down::before { + content: "\f358"; } + +.fa-arrow-alt-circle-down::before { + content: "\f358"; } + +.fa-arrows-turn-to-dots::before { + content: "\e4c1"; } + +.fa-florin-sign::before { + content: "\e184"; } + +.fa-arrow-down-short-wide::before { + content: "\f884"; } + +.fa-sort-amount-desc::before { + content: "\f884"; } + +.fa-sort-amount-down-alt::before { + content: "\f884"; } + +.fa-less-than::before { + content: "\3c"; } + +.fa-angle-down::before { + content: "\f107"; } + +.fa-car-tunnel::before { + content: "\e4de"; } + +.fa-head-side-cough::before { + content: "\e061"; } + +.fa-grip-lines::before { + content: "\f7a4"; } + +.fa-thumbs-down::before { + content: "\f165"; } + +.fa-user-lock::before { + content: "\f502"; } + +.fa-arrow-right-long::before { + content: "\f178"; } + +.fa-long-arrow-right::before { + content: "\f178"; } + +.fa-anchor-circle-xmark::before { + content: "\e4ac"; } + +.fa-ellipsis::before { + content: "\f141"; } + +.fa-ellipsis-h::before { + content: "\f141"; } + +.fa-chess-pawn::before { + content: "\f443"; } + +.fa-kit-medical::before { + content: "\f479"; } + +.fa-first-aid::before { + content: "\f479"; } + +.fa-person-through-window::before { + content: "\e5a9"; } + +.fa-toolbox::before { + content: "\f552"; } + +.fa-hands-holding-circle::before { + content: "\e4fb"; } + +.fa-bug::before { + content: "\f188"; } + +.fa-credit-card::before { + content: "\f09d"; } + +.fa-credit-card-alt::before { + content: "\f09d"; } + +.fa-car::before { + content: "\f1b9"; } + +.fa-automobile::before { + content: "\f1b9"; } + +.fa-hand-holding-hand::before { + content: "\e4f7"; } + +.fa-book-open-reader::before { + content: "\f5da"; } + +.fa-book-reader::before { + content: "\f5da"; } + +.fa-mountain-sun::before { + content: "\e52f"; } + +.fa-arrows-left-right-to-line::before { + content: "\e4ba"; } + +.fa-dice-d20::before { + content: "\f6cf"; } + +.fa-truck-droplet::before { + content: "\e58c"; } + +.fa-file-circle-xmark::before { + content: "\e5a1"; } + +.fa-temperature-arrow-up::before { + content: "\e040"; } + +.fa-temperature-up::before { + content: "\e040"; } + +.fa-medal::before { + content: "\f5a2"; } + +.fa-bed::before { + content: "\f236"; } + +.fa-square-h::before { + content: "\f0fd"; } + +.fa-h-square::before { + content: "\f0fd"; } + +.fa-podcast::before { + content: "\f2ce"; } + +.fa-temperature-full::before { + content: "\f2c7"; } + +.fa-temperature-4::before { + content: "\f2c7"; } + +.fa-thermometer-4::before { + content: "\f2c7"; } + +.fa-thermometer-full::before { + content: "\f2c7"; } + +.fa-bell::before { + content: "\f0f3"; } + +.fa-superscript::before { + content: "\f12b"; } + +.fa-plug-circle-xmark::before { + content: "\e560"; } + +.fa-star-of-life::before { + content: "\f621"; } + +.fa-phone-slash::before { + content: "\f3dd"; } + +.fa-paint-roller::before { + content: "\f5aa"; } + +.fa-handshake-angle::before { + content: "\f4c4"; } + +.fa-hands-helping::before { + content: "\f4c4"; } + +.fa-location-dot::before { + content: "\f3c5"; } + +.fa-map-marker-alt::before { + content: "\f3c5"; } + +.fa-file::before { + content: "\f15b"; } + +.fa-greater-than::before { + content: "\3e"; } + +.fa-person-swimming::before { + content: "\f5c4"; } + +.fa-swimmer::before { + content: "\f5c4"; } + +.fa-arrow-down::before { + content: "\f063"; } + +.fa-droplet::before { + content: "\f043"; } + +.fa-tint::before { + content: "\f043"; } + +.fa-eraser::before { + content: "\f12d"; } + +.fa-earth-americas::before { + content: "\f57d"; } + +.fa-earth::before { + content: "\f57d"; } + +.fa-earth-america::before { + content: "\f57d"; } + +.fa-globe-americas::before { + content: "\f57d"; } + +.fa-person-burst::before { + content: "\e53b"; } + +.fa-dove::before { + content: "\f4ba"; } + +.fa-battery-empty::before { + content: "\f244"; } + +.fa-battery-0::before { + content: "\f244"; } + +.fa-socks::before { + content: "\f696"; } + +.fa-inbox::before { + content: "\f01c"; } + +.fa-section::before { + content: "\e447"; } + +.fa-gauge-high::before { + content: "\f625"; } + +.fa-tachometer-alt::before { + content: "\f625"; } + +.fa-tachometer-alt-fast::before { + content: "\f625"; } + +.fa-envelope-open-text::before { + content: "\f658"; } + +.fa-hospital::before { + content: "\f0f8"; } + +.fa-hospital-alt::before { + content: "\f0f8"; } + +.fa-hospital-wide::before { + content: "\f0f8"; } + +.fa-wine-bottle::before { + content: "\f72f"; } + +.fa-chess-rook::before { + content: "\f447"; } + +.fa-bars-staggered::before { + content: "\f550"; } + +.fa-reorder::before { + content: "\f550"; } + +.fa-stream::before { + content: "\f550"; } + +.fa-dharmachakra::before { + content: "\f655"; } + +.fa-hotdog::before { + content: "\f80f"; } + +.fa-person-walking-with-cane::before { + content: "\f29d"; } + +.fa-blind::before { + content: "\f29d"; } + +.fa-drum::before { + content: "\f569"; } + +.fa-ice-cream::before { + content: "\f810"; } + +.fa-heart-circle-bolt::before { + content: "\e4fc"; } + +.fa-fax::before { + content: "\f1ac"; } + +.fa-paragraph::before { + content: "\f1dd"; } + +.fa-check-to-slot::before { + content: "\f772"; } + +.fa-vote-yea::before { + content: "\f772"; } + +.fa-star-half::before { + content: "\f089"; } + +.fa-boxes-stacked::before { + content: "\f468"; } + +.fa-boxes::before { + content: "\f468"; } + +.fa-boxes-alt::before { + content: "\f468"; } + +.fa-link::before { + content: "\f0c1"; } + +.fa-chain::before { + content: "\f0c1"; } + +.fa-ear-listen::before { + content: "\f2a2"; } + +.fa-assistive-listening-systems::before { + content: "\f2a2"; } + +.fa-tree-city::before { + content: "\e587"; } + +.fa-play::before { + content: "\f04b"; } + +.fa-font::before { + content: "\f031"; } + +.fa-rupiah-sign::before { + content: "\e23d"; } + +.fa-magnifying-glass::before { + content: "\f002"; } + +.fa-search::before { + content: "\f002"; } + +.fa-table-tennis-paddle-ball::before { + content: "\f45d"; } + +.fa-ping-pong-paddle-ball::before { + content: "\f45d"; } + +.fa-table-tennis::before { + content: "\f45d"; } + +.fa-person-dots-from-line::before { + content: "\f470"; } + +.fa-diagnoses::before { + content: "\f470"; } + +.fa-trash-can-arrow-up::before { + content: "\f82a"; } + +.fa-trash-restore-alt::before { + content: "\f82a"; } + +.fa-naira-sign::before { + content: "\e1f6"; } + +.fa-cart-arrow-down::before { + content: "\f218"; } + +.fa-walkie-talkie::before { + content: "\f8ef"; } + +.fa-file-pen::before { + content: "\f31c"; } + +.fa-file-edit::before { + content: "\f31c"; } + +.fa-receipt::before { + content: "\f543"; } + +.fa-square-pen::before { + content: "\f14b"; } + +.fa-pen-square::before { + content: "\f14b"; } + +.fa-pencil-square::before { + content: "\f14b"; } + +.fa-suitcase-rolling::before { + content: "\f5c1"; } + +.fa-person-circle-exclamation::before { + content: "\e53f"; } + +.fa-chevron-down::before { + content: "\f078"; } + +.fa-battery-full::before { + content: "\f240"; } + +.fa-battery::before { + content: "\f240"; } + +.fa-battery-5::before { + content: "\f240"; } + +.fa-skull-crossbones::before { + content: "\f714"; } + +.fa-code-compare::before { + content: "\e13a"; } + +.fa-list-ul::before { + content: "\f0ca"; } + +.fa-list-dots::before { + content: "\f0ca"; } + +.fa-school-lock::before { + content: "\e56f"; } + +.fa-tower-cell::before { + content: "\e585"; } + +.fa-down-long::before { + content: "\f309"; } + +.fa-long-arrow-alt-down::before { + content: "\f309"; } + +.fa-ranking-star::before { + content: "\e561"; } + +.fa-chess-king::before { + content: "\f43f"; } + +.fa-person-harassing::before { + content: "\e549"; } + +.fa-brazilian-real-sign::before { + content: "\e46c"; } + +.fa-landmark-dome::before { + content: "\f752"; } + +.fa-landmark-alt::before { + content: "\f752"; } + +.fa-arrow-up::before { + content: "\f062"; } + +.fa-tv::before { + content: "\f26c"; } + +.fa-television::before { + content: "\f26c"; } + +.fa-tv-alt::before { + content: "\f26c"; } + +.fa-shrimp::before { + content: "\e448"; } + +.fa-list-check::before { + content: "\f0ae"; } + +.fa-tasks::before { + content: "\f0ae"; } + +.fa-jug-detergent::before { + content: "\e519"; } + +.fa-circle-user::before { + content: "\f2bd"; } + +.fa-user-circle::before { + content: "\f2bd"; } + +.fa-user-shield::before { + content: "\f505"; } + +.fa-wind::before { + content: "\f72e"; } + +.fa-car-burst::before { + content: "\f5e1"; } + +.fa-car-crash::before { + content: "\f5e1"; } + +.fa-y::before { + content: "\59"; } + +.fa-person-snowboarding::before { + content: "\f7ce"; } + +.fa-snowboarding::before { + content: "\f7ce"; } + +.fa-truck-fast::before { + content: "\f48b"; } + +.fa-shipping-fast::before { + content: "\f48b"; } + +.fa-fish::before { + content: "\f578"; } + +.fa-user-graduate::before { + content: "\f501"; } + +.fa-circle-half-stroke::before { + content: "\f042"; } + +.fa-adjust::before { + content: "\f042"; } + +.fa-clapperboard::before { + content: "\e131"; } + +.fa-circle-radiation::before { + content: "\f7ba"; } + +.fa-radiation-alt::before { + content: "\f7ba"; } + +.fa-baseball::before { + content: "\f433"; } + +.fa-baseball-ball::before { + content: "\f433"; } + +.fa-jet-fighter-up::before { + content: "\e518"; } + +.fa-diagram-project::before { + content: "\f542"; } + +.fa-project-diagram::before { + content: "\f542"; } + +.fa-copy::before { + content: "\f0c5"; } + +.fa-volume-xmark::before { + content: "\f6a9"; } + +.fa-volume-mute::before { + content: "\f6a9"; } + +.fa-volume-times::before { + content: "\f6a9"; } + +.fa-hand-sparkles::before { + content: "\e05d"; } + +.fa-grip::before { + content: "\f58d"; } + +.fa-grip-horizontal::before { + content: "\f58d"; } + +.fa-share-from-square::before { + content: "\f14d"; } + +.fa-share-square::before { + content: "\f14d"; } + +.fa-child-combatant::before { + content: "\e4e0"; } + +.fa-child-rifle::before { + content: "\e4e0"; } + +.fa-gun::before { + content: "\e19b"; } + +.fa-square-phone::before { + content: "\f098"; } + +.fa-phone-square::before { + content: "\f098"; } + +.fa-plus::before { + content: "\2b"; } + +.fa-add::before { + content: "\2b"; } + +.fa-expand::before { + content: "\f065"; } + +.fa-computer::before { + content: "\e4e5"; } + +.fa-xmark::before { + content: "\f00d"; } + +.fa-close::before { + content: "\f00d"; } + +.fa-multiply::before { + content: "\f00d"; } + +.fa-remove::before { + content: "\f00d"; } + +.fa-times::before { + content: "\f00d"; } + +.fa-arrows-up-down-left-right::before { + content: "\f047"; } + +.fa-arrows::before { + content: "\f047"; } + +.fa-chalkboard-user::before { + content: "\f51c"; } + +.fa-chalkboard-teacher::before { + content: "\f51c"; } + +.fa-peso-sign::before { + content: "\e222"; } + +.fa-building-shield::before { + content: "\e4d8"; } + +.fa-baby::before { + content: "\f77c"; } + +.fa-users-line::before { + content: "\e592"; } + +.fa-quote-left::before { + content: "\f10d"; } + +.fa-quote-left-alt::before { + content: "\f10d"; } + +.fa-tractor::before { + content: "\f722"; } + +.fa-trash-arrow-up::before { + content: "\f829"; } + +.fa-trash-restore::before { + content: "\f829"; } + +.fa-arrow-down-up-lock::before { + content: "\e4b0"; } + +.fa-lines-leaning::before { + content: "\e51e"; } + +.fa-ruler-combined::before { + content: "\f546"; } + +.fa-copyright::before { + content: "\f1f9"; } + +.fa-equals::before { + content: "\3d"; } + +.fa-blender::before { + content: "\f517"; } + +.fa-teeth::before { + content: "\f62e"; } + +.fa-shekel-sign::before { + content: "\f20b"; } + +.fa-ils::before { + content: "\f20b"; } + +.fa-shekel::before { + content: "\f20b"; } + +.fa-sheqel::before { + content: "\f20b"; } + +.fa-sheqel-sign::before { + content: "\f20b"; } + +.fa-map::before { + content: "\f279"; } + +.fa-rocket::before { + content: "\f135"; } + +.fa-photo-film::before { + content: "\f87c"; } + +.fa-photo-video::before { + content: "\f87c"; } + +.fa-folder-minus::before { + content: "\f65d"; } + +.fa-store::before { + content: "\f54e"; } + +.fa-arrow-trend-up::before { + content: "\e098"; } + +.fa-plug-circle-minus::before { + content: "\e55e"; } + +.fa-sign-hanging::before { + content: "\f4d9"; } + +.fa-sign::before { + content: "\f4d9"; } + +.fa-bezier-curve::before { + content: "\f55b"; } + +.fa-bell-slash::before { + content: "\f1f6"; } + +.fa-tablet::before { + content: "\f3fb"; } + +.fa-tablet-android::before { + content: "\f3fb"; } + +.fa-school-flag::before { + content: "\e56e"; } + +.fa-fill::before { + content: "\f575"; } + +.fa-angle-up::before { + content: "\f106"; } + +.fa-drumstick-bite::before { + content: "\f6d7"; } + +.fa-holly-berry::before { + content: "\f7aa"; } + +.fa-chevron-left::before { + content: "\f053"; } + +.fa-bacteria::before { + content: "\e059"; } + +.fa-hand-lizard::before { + content: "\f258"; } + +.fa-notdef::before { + content: "\e1fe"; } + +.fa-disease::before { + content: "\f7fa"; } + +.fa-briefcase-medical::before { + content: "\f469"; } + +.fa-genderless::before { + content: "\f22d"; } + +.fa-chevron-right::before { + content: "\f054"; } + +.fa-retweet::before { + content: "\f079"; } + +.fa-car-rear::before { + content: "\f5de"; } + +.fa-car-alt::before { + content: "\f5de"; } + +.fa-pump-soap::before { + content: "\e06b"; } + +.fa-video-slash::before { + content: "\f4e2"; } + +.fa-battery-quarter::before { + content: "\f243"; } + +.fa-battery-2::before { + content: "\f243"; } + +.fa-radio::before { + content: "\f8d7"; } + +.fa-baby-carriage::before { + content: "\f77d"; } + +.fa-carriage-baby::before { + content: "\f77d"; } + +.fa-traffic-light::before { + content: "\f637"; } + +.fa-thermometer::before { + content: "\f491"; } + +.fa-vr-cardboard::before { + content: "\f729"; } + +.fa-hand-middle-finger::before { + content: "\f806"; } + +.fa-percent::before { + content: "\25"; } + +.fa-percentage::before { + content: "\25"; } + +.fa-truck-moving::before { + content: "\f4df"; } + +.fa-glass-water-droplet::before { + content: "\e4f5"; } + +.fa-display::before { + content: "\e163"; } + +.fa-face-smile::before { + content: "\f118"; } + +.fa-smile::before { + content: "\f118"; } + +.fa-thumbtack::before { + content: "\f08d"; } + +.fa-thumb-tack::before { + content: "\f08d"; } + +.fa-trophy::before { + content: "\f091"; } + +.fa-person-praying::before { + content: "\f683"; } + +.fa-pray::before { + content: "\f683"; } + +.fa-hammer::before { + content: "\f6e3"; } + +.fa-hand-peace::before { + content: "\f25b"; } + +.fa-rotate::before { + content: "\f2f1"; } + +.fa-sync-alt::before { + content: "\f2f1"; } + +.fa-spinner::before { + content: "\f110"; } + +.fa-robot::before { + content: "\f544"; } + +.fa-peace::before { + content: "\f67c"; } + +.fa-gears::before { + content: "\f085"; } + +.fa-cogs::before { + content: "\f085"; } + +.fa-warehouse::before { + content: "\f494"; } + +.fa-arrow-up-right-dots::before { + content: "\e4b7"; } + +.fa-splotch::before { + content: "\f5bc"; } + +.fa-face-grin-hearts::before { + content: "\f584"; } + +.fa-grin-hearts::before { + content: "\f584"; } + +.fa-dice-four::before { + content: "\f524"; } + +.fa-sim-card::before { + content: "\f7c4"; } + +.fa-transgender::before { + content: "\f225"; } + +.fa-transgender-alt::before { + content: "\f225"; } + +.fa-mercury::before { + content: "\f223"; } + +.fa-arrow-turn-down::before { + content: "\f149"; } + +.fa-level-down::before { + content: "\f149"; } + +.fa-person-falling-burst::before { + content: "\e547"; } + +.fa-award::before { + content: "\f559"; } + +.fa-ticket-simple::before { + content: "\f3ff"; } + +.fa-ticket-alt::before { + content: "\f3ff"; } + +.fa-building::before { + content: "\f1ad"; } + +.fa-angles-left::before { + content: "\f100"; } + +.fa-angle-double-left::before { + content: "\f100"; } + +.fa-qrcode::before { + content: "\f029"; } + +.fa-clock-rotate-left::before { + content: "\f1da"; } + +.fa-history::before { + content: "\f1da"; } + +.fa-face-grin-beam-sweat::before { + content: "\f583"; } + +.fa-grin-beam-sweat::before { + content: "\f583"; } + +.fa-file-export::before { + content: "\f56e"; } + +.fa-arrow-right-from-file::before { + content: "\f56e"; } + +.fa-shield::before { + content: "\f132"; } + +.fa-shield-blank::before { + content: "\f132"; } + +.fa-arrow-up-short-wide::before { + content: "\f885"; } + +.fa-sort-amount-up-alt::before { + content: "\f885"; } + +.fa-house-medical::before { + content: "\e3b2"; } + +.fa-golf-ball-tee::before { + content: "\f450"; } + +.fa-golf-ball::before { + content: "\f450"; } + +.fa-circle-chevron-left::before { + content: "\f137"; } + +.fa-chevron-circle-left::before { + content: "\f137"; } + +.fa-house-chimney-window::before { + content: "\e00d"; } + +.fa-pen-nib::before { + content: "\f5ad"; } + +.fa-tent-arrow-turn-left::before { + content: "\e580"; } + +.fa-tents::before { + content: "\e582"; } + +.fa-wand-magic::before { + content: "\f0d0"; } + +.fa-magic::before { + content: "\f0d0"; } + +.fa-dog::before { + content: "\f6d3"; } + +.fa-carrot::before { + content: "\f787"; } + +.fa-moon::before { + content: "\f186"; } + +.fa-wine-glass-empty::before { + content: "\f5ce"; } + +.fa-wine-glass-alt::before { + content: "\f5ce"; } + +.fa-cheese::before { + content: "\f7ef"; } + +.fa-yin-yang::before { + content: "\f6ad"; } + +.fa-music::before { + content: "\f001"; } + +.fa-code-commit::before { + content: "\f386"; } + +.fa-temperature-low::before { + content: "\f76b"; } + +.fa-person-biking::before { + content: "\f84a"; } + +.fa-biking::before { + content: "\f84a"; } + +.fa-broom::before { + content: "\f51a"; } + +.fa-shield-heart::before { + content: "\e574"; } + +.fa-gopuram::before { + content: "\f664"; } + +.fa-earth-oceania::before { + content: "\e47b"; } + +.fa-globe-oceania::before { + content: "\e47b"; } + +.fa-square-xmark::before { + content: "\f2d3"; } + +.fa-times-square::before { + content: "\f2d3"; } + +.fa-xmark-square::before { + content: "\f2d3"; } + +.fa-hashtag::before { + content: "\23"; } + +.fa-up-right-and-down-left-from-center::before { + content: "\f424"; } + +.fa-expand-alt::before { + content: "\f424"; } + +.fa-oil-can::before { + content: "\f613"; } + +.fa-t::before { + content: "\54"; } + +.fa-hippo::before { + content: "\f6ed"; } + +.fa-chart-column::before { + content: "\e0e3"; } + +.fa-infinity::before { + content: "\f534"; } + +.fa-vial-circle-check::before { + content: "\e596"; } + +.fa-person-arrow-down-to-line::before { + content: "\e538"; } + +.fa-voicemail::before { + content: "\f897"; } + +.fa-fan::before { + content: "\f863"; } + +.fa-person-walking-luggage::before { + content: "\e554"; } + +.fa-up-down::before { + content: "\f338"; } + +.fa-arrows-alt-v::before { + content: "\f338"; } + +.fa-cloud-moon-rain::before { + content: "\f73c"; } + +.fa-calendar::before { + content: "\f133"; } + +.fa-trailer::before { + content: "\e041"; } + +.fa-bahai::before { + content: "\f666"; } + +.fa-haykal::before { + content: "\f666"; } + +.fa-sd-card::before { + content: "\f7c2"; } + +.fa-dragon::before { + content: "\f6d5"; } + +.fa-shoe-prints::before { + content: "\f54b"; } + +.fa-circle-plus::before { + content: "\f055"; } + +.fa-plus-circle::before { + content: "\f055"; } + +.fa-face-grin-tongue-wink::before { + content: "\f58b"; } + +.fa-grin-tongue-wink::before { + content: "\f58b"; } + +.fa-hand-holding::before { + content: "\f4bd"; } + +.fa-plug-circle-exclamation::before { + content: "\e55d"; } + +.fa-link-slash::before { + content: "\f127"; } + +.fa-chain-broken::before { + content: "\f127"; } + +.fa-chain-slash::before { + content: "\f127"; } + +.fa-unlink::before { + content: "\f127"; } + +.fa-clone::before { + content: "\f24d"; } + +.fa-person-walking-arrow-loop-left::before { + content: "\e551"; } + +.fa-arrow-up-z-a::before { + content: "\f882"; } + +.fa-sort-alpha-up-alt::before { + content: "\f882"; } + +.fa-fire-flame-curved::before { + content: "\f7e4"; } + +.fa-fire-alt::before { + content: "\f7e4"; } + +.fa-tornado::before { + content: "\f76f"; } + +.fa-file-circle-plus::before { + content: "\e494"; } + +.fa-book-quran::before { + content: "\f687"; } + +.fa-quran::before { + content: "\f687"; } + +.fa-anchor::before { + content: "\f13d"; } + +.fa-border-all::before { + content: "\f84c"; } + +.fa-face-angry::before { + content: "\f556"; } + +.fa-angry::before { + content: "\f556"; } + +.fa-cookie-bite::before { + content: "\f564"; } + +.fa-arrow-trend-down::before { + content: "\e097"; } + +.fa-rss::before { + content: "\f09e"; } + +.fa-feed::before { + content: "\f09e"; } + +.fa-draw-polygon::before { + content: "\f5ee"; } + +.fa-scale-balanced::before { + content: "\f24e"; } + +.fa-balance-scale::before { + content: "\f24e"; } + +.fa-gauge-simple-high::before { + content: "\f62a"; } + +.fa-tachometer::before { + content: "\f62a"; } + +.fa-tachometer-fast::before { + content: "\f62a"; } + +.fa-shower::before { + content: "\f2cc"; } + +.fa-desktop::before { + content: "\f390"; } + +.fa-desktop-alt::before { + content: "\f390"; } + +.fa-m::before { + content: "\4d"; } + +.fa-table-list::before { + content: "\f00b"; } + +.fa-th-list::before { + content: "\f00b"; } + +.fa-comment-sms::before { + content: "\f7cd"; } + +.fa-sms::before { + content: "\f7cd"; } + +.fa-book::before { + content: "\f02d"; } + +.fa-user-plus::before { + content: "\f234"; } + +.fa-check::before { + content: "\f00c"; } + +.fa-battery-three-quarters::before { + content: "\f241"; } + +.fa-battery-4::before { + content: "\f241"; } + +.fa-house-circle-check::before { + content: "\e509"; } + +.fa-angle-left::before { + content: "\f104"; } + +.fa-diagram-successor::before { + content: "\e47a"; } + +.fa-truck-arrow-right::before { + content: "\e58b"; } + +.fa-arrows-split-up-and-left::before { + content: "\e4bc"; } + +.fa-hand-fist::before { + content: "\f6de"; } + +.fa-fist-raised::before { + content: "\f6de"; } + +.fa-cloud-moon::before { + content: "\f6c3"; } + +.fa-briefcase::before { + content: "\f0b1"; } + +.fa-person-falling::before { + content: "\e546"; } + +.fa-image-portrait::before { + content: "\f3e0"; } + +.fa-portrait::before { + content: "\f3e0"; } + +.fa-user-tag::before { + content: "\f507"; } + +.fa-rug::before { + content: "\e569"; } + +.fa-earth-europe::before { + content: "\f7a2"; } + +.fa-globe-europe::before { + content: "\f7a2"; } + +.fa-cart-flatbed-suitcase::before { + content: "\f59d"; } + +.fa-luggage-cart::before { + content: "\f59d"; } + +.fa-rectangle-xmark::before { + content: "\f410"; } + +.fa-rectangle-times::before { + content: "\f410"; } + +.fa-times-rectangle::before { + content: "\f410"; } + +.fa-window-close::before { + content: "\f410"; } + +.fa-baht-sign::before { + content: "\e0ac"; } + +.fa-book-open::before { + content: "\f518"; } + +.fa-book-journal-whills::before { + content: "\f66a"; } + +.fa-journal-whills::before { + content: "\f66a"; } + +.fa-handcuffs::before { + content: "\e4f8"; } + +.fa-triangle-exclamation::before { + content: "\f071"; } + +.fa-exclamation-triangle::before { + content: "\f071"; } + +.fa-warning::before { + content: "\f071"; } + +.fa-database::before { + content: "\f1c0"; } + +.fa-share::before { + content: "\f064"; } + +.fa-mail-forward::before { + content: "\f064"; } + +.fa-bottle-droplet::before { + content: "\e4c4"; } + +.fa-mask-face::before { + content: "\e1d7"; } + +.fa-hill-rockslide::before { + content: "\e508"; } + +.fa-right-left::before { + content: "\f362"; } + +.fa-exchange-alt::before { + content: "\f362"; } + +.fa-paper-plane::before { + content: "\f1d8"; } + +.fa-road-circle-exclamation::before { + content: "\e565"; } + +.fa-dungeon::before { + content: "\f6d9"; } + +.fa-align-right::before { + content: "\f038"; } + +.fa-money-bill-1-wave::before { + content: "\f53b"; } + +.fa-money-bill-wave-alt::before { + content: "\f53b"; } + +.fa-life-ring::before { + content: "\f1cd"; } + +.fa-hands::before { + content: "\f2a7"; } + +.fa-sign-language::before { + content: "\f2a7"; } + +.fa-signing::before { + content: "\f2a7"; } + +.fa-calendar-day::before { + content: "\f783"; } + +.fa-water-ladder::before { + content: "\f5c5"; } + +.fa-ladder-water::before { + content: "\f5c5"; } + +.fa-swimming-pool::before { + content: "\f5c5"; } + +.fa-arrows-up-down::before { + content: "\f07d"; } + +.fa-arrows-v::before { + content: "\f07d"; } + +.fa-face-grimace::before { + content: "\f57f"; } + +.fa-grimace::before { + content: "\f57f"; } + +.fa-wheelchair-move::before { + content: "\e2ce"; } + +.fa-wheelchair-alt::before { + content: "\e2ce"; } + +.fa-turn-down::before { + content: "\f3be"; } + +.fa-level-down-alt::before { + content: "\f3be"; } + +.fa-person-walking-arrow-right::before { + content: "\e552"; } + +.fa-square-envelope::before { + content: "\f199"; } + +.fa-envelope-square::before { + content: "\f199"; } + +.fa-dice::before { + content: "\f522"; } + +.fa-bowling-ball::before { + content: "\f436"; } + +.fa-brain::before { + content: "\f5dc"; } + +.fa-bandage::before { + content: "\f462"; } + +.fa-band-aid::before { + content: "\f462"; } + +.fa-calendar-minus::before { + content: "\f272"; } + +.fa-circle-xmark::before { + content: "\f057"; } + +.fa-times-circle::before { + content: "\f057"; } + +.fa-xmark-circle::before { + content: "\f057"; } + +.fa-gifts::before { + content: "\f79c"; } + +.fa-hotel::before { + content: "\f594"; } + +.fa-earth-asia::before { + content: "\f57e"; } + +.fa-globe-asia::before { + content: "\f57e"; } + +.fa-id-card-clip::before { + content: "\f47f"; } + +.fa-id-card-alt::before { + content: "\f47f"; } + +.fa-magnifying-glass-plus::before { + content: "\f00e"; } + +.fa-search-plus::before { + content: "\f00e"; } + +.fa-thumbs-up::before { + content: "\f164"; } + +.fa-user-clock::before { + content: "\f4fd"; } + +.fa-hand-dots::before { + content: "\f461"; } + +.fa-allergies::before { + content: "\f461"; } + +.fa-file-invoice::before { + content: "\f570"; } + +.fa-window-minimize::before { + content: "\f2d1"; } + +.fa-mug-saucer::before { + content: "\f0f4"; } + +.fa-coffee::before { + content: "\f0f4"; } + +.fa-brush::before { + content: "\f55d"; } + +.fa-mask::before { + content: "\f6fa"; } + +.fa-magnifying-glass-minus::before { + content: "\f010"; } + +.fa-search-minus::before { + content: "\f010"; } + +.fa-ruler-vertical::before { + content: "\f548"; } + +.fa-user-large::before { + content: "\f406"; } + +.fa-user-alt::before { + content: "\f406"; } + +.fa-train-tram::before { + content: "\e5b4"; } + +.fa-user-nurse::before { + content: "\f82f"; } + +.fa-syringe::before { + content: "\f48e"; } + +.fa-cloud-sun::before { + content: "\f6c4"; } + +.fa-stopwatch-20::before { + content: "\e06f"; } + +.fa-square-full::before { + content: "\f45c"; } + +.fa-magnet::before { + content: "\f076"; } + +.fa-jar::before { + content: "\e516"; } + +.fa-note-sticky::before { + content: "\f249"; } + +.fa-sticky-note::before { + content: "\f249"; } + +.fa-bug-slash::before { + content: "\e490"; } + +.fa-arrow-up-from-water-pump::before { + content: "\e4b6"; } + +.fa-bone::before { + content: "\f5d7"; } + +.fa-user-injured::before { + content: "\f728"; } + +.fa-face-sad-tear::before { + content: "\f5b4"; } + +.fa-sad-tear::before { + content: "\f5b4"; } + +.fa-plane::before { + content: "\f072"; } + +.fa-tent-arrows-down::before { + content: "\e581"; } + +.fa-exclamation::before { + content: "\21"; } + +.fa-arrows-spin::before { + content: "\e4bb"; } + +.fa-print::before { + content: "\f02f"; } + +.fa-turkish-lira-sign::before { + content: "\e2bb"; } + +.fa-try::before { + content: "\e2bb"; } + +.fa-turkish-lira::before { + content: "\e2bb"; } + +.fa-dollar-sign::before { + content: "\24"; } + +.fa-dollar::before { + content: "\24"; } + +.fa-usd::before { + content: "\24"; } + +.fa-x::before { + content: "\58"; } + +.fa-magnifying-glass-dollar::before { + content: "\f688"; } + +.fa-search-dollar::before { + content: "\f688"; } + +.fa-users-gear::before { + content: "\f509"; } + +.fa-users-cog::before { + content: "\f509"; } + +.fa-person-military-pointing::before { + content: "\e54a"; } + +.fa-building-columns::before { + content: "\f19c"; } + +.fa-bank::before { + content: "\f19c"; } + +.fa-institution::before { + content: "\f19c"; } + +.fa-museum::before { + content: "\f19c"; } + +.fa-university::before { + content: "\f19c"; } + +.fa-umbrella::before { + content: "\f0e9"; } + +.fa-trowel::before { + content: "\e589"; } + +.fa-d::before { + content: "\44"; } + +.fa-stapler::before { + content: "\e5af"; } + +.fa-masks-theater::before { + content: "\f630"; } + +.fa-theater-masks::before { + content: "\f630"; } + +.fa-kip-sign::before { + content: "\e1c4"; } + +.fa-hand-point-left::before { + content: "\f0a5"; } + +.fa-handshake-simple::before { + content: "\f4c6"; } + +.fa-handshake-alt::before { + content: "\f4c6"; } + +.fa-jet-fighter::before { + content: "\f0fb"; } + +.fa-fighter-jet::before { + content: "\f0fb"; } + +.fa-square-share-nodes::before { + content: "\f1e1"; } + +.fa-share-alt-square::before { + content: "\f1e1"; } + +.fa-barcode::before { + content: "\f02a"; } + +.fa-plus-minus::before { + content: "\e43c"; } + +.fa-video::before { + content: "\f03d"; } + +.fa-video-camera::before { + content: "\f03d"; } + +.fa-graduation-cap::before { + content: "\f19d"; } + +.fa-mortar-board::before { + content: "\f19d"; } + +.fa-hand-holding-medical::before { + content: "\e05c"; } + +.fa-person-circle-check::before { + content: "\e53e"; } + +.fa-turn-up::before { + content: "\f3bf"; } + +.fa-level-up-alt::before { + content: "\f3bf"; } + +.sr-only, +.fa-sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; } + +.sr-only-focusable:not(:focus), +.fa-sr-only-focusable:not(:focus) { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; } +:root, :host { + --fa-style-family-brands: 'Font Awesome 6 Brands'; + --fa-font-brands: normal 400 1em/1 'Font Awesome 6 Brands'; } + +@font-face { + font-family: 'Font Awesome 6 Brands'; + font-style: normal; + font-weight: 400; + font-display: block; + src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); } + +.fab, +.fa-brands { + font-weight: 400; } + +.fa-monero:before { + content: "\f3d0"; } + +.fa-hooli:before { + content: "\f427"; } + +.fa-yelp:before { + content: "\f1e9"; } + +.fa-cc-visa:before { + content: "\f1f0"; } + +.fa-lastfm:before { + content: "\f202"; } + +.fa-shopware:before { + content: "\f5b5"; } + +.fa-creative-commons-nc:before { + content: "\f4e8"; } + +.fa-aws:before { + content: "\f375"; } + +.fa-redhat:before { + content: "\f7bc"; } + +.fa-yoast:before { + content: "\f2b1"; } + +.fa-cloudflare:before { + content: "\e07d"; } + +.fa-ups:before { + content: "\f7e0"; } + +.fa-pixiv:before { + content: "\e640"; } + +.fa-wpexplorer:before { + content: "\f2de"; } + +.fa-dyalog:before { + content: "\f399"; } + +.fa-bity:before { + content: "\f37a"; } + +.fa-stackpath:before { + content: "\f842"; } + +.fa-buysellads:before { + content: "\f20d"; } + +.fa-first-order:before { + content: "\f2b0"; } + +.fa-modx:before { + content: "\f285"; } + +.fa-guilded:before { + content: "\e07e"; } + +.fa-vnv:before { + content: "\f40b"; } + +.fa-square-js:before { + content: "\f3b9"; } + +.fa-js-square:before { + content: "\f3b9"; } + +.fa-microsoft:before { + content: "\f3ca"; } + +.fa-qq:before { + content: "\f1d6"; } + +.fa-orcid:before { + content: "\f8d2"; } + +.fa-java:before { + content: "\f4e4"; } + +.fa-invision:before { + content: "\f7b0"; } + +.fa-creative-commons-pd-alt:before { + content: "\f4ed"; } + +.fa-centercode:before { + content: "\f380"; } + +.fa-glide-g:before { + content: "\f2a6"; } + +.fa-drupal:before { + content: "\f1a9"; } + +.fa-hire-a-helper:before { + content: "\f3b0"; } + +.fa-creative-commons-by:before { + content: "\f4e7"; } + +.fa-unity:before { + content: "\e049"; } + +.fa-whmcs:before { + content: "\f40d"; } + +.fa-rocketchat:before { + content: "\f3e8"; } + +.fa-vk:before { + content: "\f189"; } + +.fa-untappd:before { + content: "\f405"; } + +.fa-mailchimp:before { + content: "\f59e"; } + +.fa-css3-alt:before { + content: "\f38b"; } + +.fa-square-reddit:before { + content: "\f1a2"; } + +.fa-reddit-square:before { + content: "\f1a2"; } + +.fa-vimeo-v:before { + content: "\f27d"; } + +.fa-contao:before { + content: "\f26d"; } + +.fa-square-font-awesome:before { + content: "\e5ad"; } + +.fa-deskpro:before { + content: "\f38f"; } + +.fa-brave:before { + content: "\e63c"; } + +.fa-sistrix:before { + content: "\f3ee"; } + +.fa-square-instagram:before { + content: "\e055"; } + +.fa-instagram-square:before { + content: "\e055"; } + +.fa-battle-net:before { + content: "\f835"; } + +.fa-the-red-yeti:before { + content: "\f69d"; } + +.fa-square-hacker-news:before { + content: "\f3af"; } + +.fa-hacker-news-square:before { + content: "\f3af"; } + +.fa-edge:before { + content: "\f282"; } + +.fa-threads:before { + content: "\e618"; } + +.fa-napster:before { + content: "\f3d2"; } + +.fa-square-snapchat:before { + content: "\f2ad"; } + +.fa-snapchat-square:before { + content: "\f2ad"; } + +.fa-google-plus-g:before { + content: "\f0d5"; } + +.fa-artstation:before { + content: "\f77a"; } + +.fa-markdown:before { + content: "\f60f"; } + +.fa-sourcetree:before { + content: "\f7d3"; } + +.fa-google-plus:before { + content: "\f2b3"; } + +.fa-diaspora:before { + content: "\f791"; } + +.fa-foursquare:before { + content: "\f180"; } + +.fa-stack-overflow:before { + content: "\f16c"; } + +.fa-github-alt:before { + content: "\f113"; } + +.fa-phoenix-squadron:before { + content: "\f511"; } + +.fa-pagelines:before { + content: "\f18c"; } + +.fa-algolia:before { + content: "\f36c"; } + +.fa-red-river:before { + content: "\f3e3"; } + +.fa-creative-commons-sa:before { + content: "\f4ef"; } + +.fa-safari:before { + content: "\f267"; } + +.fa-google:before { + content: "\f1a0"; } + +.fa-square-font-awesome-stroke:before { + content: "\f35c"; } + +.fa-font-awesome-alt:before { + content: "\f35c"; } + +.fa-atlassian:before { + content: "\f77b"; } + +.fa-linkedin-in:before { + content: "\f0e1"; } + +.fa-digital-ocean:before { + content: "\f391"; } + +.fa-nimblr:before { + content: "\f5a8"; } + +.fa-chromecast:before { + content: "\f838"; } + +.fa-evernote:before { + content: "\f839"; } + +.fa-hacker-news:before { + content: "\f1d4"; } + +.fa-creative-commons-sampling:before { + content: "\f4f0"; } + +.fa-adversal:before { + content: "\f36a"; } + +.fa-creative-commons:before { + content: "\f25e"; } + +.fa-watchman-monitoring:before { + content: "\e087"; } + +.fa-fonticons:before { + content: "\f280"; } + +.fa-weixin:before { + content: "\f1d7"; } + +.fa-shirtsinbulk:before { + content: "\f214"; } + +.fa-codepen:before { + content: "\f1cb"; } + +.fa-git-alt:before { + content: "\f841"; } + +.fa-lyft:before { + content: "\f3c3"; } + +.fa-rev:before { + content: "\f5b2"; } + +.fa-windows:before { + content: "\f17a"; } + +.fa-wizards-of-the-coast:before { + content: "\f730"; } + +.fa-square-viadeo:before { + content: "\f2aa"; } + +.fa-viadeo-square:before { + content: "\f2aa"; } + +.fa-meetup:before { + content: "\f2e0"; } + +.fa-centos:before { + content: "\f789"; } + +.fa-adn:before { + content: "\f170"; } + +.fa-cloudsmith:before { + content: "\f384"; } + +.fa-opensuse:before { + content: "\e62b"; } + +.fa-pied-piper-alt:before { + content: "\f1a8"; } + +.fa-square-dribbble:before { + content: "\f397"; } + +.fa-dribbble-square:before { + content: "\f397"; } + +.fa-codiepie:before { + content: "\f284"; } + +.fa-node:before { + content: "\f419"; } + +.fa-mix:before { + content: "\f3cb"; } + +.fa-steam:before { + content: "\f1b6"; } + +.fa-cc-apple-pay:before { + content: "\f416"; } + +.fa-scribd:before { + content: "\f28a"; } + +.fa-debian:before { + content: "\e60b"; } + +.fa-openid:before { + content: "\f19b"; } + +.fa-instalod:before { + content: "\e081"; } + +.fa-expeditedssl:before { + content: "\f23e"; } + +.fa-sellcast:before { + content: "\f2da"; } + +.fa-square-twitter:before { + content: "\f081"; } + +.fa-twitter-square:before { + content: "\f081"; } + +.fa-r-project:before { + content: "\f4f7"; } + +.fa-delicious:before { + content: "\f1a5"; } + +.fa-freebsd:before { + content: "\f3a4"; } + +.fa-vuejs:before { + content: "\f41f"; } + +.fa-accusoft:before { + content: "\f369"; } + +.fa-ioxhost:before { + content: "\f208"; } + +.fa-fonticons-fi:before { + content: "\f3a2"; } + +.fa-app-store:before { + content: "\f36f"; } + +.fa-cc-mastercard:before { + content: "\f1f1"; } + +.fa-itunes-note:before { + content: "\f3b5"; } + +.fa-golang:before { + content: "\e40f"; } + +.fa-kickstarter:before { + content: "\f3bb"; } + +.fa-grav:before { + content: "\f2d6"; } + +.fa-weibo:before { + content: "\f18a"; } + +.fa-uncharted:before { + content: "\e084"; } + +.fa-firstdraft:before { + content: "\f3a1"; } + +.fa-square-youtube:before { + content: "\f431"; } + +.fa-youtube-square:before { + content: "\f431"; } + +.fa-wikipedia-w:before { + content: "\f266"; } + +.fa-wpressr:before { + content: "\f3e4"; } + +.fa-rendact:before { + content: "\f3e4"; } + +.fa-angellist:before { + content: "\f209"; } + +.fa-galactic-republic:before { + content: "\f50c"; } + +.fa-nfc-directional:before { + content: "\e530"; } + +.fa-skype:before { + content: "\f17e"; } + +.fa-joget:before { + content: "\f3b7"; } + +.fa-fedora:before { + content: "\f798"; } + +.fa-stripe-s:before { + content: "\f42a"; } + +.fa-meta:before { + content: "\e49b"; } + +.fa-laravel:before { + content: "\f3bd"; } + +.fa-hotjar:before { + content: "\f3b1"; } + +.fa-bluetooth-b:before { + content: "\f294"; } + +.fa-square-letterboxd:before { + content: "\e62e"; } + +.fa-sticker-mule:before { + content: "\f3f7"; } + +.fa-creative-commons-zero:before { + content: "\f4f3"; } + +.fa-hips:before { + content: "\f452"; } + +.fa-behance:before { + content: "\f1b4"; } + +.fa-reddit:before { + content: "\f1a1"; } + +.fa-discord:before { + content: "\f392"; } + +.fa-chrome:before { + content: "\f268"; } + +.fa-app-store-ios:before { + content: "\f370"; } + +.fa-cc-discover:before { + content: "\f1f2"; } + +.fa-wpbeginner:before { + content: "\f297"; } + +.fa-confluence:before { + content: "\f78d"; } + +.fa-shoelace:before { + content: "\e60c"; } + +.fa-mdb:before { + content: "\f8ca"; } + +.fa-dochub:before { + content: "\f394"; } + +.fa-accessible-icon:before { + content: "\f368"; } + +.fa-ebay:before { + content: "\f4f4"; } + +.fa-amazon:before { + content: "\f270"; } + +.fa-unsplash:before { + content: "\e07c"; } + +.fa-yarn:before { + content: "\f7e3"; } + +.fa-square-steam:before { + content: "\f1b7"; } + +.fa-steam-square:before { + content: "\f1b7"; } + +.fa-500px:before { + content: "\f26e"; } + +.fa-square-vimeo:before { + content: "\f194"; } + +.fa-vimeo-square:before { + content: "\f194"; } + +.fa-asymmetrik:before { + content: "\f372"; } + +.fa-font-awesome:before { + content: "\f2b4"; } + +.fa-font-awesome-flag:before { + content: "\f2b4"; } + +.fa-font-awesome-logo-full:before { + content: "\f2b4"; } + +.fa-gratipay:before { + content: "\f184"; } + +.fa-apple:before { + content: "\f179"; } + +.fa-hive:before { + content: "\e07f"; } + +.fa-gitkraken:before { + content: "\f3a6"; } + +.fa-keybase:before { + content: "\f4f5"; } + +.fa-apple-pay:before { + content: "\f415"; } + +.fa-padlet:before { + content: "\e4a0"; } + +.fa-amazon-pay:before { + content: "\f42c"; } + +.fa-square-github:before { + content: "\f092"; } + +.fa-github-square:before { + content: "\f092"; } + +.fa-stumbleupon:before { + content: "\f1a4"; } + +.fa-fedex:before { + content: "\f797"; } + +.fa-phoenix-framework:before { + content: "\f3dc"; } + +.fa-shopify:before { + content: "\e057"; } + +.fa-neos:before { + content: "\f612"; } + +.fa-square-threads:before { + content: "\e619"; } + +.fa-hackerrank:before { + content: "\f5f7"; } + +.fa-researchgate:before { + content: "\f4f8"; } + +.fa-swift:before { + content: "\f8e1"; } + +.fa-angular:before { + content: "\f420"; } + +.fa-speakap:before { + content: "\f3f3"; } + +.fa-angrycreative:before { + content: "\f36e"; } + +.fa-y-combinator:before { + content: "\f23b"; } + +.fa-empire:before { + content: "\f1d1"; } + +.fa-envira:before { + content: "\f299"; } + +.fa-google-scholar:before { + content: "\e63b"; } + +.fa-square-gitlab:before { + content: "\e5ae"; } + +.fa-gitlab-square:before { + content: "\e5ae"; } + +.fa-studiovinari:before { + content: "\f3f8"; } + +.fa-pied-piper:before { + content: "\f2ae"; } + +.fa-wordpress:before { + content: "\f19a"; } + +.fa-product-hunt:before { + content: "\f288"; } + +.fa-firefox:before { + content: "\f269"; } + +.fa-linode:before { + content: "\f2b8"; } + +.fa-goodreads:before { + content: "\f3a8"; } + +.fa-square-odnoklassniki:before { + content: "\f264"; } + +.fa-odnoklassniki-square:before { + content: "\f264"; } + +.fa-jsfiddle:before { + content: "\f1cc"; } + +.fa-sith:before { + content: "\f512"; } + +.fa-themeisle:before { + content: "\f2b2"; } + +.fa-page4:before { + content: "\f3d7"; } + +.fa-hashnode:before { + content: "\e499"; } + +.fa-react:before { + content: "\f41b"; } + +.fa-cc-paypal:before { + content: "\f1f4"; } + +.fa-squarespace:before { + content: "\f5be"; } + +.fa-cc-stripe:before { + content: "\f1f5"; } + +.fa-creative-commons-share:before { + content: "\f4f2"; } + +.fa-bitcoin:before { + content: "\f379"; } + +.fa-keycdn:before { + content: "\f3ba"; } + +.fa-opera:before { + content: "\f26a"; } + +.fa-itch-io:before { + content: "\f83a"; } + +.fa-umbraco:before { + content: "\f8e8"; } + +.fa-galactic-senate:before { + content: "\f50d"; } + +.fa-ubuntu:before { + content: "\f7df"; } + +.fa-draft2digital:before { + content: "\f396"; } + +.fa-stripe:before { + content: "\f429"; } + +.fa-houzz:before { + content: "\f27c"; } + +.fa-gg:before { + content: "\f260"; } + +.fa-dhl:before { + content: "\f790"; } + +.fa-square-pinterest:before { + content: "\f0d3"; } + +.fa-pinterest-square:before { + content: "\f0d3"; } + +.fa-xing:before { + content: "\f168"; } + +.fa-blackberry:before { + content: "\f37b"; } + +.fa-creative-commons-pd:before { + content: "\f4ec"; } + +.fa-playstation:before { + content: "\f3df"; } + +.fa-quinscape:before { + content: "\f459"; } + +.fa-less:before { + content: "\f41d"; } + +.fa-blogger-b:before { + content: "\f37d"; } + +.fa-opencart:before { + content: "\f23d"; } + +.fa-vine:before { + content: "\f1ca"; } + +.fa-signal-messenger:before { + content: "\e663"; } + +.fa-paypal:before { + content: "\f1ed"; } + +.fa-gitlab:before { + content: "\f296"; } + +.fa-typo3:before { + content: "\f42b"; } + +.fa-reddit-alien:before { + content: "\f281"; } + +.fa-yahoo:before { + content: "\f19e"; } + +.fa-dailymotion:before { + content: "\e052"; } + +.fa-affiliatetheme:before { + content: "\f36b"; } + +.fa-pied-piper-pp:before { + content: "\f1a7"; } + +.fa-bootstrap:before { + content: "\f836"; } + +.fa-odnoklassniki:before { + content: "\f263"; } + +.fa-nfc-symbol:before { + content: "\e531"; } + +.fa-mintbit:before { + content: "\e62f"; } + +.fa-ethereum:before { + content: "\f42e"; } + +.fa-speaker-deck:before { + content: "\f83c"; } + +.fa-creative-commons-nc-eu:before { + content: "\f4e9"; } + +.fa-patreon:before { + content: "\f3d9"; } + +.fa-avianex:before { + content: "\f374"; } + +.fa-ello:before { + content: "\f5f1"; } + +.fa-gofore:before { + content: "\f3a7"; } + +.fa-bimobject:before { + content: "\f378"; } + +.fa-brave-reverse:before { + content: "\e63d"; } + +.fa-facebook-f:before { + content: "\f39e"; } + +.fa-square-google-plus:before { + content: "\f0d4"; } + +.fa-google-plus-square:before { + content: "\f0d4"; } + +.fa-mandalorian:before { + content: "\f50f"; } + +.fa-first-order-alt:before { + content: "\f50a"; } + +.fa-osi:before { + content: "\f41a"; } + +.fa-google-wallet:before { + content: "\f1ee"; } + +.fa-d-and-d-beyond:before { + content: "\f6ca"; } + +.fa-periscope:before { + content: "\f3da"; } + +.fa-fulcrum:before { + content: "\f50b"; } + +.fa-cloudscale:before { + content: "\f383"; } + +.fa-forumbee:before { + content: "\f211"; } + +.fa-mizuni:before { + content: "\f3cc"; } + +.fa-schlix:before { + content: "\f3ea"; } + +.fa-square-xing:before { + content: "\f169"; } + +.fa-xing-square:before { + content: "\f169"; } + +.fa-bandcamp:before { + content: "\f2d5"; } + +.fa-wpforms:before { + content: "\f298"; } + +.fa-cloudversify:before { + content: "\f385"; } + +.fa-usps:before { + content: "\f7e1"; } + +.fa-megaport:before { + content: "\f5a3"; } + +.fa-magento:before { + content: "\f3c4"; } + +.fa-spotify:before { + content: "\f1bc"; } + +.fa-optin-monster:before { + content: "\f23c"; } + +.fa-fly:before { + content: "\f417"; } + +.fa-aviato:before { + content: "\f421"; } + +.fa-itunes:before { + content: "\f3b4"; } + +.fa-cuttlefish:before { + content: "\f38c"; } + +.fa-blogger:before { + content: "\f37c"; } + +.fa-flickr:before { + content: "\f16e"; } + +.fa-viber:before { + content: "\f409"; } + +.fa-soundcloud:before { + content: "\f1be"; } + +.fa-digg:before { + content: "\f1a6"; } + +.fa-tencent-weibo:before { + content: "\f1d5"; } + +.fa-letterboxd:before { + content: "\e62d"; } + +.fa-symfony:before { + content: "\f83d"; } + +.fa-maxcdn:before { + content: "\f136"; } + +.fa-etsy:before { + content: "\f2d7"; } + +.fa-facebook-messenger:before { + content: "\f39f"; } + +.fa-audible:before { + content: "\f373"; } + +.fa-think-peaks:before { + content: "\f731"; } + +.fa-bilibili:before { + content: "\e3d9"; } + +.fa-erlang:before { + content: "\f39d"; } + +.fa-x-twitter:before { + content: "\e61b"; } + +.fa-cotton-bureau:before { + content: "\f89e"; } + +.fa-dashcube:before { + content: "\f210"; } + +.fa-42-group:before { + content: "\e080"; } + +.fa-innosoft:before { + content: "\e080"; } + +.fa-stack-exchange:before { + content: "\f18d"; } + +.fa-elementor:before { + content: "\f430"; } + +.fa-square-pied-piper:before { + content: "\e01e"; } + +.fa-pied-piper-square:before { + content: "\e01e"; } + +.fa-creative-commons-nd:before { + content: "\f4eb"; } + +.fa-palfed:before { + content: "\f3d8"; } + +.fa-superpowers:before { + content: "\f2dd"; } + +.fa-resolving:before { + content: "\f3e7"; } + +.fa-xbox:before { + content: "\f412"; } + +.fa-searchengin:before { + content: "\f3eb"; } + +.fa-tiktok:before { + content: "\e07b"; } + +.fa-square-facebook:before { + content: "\f082"; } + +.fa-facebook-square:before { + content: "\f082"; } + +.fa-renren:before { + content: "\f18b"; } + +.fa-linux:before { + content: "\f17c"; } + +.fa-glide:before { + content: "\f2a5"; } + +.fa-linkedin:before { + content: "\f08c"; } + +.fa-hubspot:before { + content: "\f3b2"; } + +.fa-deploydog:before { + content: "\f38e"; } + +.fa-twitch:before { + content: "\f1e8"; } + +.fa-ravelry:before { + content: "\f2d9"; } + +.fa-mixer:before { + content: "\e056"; } + +.fa-square-lastfm:before { + content: "\f203"; } + +.fa-lastfm-square:before { + content: "\f203"; } + +.fa-vimeo:before { + content: "\f40a"; } + +.fa-mendeley:before { + content: "\f7b3"; } + +.fa-uniregistry:before { + content: "\f404"; } + +.fa-figma:before { + content: "\f799"; } + +.fa-creative-commons-remix:before { + content: "\f4ee"; } + +.fa-cc-amazon-pay:before { + content: "\f42d"; } + +.fa-dropbox:before { + content: "\f16b"; } + +.fa-instagram:before { + content: "\f16d"; } + +.fa-cmplid:before { + content: "\e360"; } + +.fa-upwork:before { + content: "\e641"; } + +.fa-facebook:before { + content: "\f09a"; } + +.fa-gripfire:before { + content: "\f3ac"; } + +.fa-jedi-order:before { + content: "\f50e"; } + +.fa-uikit:before { + content: "\f403"; } + +.fa-fort-awesome-alt:before { + content: "\f3a3"; } + +.fa-phabricator:before { + content: "\f3db"; } + +.fa-ussunnah:before { + content: "\f407"; } + +.fa-earlybirds:before { + content: "\f39a"; } + +.fa-trade-federation:before { + content: "\f513"; } + +.fa-autoprefixer:before { + content: "\f41c"; } + +.fa-whatsapp:before { + content: "\f232"; } + +.fa-slideshare:before { + content: "\f1e7"; } + +.fa-google-play:before { + content: "\f3ab"; } + +.fa-viadeo:before { + content: "\f2a9"; } + +.fa-line:before { + content: "\f3c0"; } + +.fa-google-drive:before { + content: "\f3aa"; } + +.fa-servicestack:before { + content: "\f3ec"; } + +.fa-simplybuilt:before { + content: "\f215"; } + +.fa-bitbucket:before { + content: "\f171"; } + +.fa-imdb:before { + content: "\f2d8"; } + +.fa-deezer:before { + content: "\e077"; } + +.fa-raspberry-pi:before { + content: "\f7bb"; } + +.fa-jira:before { + content: "\f7b1"; } + +.fa-docker:before { + content: "\f395"; } + +.fa-screenpal:before { + content: "\e570"; } + +.fa-bluetooth:before { + content: "\f293"; } + +.fa-gitter:before { + content: "\f426"; } + +.fa-d-and-d:before { + content: "\f38d"; } + +.fa-microblog:before { + content: "\e01a"; } + +.fa-cc-diners-club:before { + content: "\f24c"; } + +.fa-gg-circle:before { + content: "\f261"; } + +.fa-pied-piper-hat:before { + content: "\f4e5"; } + +.fa-kickstarter-k:before { + content: "\f3bc"; } + +.fa-yandex:before { + content: "\f413"; } + +.fa-readme:before { + content: "\f4d5"; } + +.fa-html5:before { + content: "\f13b"; } + +.fa-sellsy:before { + content: "\f213"; } + +.fa-sass:before { + content: "\f41e"; } + +.fa-wirsindhandwerk:before { + content: "\e2d0"; } + +.fa-wsh:before { + content: "\e2d0"; } + +.fa-buromobelexperte:before { + content: "\f37f"; } + +.fa-salesforce:before { + content: "\f83b"; } + +.fa-octopus-deploy:before { + content: "\e082"; } + +.fa-medapps:before { + content: "\f3c6"; } + +.fa-ns8:before { + content: "\f3d5"; } + +.fa-pinterest-p:before { + content: "\f231"; } + +.fa-apper:before { + content: "\f371"; } + +.fa-fort-awesome:before { + content: "\f286"; } + +.fa-waze:before { + content: "\f83f"; } + +.fa-cc-jcb:before { + content: "\f24b"; } + +.fa-snapchat:before { + content: "\f2ab"; } + +.fa-snapchat-ghost:before { + content: "\f2ab"; } + +.fa-fantasy-flight-games:before { + content: "\f6dc"; } + +.fa-rust:before { + content: "\e07a"; } + +.fa-wix:before { + content: "\f5cf"; } + +.fa-square-behance:before { + content: "\f1b5"; } + +.fa-behance-square:before { + content: "\f1b5"; } + +.fa-supple:before { + content: "\f3f9"; } + +.fa-webflow:before { + content: "\e65c"; } + +.fa-rebel:before { + content: "\f1d0"; } + +.fa-css3:before { + content: "\f13c"; } + +.fa-staylinked:before { + content: "\f3f5"; } + +.fa-kaggle:before { + content: "\f5fa"; } + +.fa-space-awesome:before { + content: "\e5ac"; } + +.fa-deviantart:before { + content: "\f1bd"; } + +.fa-cpanel:before { + content: "\f388"; } + +.fa-goodreads-g:before { + content: "\f3a9"; } + +.fa-square-git:before { + content: "\f1d2"; } + +.fa-git-square:before { + content: "\f1d2"; } + +.fa-square-tumblr:before { + content: "\f174"; } + +.fa-tumblr-square:before { + content: "\f174"; } + +.fa-trello:before { + content: "\f181"; } + +.fa-creative-commons-nc-jp:before { + content: "\f4ea"; } + +.fa-get-pocket:before { + content: "\f265"; } + +.fa-perbyte:before { + content: "\e083"; } + +.fa-grunt:before { + content: "\f3ad"; } + +.fa-weebly:before { + content: "\f5cc"; } + +.fa-connectdevelop:before { + content: "\f20e"; } + +.fa-leanpub:before { + content: "\f212"; } + +.fa-black-tie:before { + content: "\f27e"; } + +.fa-themeco:before { + content: "\f5c6"; } + +.fa-python:before { + content: "\f3e2"; } + +.fa-android:before { + content: "\f17b"; } + +.fa-bots:before { + content: "\e340"; } + +.fa-free-code-camp:before { + content: "\f2c5"; } + +.fa-hornbill:before { + content: "\f592"; } + +.fa-js:before { + content: "\f3b8"; } + +.fa-ideal:before { + content: "\e013"; } + +.fa-git:before { + content: "\f1d3"; } + +.fa-dev:before { + content: "\f6cc"; } + +.fa-sketch:before { + content: "\f7c6"; } + +.fa-yandex-international:before { + content: "\f414"; } + +.fa-cc-amex:before { + content: "\f1f3"; } + +.fa-uber:before { + content: "\f402"; } + +.fa-github:before { + content: "\f09b"; } + +.fa-php:before { + content: "\f457"; } + +.fa-alipay:before { + content: "\f642"; } + +.fa-youtube:before { + content: "\f167"; } + +.fa-skyatlas:before { + content: "\f216"; } + +.fa-firefox-browser:before { + content: "\e007"; } + +.fa-replyd:before { + content: "\f3e6"; } + +.fa-suse:before { + content: "\f7d6"; } + +.fa-jenkins:before { + content: "\f3b6"; } + +.fa-twitter:before { + content: "\f099"; } + +.fa-rockrms:before { + content: "\f3e9"; } + +.fa-pinterest:before { + content: "\f0d2"; } + +.fa-buffer:before { + content: "\f837"; } + +.fa-npm:before { + content: "\f3d4"; } + +.fa-yammer:before { + content: "\f840"; } + +.fa-btc:before { + content: "\f15a"; } + +.fa-dribbble:before { + content: "\f17d"; } + +.fa-stumbleupon-circle:before { + content: "\f1a3"; } + +.fa-internet-explorer:before { + content: "\f26b"; } + +.fa-stubber:before { + content: "\e5c7"; } + +.fa-telegram:before { + content: "\f2c6"; } + +.fa-telegram-plane:before { + content: "\f2c6"; } + +.fa-old-republic:before { + content: "\f510"; } + +.fa-odysee:before { + content: "\e5c6"; } + +.fa-square-whatsapp:before { + content: "\f40c"; } + +.fa-whatsapp-square:before { + content: "\f40c"; } + +.fa-node-js:before { + content: "\f3d3"; } + +.fa-edge-legacy:before { + content: "\e078"; } + +.fa-slack:before { + content: "\f198"; } + +.fa-slack-hash:before { + content: "\f198"; } + +.fa-medrt:before { + content: "\f3c8"; } + +.fa-usb:before { + content: "\f287"; } + +.fa-tumblr:before { + content: "\f173"; } + +.fa-vaadin:before { + content: "\f408"; } + +.fa-quora:before { + content: "\f2c4"; } + +.fa-square-x-twitter:before { + content: "\e61a"; } + +.fa-reacteurope:before { + content: "\f75d"; } + +.fa-medium:before { + content: "\f23a"; } + +.fa-medium-m:before { + content: "\f23a"; } + +.fa-amilia:before { + content: "\f36d"; } + +.fa-mixcloud:before { + content: "\f289"; } + +.fa-flipboard:before { + content: "\f44d"; } + +.fa-viacoin:before { + content: "\f237"; } + +.fa-critical-role:before { + content: "\f6c9"; } + +.fa-sitrox:before { + content: "\e44a"; } + +.fa-discourse:before { + content: "\f393"; } + +.fa-joomla:before { + content: "\f1aa"; } + +.fa-mastodon:before { + content: "\f4f6"; } + +.fa-airbnb:before { + content: "\f834"; } + +.fa-wolf-pack-battalion:before { + content: "\f514"; } + +.fa-buy-n-large:before { + content: "\f8a6"; } + +.fa-gulp:before { + content: "\f3ae"; } + +.fa-creative-commons-sampling-plus:before { + content: "\f4f1"; } + +.fa-strava:before { + content: "\f428"; } + +.fa-ember:before { + content: "\f423"; } + +.fa-canadian-maple-leaf:before { + content: "\f785"; } + +.fa-teamspeak:before { + content: "\f4f9"; } + +.fa-pushed:before { + content: "\f3e1"; } + +.fa-wordpress-simple:before { + content: "\f411"; } + +.fa-nutritionix:before { + content: "\f3d6"; } + +.fa-wodu:before { + content: "\e088"; } + +.fa-google-pay:before { + content: "\e079"; } + +.fa-intercom:before { + content: "\f7af"; } + +.fa-zhihu:before { + content: "\f63f"; } + +.fa-korvue:before { + content: "\f42f"; } + +.fa-pix:before { + content: "\e43a"; } + +.fa-steam-symbol:before { + content: "\f3f6"; } +:root, :host { + --fa-style-family-classic: 'Font Awesome 6 Free'; + --fa-font-regular: normal 400 1em/1 'Font Awesome 6 Free'; } + +@font-face { + font-family: 'Font Awesome 6 Free'; + font-style: normal; + font-weight: 400; + font-display: block; + src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); } + +.far, +.fa-regular { + font-weight: 400; } +:root, :host { + --fa-style-family-classic: 'Font Awesome 6 Free'; + --fa-font-solid: normal 900 1em/1 'Font Awesome 6 Free'; } + +@font-face { + font-family: 'Font Awesome 6 Free'; + font-style: normal; + font-weight: 900; + font-display: block; + src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); } + +.fas, +.fa-solid { + font-weight: 900; } +@font-face { + font-family: 'Font Awesome 5 Brands'; + font-display: block; + font-weight: 400; + src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); } + +@font-face { + font-family: 'Font Awesome 5 Free'; + font-display: block; + font-weight: 900; + src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); } + +@font-face { + font-family: 'Font Awesome 5 Free'; + font-display: block; + font-weight: 400; + src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); } +@font-face { + font-family: 'FontAwesome'; + font-display: block; + src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); } + +@font-face { + font-family: 'FontAwesome'; + font-display: block; + src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); } + +@font-face { + font-family: 'FontAwesome'; + font-display: block; + src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); + unicode-range: U+F003,U+F006,U+F014,U+F016-F017,U+F01A-F01B,U+F01D,U+F022,U+F03E,U+F044,U+F046,U+F05C-F05D,U+F06E,U+F070,U+F087-F088,U+F08A,U+F094,U+F096-F097,U+F09D,U+F0A0,U+F0A2,U+F0A4-F0A7,U+F0C5,U+F0C7,U+F0E5-F0E6,U+F0EB,U+F0F6-F0F8,U+F10C,U+F114-F115,U+F118-F11A,U+F11C-F11D,U+F133,U+F147,U+F14E,U+F150-F152,U+F185-F186,U+F18E,U+F190-F192,U+F196,U+F1C1-F1C9,U+F1D9,U+F1DB,U+F1E3,U+F1EA,U+F1F7,U+F1F9,U+F20A,U+F247-F248,U+F24A,U+F24D,U+F255-F25B,U+F25D,U+F271-F274,U+F278,U+F27B,U+F28C,U+F28E,U+F29C,U+F2B5,U+F2B7,U+F2BA,U+F2BC,U+F2BE,U+F2C0-F2C1,U+F2C3,U+F2D0,U+F2D2,U+F2D4,U+F2DC; } + +@font-face { + font-family: 'FontAwesome'; + font-display: block; + src: url("../webfonts/fa-v4compatibility.woff2") format("woff2"), url("../webfonts/fa-v4compatibility.ttf") format("truetype"); + unicode-range: U+F041,U+F047,U+F065-F066,U+F07D-F07E,U+F080,U+F08B,U+F08E,U+F090,U+F09A,U+F0AC,U+F0AE,U+F0B2,U+F0D0,U+F0D6,U+F0E4,U+F0EC,U+F10A-F10B,U+F123,U+F13E,U+F148-F149,U+F14C,U+F156,U+F15E,U+F160-F161,U+F163,U+F175-F178,U+F195,U+F1F8,U+F219,U+F27A; } diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-brands-400.ttf b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-brands-400.ttf new file mode 100644 index 0000000000..5efb1d4f96 Binary files /dev/null and b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-brands-400.ttf differ diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-brands-400.woff2 b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-brands-400.woff2 new file mode 100644 index 0000000000..36fbda7d33 Binary files /dev/null and b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-brands-400.woff2 differ diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-regular-400.ttf b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-regular-400.ttf new file mode 100644 index 0000000000..838b4e2cfe Binary files /dev/null and b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-regular-400.ttf differ diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-regular-400.woff2 b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-regular-400.woff2 new file mode 100644 index 0000000000..b6cabbacb6 Binary files /dev/null and b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-regular-400.woff2 differ diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-solid-900.ttf b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-solid-900.ttf new file mode 100644 index 0000000000..ec24749db9 Binary files /dev/null and b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-solid-900.ttf differ diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-solid-900.woff2 b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-solid-900.woff2 new file mode 100644 index 0000000000..824d518eb4 Binary files /dev/null and b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-solid-900.woff2 differ diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-v4compatibility.ttf b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-v4compatibility.ttf new file mode 100644 index 0000000000..b175aa8ece Binary files /dev/null and b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-v4compatibility.ttf differ diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-v4compatibility.woff2 b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-v4compatibility.woff2 new file mode 100644 index 0000000000..e09b5a5500 Binary files /dev/null and b/framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-v4compatibility.woff2 differ diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/AbpAspNetCoreComponentsWebThemingMudBlazorModule.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/AbpAspNetCoreComponentsWebThemingMudBlazorModule.cs new file mode 100644 index 0000000000..08b43b5cb1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/AbpAspNetCoreComponentsWebThemingMudBlazorModule.cs @@ -0,0 +1,22 @@ +using Volo.Abp.AspNetCore.Components.Web.Security; +using Volo.Abp.Modularity; +using Volo.Abp.MudBlazorUI; +using Volo.Abp.UI.Navigation; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor; + +[DependsOn( + typeof(AbpMudBlazorUIModule), + typeof(AbpUiNavigationModule) +)] +public class AbpAspNetCoreComponentsWebThemingMudBlazorModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Components.Add(typeof(AbpAuthenticationState), null); + }); + } +} + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/AbpDynamicLayoutComponentOptions.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/AbpDynamicLayoutComponentOptions.cs new file mode 100644 index 0000000000..ab6b516004 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/AbpDynamicLayoutComponentOptions.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor; + +public class AbpDynamicLayoutComponentOptions +{ + /// + /// Used to define components that renders in the layout + /// + [NotNull] + public Dictionary?> Components { get; set; } + + public AbpDynamicLayoutComponentOptions() + { + Components = new Dictionary?>(); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Bundling/AbpScripts.razor b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Bundling/AbpScripts.razor new file mode 100644 index 0000000000..436a22fc0e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Bundling/AbpScripts.razor @@ -0,0 +1,61 @@ +@implements IDisposable +@inject IComponentBundleManager BundleManager +@inject PersistentComponentState ApplicationState +@if (ScriptFiles != null) +{ + foreach (var file in ScriptFiles) + { + var src = file; + if (!AppBasePath.IsNullOrWhiteSpace()) + { + src = AppBasePath.EnsureEndsWith('/') + file.RemovePreFix("/"); + } + + } +} + +@code { + + private const string PrerenderedKey = "abp_script_prerendered"; + + [Parameter] + public List? WebAssemblyScriptFiles { get; set; } + + [Parameter] + public string? BundleName { get; set; } + + [Parameter] + public string? AppBasePath { get; set; } + + private List? ScriptFiles { get; set; } + + private PersistingComponentStateSubscription _persistingSubscription; + + protected override async Task OnInitializedAsync() + { + _persistingSubscription = ApplicationState.RegisterOnPersisting(Callback); + if (!ApplicationState.TryTakeFromJson(PrerenderedKey, out _)) + { + // We are in prerendering mode + if (!BundleName.IsNullOrWhiteSpace()) + { + ScriptFiles = (await BundleManager.GetScriptBundleFilesAsync(BundleName!)).ToList(); + } + } + else + { + if (OperatingSystem.IsBrowser() && WebAssemblyScriptFiles != null) + { + ScriptFiles = WebAssemblyScriptFiles; + } + } + } + + private Task Callback() + { + ApplicationState.PersistAsJson(PrerenderedKey, PrerenderedKey); + return Task.CompletedTask; + } + + public void Dispose() => _persistingSubscription.Dispose(); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Bundling/AbpStyles.razor b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Bundling/AbpStyles.razor new file mode 100644 index 0000000000..1c2ede488f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Bundling/AbpStyles.razor @@ -0,0 +1,75 @@ +@implements IDisposable +@inject IComponentBundleManager BundleManager +@inject PersistentComponentState ApplicationState +@if (StyleFiles != null) +{ + foreach (var file in StyleFiles) + { + var href = file; + if (!AppBasePath.IsNullOrWhiteSpace()) + { + href = AppBasePath.EnsureEndsWith('/') + file.RemovePreFix("/"); + } + + } +} + +@code { + + private const string PrerenderedKey = "abp_style_prerendered"; + + [Parameter] + public List? WebAssemblyStyleFiles { get; set; } + + [Parameter] + public string? BundleName { get; set; } + + [Parameter] + public string? AppBasePath { get; set; } + + private List? StyleFiles { get; set; } + + private PersistingComponentStateSubscription _persistingSubscription; + + protected override async Task OnInitializedAsync() + { + _persistingSubscription = ApplicationState.RegisterOnPersisting(Callback); + if (!ApplicationState.TryTakeFromJson>(PrerenderedKey, out var scriptFiles)) + { + // We are in prerendering mode + if (!BundleName.IsNullOrWhiteSpace()) + { + StyleFiles = (await BundleManager.GetStyleBundleFilesAsync(BundleName!)).ToList(); + } + } + else + { + StyleFiles = scriptFiles; + if (OperatingSystem.IsBrowser() && StyleFiles != null && WebAssemblyStyleFiles != null) + { + StyleFiles.AddIfNotContains(WebAssemblyStyleFiles); + } + } + } + + private bool _hasRemoveServerStyle = false; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (!_hasRemoveServerStyle && OperatingSystem.IsBrowser() && WebAssemblyStyleFiles != null) + { + _hasRemoveServerStyle = true; + await Task.Delay(3000); + StyleFiles = WebAssemblyStyleFiles; + StateHasChanged(); + } + } + + private Task Callback() + { + ApplicationState.PersistAsJson(PrerenderedKey, StyleFiles); + return Task.CompletedTask; + } + + public void Dispose() => _persistingSubscription.Dispose(); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Bundling/IComponentBundleManager.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Bundling/IComponentBundleManager.cs new file mode 100644 index 0000000000..8fe8aa710f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Bundling/IComponentBundleManager.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Bundling; + +public interface IComponentBundleManager +{ + Task> GetStyleBundleFilesAsync(string bundleName); + + Task> GetScriptBundleFilesAsync(string bundleName); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Components/DynamicLayoutComponent.razor b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Components/DynamicLayoutComponent.razor new file mode 100644 index 0000000000..7119f6a54c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Components/DynamicLayoutComponent.razor @@ -0,0 +1,7 @@ +@if (AbpDynamicLayoutComponentOptions.Value.Components.Any()) +{ + foreach (var (componentType, parameters) in AbpDynamicLayoutComponentOptions.Value.Components) + { + + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Components/DynamicLayoutComponent.razor.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Components/DynamicLayoutComponent.razor.cs new file mode 100644 index 0000000000..5b9ba2bb66 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Components/DynamicLayoutComponent.razor.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Options; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Components; + +public partial class DynamicLayoutComponent : ComponentBase +{ + [Inject] + protected IOptions AbpDynamicLayoutComponentOptions { get; set; } = default!; +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Components/LayoutHooks/LayoutHook.razor b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Components/LayoutHooks/LayoutHook.razor new file mode 100644 index 0000000000..d7c5718d7b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Components/LayoutHooks/LayoutHook.razor @@ -0,0 +1,7 @@ +@if (LayoutHookViewModel.Hooks.Any()) +{ + foreach (var hook in LayoutHookViewModel.Hooks) + { + + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Components/LayoutHooks/LayoutHook.razor.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Components/LayoutHooks/LayoutHook.razor.cs new file mode 100644 index 0000000000..3cf75df16f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Components/LayoutHooks/LayoutHook.razor.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Options; +using Volo.Abp.Ui.LayoutHooks; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Components.LayoutHooks; + +public partial class LayoutHook : ComponentBase +{ + [Parameter] + public string Name { get; set; } = default!; + + [Parameter] + public string? Layout { get; set; } + + [Inject] + protected IOptions LayoutHookOptions { get; set; } = default!; + + protected LayoutHookViewModel LayoutHookViewModel { get; private set; } = default!; + + protected override Task OnInitializedAsync() + { + if (LayoutHookOptions.Value.Hooks.TryGetValue(Name, out var layoutHooks)) + { + layoutHooks = layoutHooks + .Where(x => IsComponentBase(x) && (string.IsNullOrWhiteSpace(x.Layout) || x.Layout == Layout)) + .ToList(); + } + + layoutHooks ??= new List(); + + LayoutHookViewModel = new LayoutHookViewModel(layoutHooks.ToArray(), Layout); + + return Task.CompletedTask; + } + + protected virtual bool IsComponentBase(LayoutHookInfo layoutHook) + { + return typeof(ComponentBase).IsAssignableFrom(layoutHook.ComponentType); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/FodyWeavers.xml b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/FodyWeavers.xml new file mode 100644 index 0000000000..7e9f94ead6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/FodyWeavers.xsd b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/FodyWeavers.xsd new file mode 100644 index 0000000000..ffa6fc4b78 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Layout/PageHeader.razor b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Layout/PageHeader.razor new file mode 100644 index 0000000000..0560bcb970 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Layout/PageHeader.razor @@ -0,0 +1,65 @@ +@using global::MudBlazor +@using Microsoft.Extensions.Options +@using Microsoft.AspNetCore.Components.Web +@using Volo.Abp.Ui.Branding + +@inject IOptions Options +@inject IBrandingProvider BrandingProvider + + + + @if (PageLayout.Title.IsNullOrEmpty()) + { + @BrandingProvider.AppName + } + else + { + @PageLayout.Title | @BrandingProvider.AppName + } + + @if(Options.Value.RenderPageTitle) + { + + @PageLayout.Title + + } + + @if (Options.Value.RenderBreadcrumbs && PageLayout.BreadcrumbItems.Any()) + { + + + + + @if (!string.IsNullOrEmpty(item.Icon)) + { + + } + @item.Text + + + + + + + + } + + @if(Options.Value.RenderToolbar) + { + + + @if (Toolbar == null) + { + @ChildContent + } + + @foreach (var toolbarItemRender in ToolbarItemRenders) + { + + @toolbarItemRender + + } + + + } + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Layout/PageHeader.razor.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Layout/PageHeader.razor.cs new file mode 100644 index 0000000000..301045d6b7 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Layout/PageHeader.razor.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using MudBlazor; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.PageToolbars; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Layout; + +public partial class PageHeader : ComponentBase, IDisposable +{ + protected List ToolbarItemRenders { get; set; } + + private List _breadcrumbItems = new(); + + public IPageToolbarManager PageToolbarManager { get; set; } = default!; + + [Inject] + public PageLayout PageLayout { get; private set; } = default!; + + [Parameter] // TODO: Consider removing this property in future and use only PageLayout. + public string? Title { get => PageLayout.Title; set => PageLayout.Title = value; } + + [Parameter] + public bool BreadcrumbShowHome { get; set; } = true; + + [Parameter] + public bool BreadcrumbShowCurrent { get; set; } = true; + + [Parameter] + public RenderFragment ChildContent { get; set; } = default!; + + [Parameter] // TODO: Consider removing this property in future and use only PageLayout. + public List BreadcrumbItems { + get => PageLayout.BreadcrumbItems.ToList(); + set + { + PageLayout.BreadcrumbItems.Clear(); + foreach (var item in value) + { + PageLayout.BreadcrumbItems.Add(item); + } + } + } + + [Parameter] + public PageToolbar? Toolbar { get; set; } + + public PageHeader() + { + ToolbarItemRenders = new List(); + } + + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync(); + + // Build MudBlazor breadcrumb items + BuildBreadcrumbItems(); + + if (Toolbar != null) + { + var toolbarItems = await PageToolbarManager.GetItemsAsync(Toolbar); + + if (!ShouldRenderToolbarItems(toolbarItems)) + { + return; + } + + ToolbarItemRenders.Clear(); + + if (!Options.Value.RenderToolbar) + { + PageLayout.ToolbarItems.Clear(); + foreach (var item in toolbarItems) + { + PageLayout.ToolbarItems.Add(item); + } + return; + } + + foreach (var item in toolbarItems) + { + var sequence = 0; + ToolbarItemRenders.Add(builder => + { + builder.OpenComponent(sequence, item.ComponentType); + if (item.Arguments != null) + { + foreach (var argument in item.Arguments) + { + sequence++; + builder.AddAttribute(sequence, argument.Key, argument.Value); + } + } + builder.CloseComponent(); + }); + } + } + } + + private void BuildBreadcrumbItems() + { + _breadcrumbItems.Clear(); + + if (BreadcrumbShowHome) + { + _breadcrumbItems.Add(new BreadcrumbItem(string.Empty, href: "/", icon: Icons.Material.Filled.Home)); + } + + var pageItems = PageLayout.BreadcrumbItems.ToList(); + for (var i = 0; i < pageItems.Count; i++) + { + var item = pageItems[i]; + var isLast = i == pageItems.Count - 1; + + var mudIcon = ConvertToMudIcon(item.Icon); + + // If BreadcrumbShowCurrent is false, skip the last item or make it non-clickable + if (isLast && !BreadcrumbShowCurrent) + { + continue; + } + + _breadcrumbItems.Add(new BreadcrumbItem( + item.Text, + href: isLast ? null : item.Href, + disabled: isLast, + icon: mudIcon)); + } + } + + private static string? ConvertToMudIcon(object? icon) + { + if (icon == null) + { + return null; + } + + if (icon is string iconString) + { + return iconString; + } + + var iconName = icon.ToString(); + + return iconName switch + { + "Home" => Icons.Material.Filled.Home, + "Edit" => Icons.Material.Filled.Edit, + "Delete" => Icons.Material.Filled.Delete, + "Add" => Icons.Material.Filled.Add, + "Search" => Icons.Material.Filled.Search, + "Settings" => Icons.Material.Filled.Settings, + "User" => Icons.Material.Filled.Person, + "Users" => Icons.Material.Filled.People, + "File" => Icons.Material.Filled.InsertDriveFile, + "Folder" => Icons.Material.Filled.Folder, + "Dashboard" => Icons.Material.Filled.Dashboard, + "List" => Icons.Material.Filled.List, + "Check" => Icons.Material.Filled.Check, + "Times" => Icons.Material.Filled.Close, + "Warning" => Icons.Material.Filled.Warning, + "Info" => Icons.Material.Filled.Info, + "Question" => Icons.Material.Filled.Help, + _ => null // Return null for unmapped icons + }; + } + + protected virtual bool ShouldRenderToolbarItems(PageToolbarItem[] items) + { + if (items.Length != PageLayout.ToolbarItems.Count) + { + return true; + } + + return items.Where((t, i) => t.ComponentType != PageLayout.ToolbarItems[i].ComponentType).Any(); + } + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + } + + public void Dispose() + { + PageLayout.Reset(); + ToolbarItemRenders.Clear(); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Layout/PageHeaderOptions.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Layout/PageHeaderOptions.cs new file mode 100644 index 0000000000..68492898d5 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Layout/PageHeaderOptions.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Layout; + +public class PageHeaderOptions +{ + public bool RenderPageTitle { get; set; } = true; + public bool RenderBreadcrumbs { get; set; } = true; + public bool RenderToolbar { get; set; } = true; +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Layout/PageLayout.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Layout/PageLayout.cs new file mode 100644 index 0000000000..de1ba5e35b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Layout/PageLayout.cs @@ -0,0 +1,54 @@ +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using MudBlazor; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.PageToolbars; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Layout; + +public class PageLayout : IScopedDependency, INotifyPropertyChanged +{ + private string? title; + + // TODO: Consider using this property for setting Page Title too. + public virtual string? Title { + get => title; + set { + title = value; + OnPropertyChanged(); + } + } + + private string? menuItemName; + + public string? MenuItemName { + get => menuItemName; + set + { + menuItemName = value; + OnPropertyChanged(); + } + } + + public bool ShowToolbar { get; set; } = true; + + public virtual ObservableCollection BreadcrumbItems { get; } = new(); + + public virtual ObservableCollection ToolbarItems { get; } = new(); + + public event PropertyChangedEventHandler? PropertyChanged; + + protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public void Reset() + { + Title = string.Empty; + MenuItemName = string.Empty; + BreadcrumbItems.Clear(); + ToolbarItems.Clear(); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Layout/StandardLayouts.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Layout/StandardLayouts.cs new file mode 100644 index 0000000000..bc25f503bd --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Layout/StandardLayouts.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Layout; + +public static class StandardLayouts +{ + public const string Application = "Application"; + public const string Account = "Account"; + public const string Public = "Public"; + public const string Empty = "Empty"; +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/IPageToolbarContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/IPageToolbarContributor.cs new file mode 100644 index 0000000000..f1f5b230f4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/IPageToolbarContributor.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.PageToolbars; + +public interface IPageToolbarContributor +{ + Task ContributeAsync(PageToolbarContributionContext context); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/IPageToolbarManager.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/IPageToolbarManager.cs new file mode 100644 index 0000000000..58f5833249 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/IPageToolbarManager.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.PageToolbars; + +public interface IPageToolbarManager +{ + Task GetItemsAsync(PageToolbar toolbar); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/MudPageToolbarButton.razor b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/MudPageToolbarButton.razor new file mode 100644 index 0000000000..e37ccee5c3 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/MudPageToolbarButton.razor @@ -0,0 +1,21 @@ +@using global::MudBlazor + + + @Text + + +@code { + private string? IconString => Icon?.ToString(); + + private async Task OnClickHandler() + { + if (OnClickAsync != null) + { + await OnClickAsync(); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/MudPageToolbarButton.razor.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/MudPageToolbarButton.razor.cs new file mode 100644 index 0000000000..a8918a5f18 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/MudPageToolbarButton.razor.cs @@ -0,0 +1,24 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using MudBlazor; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.PageToolbars; + +public partial class MudPageToolbarButton : ComponentBase +{ + [Parameter] + public Color Color { get; set; } = Color.Primary; + + [Parameter] + public object? Icon { get; set; } + + [Parameter] + public string Text { get; set; } = default!; + + [Parameter] + public Func? OnClickAsync { get; set; } + + [Parameter] + public bool Disabled { get; set; } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbar.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbar.cs new file mode 100644 index 0000000000..1f808816ab --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbar.cs @@ -0,0 +1,11 @@ +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.PageToolbars; + +public class PageToolbar +{ + public PageToolbarContributorList Contributors { get; set; } + + public PageToolbar() + { + Contributors = new PageToolbarContributorList(); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarContributionContext.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarContributionContext.cs new file mode 100644 index 0000000000..37f7677cf9 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarContributionContext.cs @@ -0,0 +1,20 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.PageToolbars; + +public class PageToolbarContributionContext +{ + [NotNull] + public IServiceProvider ServiceProvider { get; } + + [NotNull] + public PageToolbarItemList Items { get; } + + public PageToolbarContributionContext( + [NotNull] IServiceProvider serviceProvider) + { + ServiceProvider = Check.NotNull(serviceProvider, nameof(serviceProvider)); + Items = new PageToolbarItemList(); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarContributor.cs new file mode 100644 index 0000000000..92fb22838e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarContributor.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.PageToolbars; + +public abstract class PageToolbarContributor : IPageToolbarContributor +{ + public abstract Task ContributeAsync(PageToolbarContributionContext context); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarContributorList.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarContributorList.cs new file mode 100644 index 0000000000..0723bbe571 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarContributorList.cs @@ -0,0 +1,7 @@ +using System.Collections.Generic; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.PageToolbars; + +public class PageToolbarContributorList : List +{ +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarDictionary.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarDictionary.cs new file mode 100644 index 0000000000..2016008a5e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarDictionary.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.PageToolbars; + +public class PageToolbarDictionary : Dictionary +{ + +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarExtensions.cs new file mode 100644 index 0000000000..136a95c689 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarExtensions.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using MudBlazor; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.PageToolbars; + +public static class PageToolbarExtensions +{ + public static PageToolbar AddComponent( + this PageToolbar toolbar, + Dictionary? arguments = null, + int order = 0, + string? requiredPolicyName = null) + { + return toolbar.AddComponent( + typeof(TComponent), + arguments, + order, + requiredPolicyName + ); + } + + public static PageToolbar AddComponent( + this PageToolbar toolbar, + Type componentType, + Dictionary? arguments = null, + int order = 0, + string? requiredPolicyName = null) + { + toolbar.Contributors.Add( + new SimplePageToolbarContributor( + componentType, + arguments, + order, + requiredPolicyName + ) + ); + + return toolbar; + } + + public static PageToolbar AddButton( + this PageToolbar toolbar, + string text, + Func clicked, + object? icon = null, + Color? color = null, + bool disabled = false, + int order = 0, + string? requiredPolicyName = null) + { + toolbar.AddComponent( + new Dictionary + { + { nameof(MudPageToolbarButton.Color), color ?? Color.Primary }, + { nameof(MudPageToolbarButton.Text), text }, + { nameof(MudPageToolbarButton.Disabled), disabled }, + { nameof(MudPageToolbarButton.Icon), icon }, + { nameof(MudPageToolbarButton.OnClickAsync), clicked }, + }, + order, + requiredPolicyName + ); + + return toolbar; + } + + public static PageToolbar AddMudButton( + this PageToolbar toolbar, + string text, + Func clicked, + string? icon = null, + Color? color = null, + bool disabled = false, + int order = 0, + string? requiredPolicyName = null) + { + return toolbar.AddButton(text, clicked, icon, color, disabled, order, requiredPolicyName); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarItem.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarItem.cs new file mode 100644 index 0000000000..f5820599d4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarItem.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.PageToolbars; + +public class PageToolbarItem +{ + [NotNull] + public Type ComponentType { get; } + + public Dictionary? Arguments { get; set; } + + public int Order { get; set; } + + public PageToolbarItem( + [NotNull] Type componentType, + Dictionary? arguments = null, + int order = 0) + { + ComponentType = Check.NotNull(componentType, nameof(componentType)); + Arguments = arguments; + Order = order; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarItemList.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarItemList.cs new file mode 100644 index 0000000000..761efa01ed --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarItemList.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.PageToolbars; + +public class PageToolbarItemList : List +{ + +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarManager.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarManager.cs new file mode 100644 index 0000000000..24efd89e1b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/PageToolbarManager.cs @@ -0,0 +1,38 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.PageToolbars; + +public class PageToolbarManager : IPageToolbarManager, ITransientDependency +{ + protected IServiceScopeFactory ServiceScopeFactory { get; } + + public PageToolbarManager( + IServiceScopeFactory serviceScopeFactory) + { + ServiceScopeFactory = serviceScopeFactory; + } + + public virtual async Task GetItemsAsync(PageToolbar toolbar) + { + if (toolbar == null || !toolbar.Contributors.Any()) + { + return Array.Empty(); + } + + using (var scope = ServiceScopeFactory.CreateScope()) + { + var context = new PageToolbarContributionContext(scope.ServiceProvider); + + foreach (var contributor in toolbar.Contributors) + { + await contributor.ContributeAsync(context); + } + + return context.Items.OrderBy(i => i.Order).ToArray(); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/SimplePageToolbarContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/SimplePageToolbarContributor.cs new file mode 100644 index 0000000000..4b333e2b90 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/PageToolbars/SimplePageToolbarContributor.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.PageToolbars; + +public class SimplePageToolbarContributor : IPageToolbarContributor +{ + public Type ComponentType { get; } + + public Dictionary? Arguments { get; set; } + + public int Order { get; } + + public string? RequiredPolicyName { get; } + + private bool? _shouldAddComponent; + + public SimplePageToolbarContributor( + Type componentType, + Dictionary? arguments = null, + int order = 0, + string? requiredPolicyName = null) + { + ComponentType = componentType; + Arguments = arguments; + Order = order; + RequiredPolicyName = requiredPolicyName; + } + + public async Task ContributeAsync(PageToolbarContributionContext context) + { + if (await ShouldAddComponentAsync(context)) + { + context.Items.Add(new PageToolbarItem(ComponentType, Arguments, Order)); + } + } + + protected virtual async Task ShouldAddComponentAsync(PageToolbarContributionContext context) + { + if (_shouldAddComponent.HasValue) + { + return _shouldAddComponent.Value; + } + + if (RequiredPolicyName == null) + { + _shouldAddComponent = true; + return _shouldAddComponent.Value; + } + + var authorizationService = context.ServiceProvider.GetRequiredService(); + _shouldAddComponent = await authorizationService.IsGrantedAsync(RequiredPolicyName); + return _shouldAddComponent.Value; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Routing/AbpRouterOptions.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Routing/AbpRouterOptions.cs new file mode 100644 index 0000000000..390322da3b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Routing/AbpRouterOptions.cs @@ -0,0 +1,15 @@ +using System.Reflection; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; + +public class AbpRouterOptions +{ + public Assembly AppAssembly { get; set; } = default!; + + public RouterAssemblyList AdditionalAssemblies { get; } + + public AbpRouterOptions() + { + AdditionalAssemblies = new RouterAssemblyList(); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Routing/RouterAssemblyList.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Routing/RouterAssemblyList.cs new file mode 100644 index 0000000000..a23841c13e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Routing/RouterAssemblyList.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using System.Reflection; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; + +public class RouterAssemblyList : List +{ + +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/AbpThemingOptions.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/AbpThemingOptions.cs new file mode 100644 index 0000000000..4fd7d8958c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/AbpThemingOptions.cs @@ -0,0 +1,13 @@ +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Theming; + +public class AbpThemingOptions +{ + public ThemeDictionary Themes { get; } + + public string? DefaultThemeName { get; set; } + + public AbpThemingOptions() + { + Themes = new ThemeDictionary(); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/DefaultThemeManager.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/DefaultThemeManager.cs new file mode 100644 index 0000000000..30bf198a9f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/DefaultThemeManager.cs @@ -0,0 +1,34 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Theming; + +public class DefaultThemeManager : IThemeManager, IScopedDependency, IServiceProviderAccessor +{ + public IServiceProvider ServiceProvider { get; } + public ITheme CurrentTheme => GetCurrentTheme(); + + private ITheme? _currentTheme; + + protected IThemeSelector ThemeSelector { get; } + + public DefaultThemeManager( + IServiceProvider serviceProvider, + IThemeSelector themeSelector) + { + ServiceProvider = serviceProvider; + ThemeSelector = themeSelector; + } + + protected virtual ITheme GetCurrentTheme() + { + if (_currentTheme != null) + { + return _currentTheme; + } + + _currentTheme = (ITheme)ServiceProvider.GetRequiredService(ThemeSelector.GetCurrentThemeInfo().ThemeType); + return _currentTheme; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/DefaultThemeSelector.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/DefaultThemeSelector.cs new file mode 100644 index 0000000000..18fa99744c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/DefaultThemeSelector.cs @@ -0,0 +1,36 @@ +using System.Linq; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Theming; + +public class DefaultThemeSelector : IThemeSelector, ITransientDependency +{ + protected AbpThemingOptions Options { get; } + + public DefaultThemeSelector(IOptions options) + { + Options = options.Value; + } + + public virtual ThemeInfo GetCurrentThemeInfo() + { + if (!Options.Themes.Any()) + { + throw new AbpException($"No theme registered! Use {nameof(AbpThemingOptions)} to register themes."); + } + + if (Options.DefaultThemeName == null) + { + return Options.Themes.Values.First(); + } + + var themeInfo = Options.Themes.Values.FirstOrDefault(t => t.Name == Options.DefaultThemeName); + if (themeInfo == null) + { + throw new AbpException("Default theme is configured but it's not found in the registered themes: " + Options.DefaultThemeName); + } + + return themeInfo; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/ITheme.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/ITheme.cs new file mode 100644 index 0000000000..270ff2c427 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/ITheme.cs @@ -0,0 +1,8 @@ +using System; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Theming; + +public interface ITheme +{ + Type GetLayout(string name, bool fallbackToDefault = true); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/IThemeManager.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/IThemeManager.cs new file mode 100644 index 0000000000..663ce4716c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/IThemeManager.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Theming; + +public interface IThemeManager +{ + ITheme CurrentTheme { get; } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/IThemeSelector.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/IThemeSelector.cs new file mode 100644 index 0000000000..51809a6f54 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/IThemeSelector.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Theming; + +public interface IThemeSelector +{ + ThemeInfo GetCurrentThemeInfo(); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/ThemeDictionary.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/ThemeDictionary.cs new file mode 100644 index 0000000000..e2361c2c6a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/ThemeDictionary.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Theming; + +public class ThemeDictionary : Dictionary +{ + public ThemeInfo Add() + where TTheme : ITheme + { + return Add(typeof(TTheme)); + } + + public ThemeInfo Add(Type themeType) + { + if (ContainsKey(themeType)) + { + throw new AbpException("This theme is already added before: " + themeType.AssemblyQualifiedName); + } + + return this[themeType] = new ThemeInfo(themeType); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/ThemeExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/ThemeExtensions.cs new file mode 100644 index 0000000000..09798cecea --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/ThemeExtensions.cs @@ -0,0 +1,27 @@ +using System; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Layout; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Theming; + +public static class ThemeExtensions +{ + public static Type GetApplicationLayout(this ITheme theme, bool fallbackToDefault = true) + { + return theme.GetLayout(StandardLayouts.Application, fallbackToDefault); + } + + public static Type GetAccountLayout(this ITheme theme, bool fallbackToDefault = true) + { + return theme.GetLayout(StandardLayouts.Account, fallbackToDefault); + } + + public static Type GetPublicLayout(this ITheme theme, bool fallbackToDefault = true) + { + return theme.GetLayout(StandardLayouts.Public, fallbackToDefault); + } + + public static Type GetEmptyLayout(this ITheme theme, bool fallbackToDefault = true) + { + return theme.GetLayout(StandardLayouts.Empty, fallbackToDefault); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/ThemeInfo.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/ThemeInfo.cs new file mode 100644 index 0000000000..d9525cd758 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/ThemeInfo.cs @@ -0,0 +1,24 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Theming; + +public class ThemeInfo +{ + public Type ThemeType { get; } + + public string Name { get; } + + public ThemeInfo([NotNull] Type themeType) + { + Check.NotNull(themeType, nameof(themeType)); + + if (!typeof(ITheme).IsAssignableFrom(themeType)) + { + throw new AbpException($"Given {nameof(themeType)} ({themeType.AssemblyQualifiedName}) should be type of {typeof(ITheme).AssemblyQualifiedName}"); + } + + ThemeType = themeType; + Name = ThemeNameAttribute.GetName(themeType); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/ThemeNameAttribute.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/ThemeNameAttribute.cs new file mode 100644 index 0000000000..fc85e1e5cd --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Theming/ThemeNameAttribute.cs @@ -0,0 +1,23 @@ +using System; +using System.Linq; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Theming; + +[AttributeUsage(AttributeTargets.Class)] +public class ThemeNameAttribute : Attribute +{ + public string Name { get; set; } + + public ThemeNameAttribute(string name) + { + Name = name; + } + + public static string GetName(Type themeType) + { + return themeType + .GetCustomAttributes(true) + .OfType() + .FirstOrDefault()?.Name ?? themeType.Name; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/AbpToolbarOptions.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/AbpToolbarOptions.cs new file mode 100644 index 0000000000..e096cebbc2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/AbpToolbarOptions.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Toolbars; + +public class AbpToolbarOptions +{ + [NotNull] + public List Contributors { get; } + + public AbpToolbarOptions() + { + Contributors = new List(); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/IToolbarConfigurationContext.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/IToolbarConfigurationContext.cs new file mode 100644 index 0000000000..b019b269d8 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/IToolbarConfigurationContext.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Localization; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Toolbars; + +public interface IToolbarConfigurationContext : IServiceProviderAccessor +{ + Toolbar Toolbar { get; } + + IAuthorizationService AuthorizationService { get; } + + IStringLocalizerFactory StringLocalizerFactory { get; } + + Task IsGrantedAsync(string policyName); + + IStringLocalizer? GetDefaultLocalizer(); + + [NotNull] + public IStringLocalizer GetLocalizer(); + + [NotNull] + public IStringLocalizer GetLocalizer(Type resourceType); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/IToolbarContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/IToolbarContributor.cs new file mode 100644 index 0000000000..973509464c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/IToolbarContributor.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Toolbars; + +public interface IToolbarContributor +{ + Task ConfigureToolbarAsync(IToolbarConfigurationContext context); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/IToolbarManager.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/IToolbarManager.cs new file mode 100644 index 0000000000..cb483092d9 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/IToolbarManager.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Toolbars; + +public interface IToolbarManager +{ + Task GetAsync(string name); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/StandardToolbars.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/StandardToolbars.cs new file mode 100644 index 0000000000..c3443f7efd --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/StandardToolbars.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Toolbars; + +public static class StandardToolbars +{ + public const string Main = "Main"; +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/Toolbar.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/Toolbar.cs new file mode 100644 index 0000000000..5a0d829124 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/Toolbar.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Toolbars; + +public class Toolbar +{ + public string Name { get; } + + public List Items { get; } + + public Toolbar([NotNull] string name) + { + Name = Check.NotNull(name, nameof(name)); + Items = new List(); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/ToolbarConfigurationContext.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/ToolbarConfigurationContext.cs new file mode 100644 index 0000000000..1fdff594b0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/ToolbarConfigurationContext.cs @@ -0,0 +1,51 @@ +using System; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Toolbars; + +public class ToolbarConfigurationContext : IToolbarConfigurationContext +{ + public IServiceProvider ServiceProvider { get; } + + private readonly IAbpLazyServiceProvider _lazyServiceProvider; + + public IAuthorizationService AuthorizationService => _lazyServiceProvider.LazyGetRequiredService(); + + public IStringLocalizerFactory StringLocalizerFactory => _lazyServiceProvider.LazyGetRequiredService(); + + public Toolbar Toolbar { get; } + + public ToolbarConfigurationContext(Toolbar toolbar, IServiceProvider serviceProvider) + { + Toolbar = toolbar; + ServiceProvider = serviceProvider; + _lazyServiceProvider = ServiceProvider.GetRequiredService(); + } + + public Task IsGrantedAsync(string policyName) + { + return AuthorizationService.IsGrantedAsync(policyName); + } + + public IStringLocalizer? GetDefaultLocalizer() + { + return StringLocalizerFactory.CreateDefaultOrNull(); + } + + [NotNull] + public IStringLocalizer GetLocalizer() + { + return StringLocalizerFactory.Create(); + } + + [NotNull] + public IStringLocalizer GetLocalizer(Type resourceType) + { + return StringLocalizerFactory.Create(resourceType); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/ToolbarItem.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/ToolbarItem.cs new file mode 100644 index 0000000000..31b2839cc6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/ToolbarItem.cs @@ -0,0 +1,21 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Toolbars; + +public class ToolbarItem +{ + public Type ComponentType { + get => _componentType; + set => _componentType = Check.NotNull(value, nameof(value)); + } + private Type _componentType = default!; + + public int Order { get; set; } + + public ToolbarItem([NotNull] Type componentType, int order = 0) + { + Order = order; + ComponentType = Check.NotNull(componentType, nameof(componentType)); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/ToolbarManager.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/ToolbarManager.cs new file mode 100644 index 0000000000..cfdfcfd1e7 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Toolbars/ToolbarManager.cs @@ -0,0 +1,38 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Toolbars; + +public class ToolbarManager : IToolbarManager, ITransientDependency +{ + protected AbpToolbarOptions Options { get; } + protected IServiceProvider ServiceProvider { get; } + + public ToolbarManager( + IOptions options, + IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + Options = options.Value; + } + + public async Task GetAsync(string name) + { + var toolbar = new Toolbar(name); + + using (var scope = ServiceProvider.CreateScope()) + { + var context = new ToolbarConfigurationContext(toolbar, scope.ServiceProvider); + + foreach (var contributor in Options.Contributors) + { + await contributor.ConfigureToolbarAsync(context); + } + } + + return toolbar; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.abppkg b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.abppkg new file mode 100644 index 0000000000..e0c33eaab1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.abppkg @@ -0,0 +1,3 @@ +{ + "role": "lib.framework" +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.csproj b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.csproj new file mode 100644 index 0000000000..0e174f1ab8 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.csproj @@ -0,0 +1,22 @@ + + + + + + + net10.0 + enable + Nullable + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpScripts.razor b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpScripts.razor index 436a22fc0e..6560d97bd6 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpScripts.razor +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpScripts.razor @@ -1,16 +1,14 @@ +@using Microsoft.AspNetCore.Components @implements IDisposable @inject IComponentBundleManager BundleManager @inject PersistentComponentState ApplicationState +@inject NavigationManager NavigationManager +@inject IComponentBundleUrlBuilder BundleUrlBuilder @if (ScriptFiles != null) { foreach (var file in ScriptFiles) { - var src = file; - if (!AppBasePath.IsNullOrWhiteSpace()) - { - src = AppBasePath.EnsureEndsWith('/') + file.RemovePreFix("/"); - } - + } } @@ -39,18 +37,29 @@ // We are in prerendering mode if (!BundleName.IsNullOrWhiteSpace()) { - ScriptFiles = (await BundleManager.GetScriptBundleFilesAsync(BundleName!)).ToList(); + var rawFiles = await BundleManager.GetScriptBundleFilesAsync(BundleName!); + ScriptFiles = await ResolveAsync(rawFiles); } } else { if (OperatingSystem.IsBrowser() && WebAssemblyScriptFiles != null) { - ScriptFiles = WebAssemblyScriptFiles; + ScriptFiles = await ResolveAsync(WebAssemblyScriptFiles); } } } + private async Task> ResolveAsync(IReadOnlyList files) + { + var resolved = new List(files.Count); + foreach (var file in files) + { + resolved.Add(await BundleUrlBuilder.BuildAsync(file, AppBasePath, NavigationManager.BaseUri)); + } + return resolved; + } + private Task Callback() { ApplicationState.PersistAsJson(PrerenderedKey, PrerenderedKey); diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpStyles.razor b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpStyles.razor index 1c2ede488f..015ef9fa35 100644 --- a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpStyles.razor +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/AbpStyles.razor @@ -1,16 +1,14 @@ +@using Microsoft.AspNetCore.Components @implements IDisposable @inject IComponentBundleManager BundleManager @inject PersistentComponentState ApplicationState +@inject NavigationManager NavigationManager +@inject IComponentBundleUrlBuilder BundleUrlBuilder @if (StyleFiles != null) { foreach (var file in StyleFiles) { - var href = file; - if (!AppBasePath.IsNullOrWhiteSpace()) - { - href = AppBasePath.EnsureEndsWith('/') + file.RemovePreFix("/"); - } - + } } @@ -39,7 +37,8 @@ // We are in prerendering mode if (!BundleName.IsNullOrWhiteSpace()) { - StyleFiles = (await BundleManager.GetStyleBundleFilesAsync(BundleName!)).ToList(); + var rawFiles = await BundleManager.GetStyleBundleFilesAsync(BundleName!); + StyleFiles = await ResolveAsync(rawFiles); } } else @@ -47,7 +46,7 @@ StyleFiles = scriptFiles; if (OperatingSystem.IsBrowser() && StyleFiles != null && WebAssemblyStyleFiles != null) { - StyleFiles.AddIfNotContains(WebAssemblyStyleFiles); + StyleFiles.AddIfNotContains(await ResolveAsync(WebAssemblyStyleFiles)); } } } @@ -60,11 +59,21 @@ { _hasRemoveServerStyle = true; await Task.Delay(3000); - StyleFiles = WebAssemblyStyleFiles; + StyleFiles = await ResolveAsync(WebAssemblyStyleFiles); StateHasChanged(); } } + private async Task> ResolveAsync(IReadOnlyList files) + { + var resolved = new List(files.Count); + foreach (var file in files) + { + resolved.Add(await BundleUrlBuilder.BuildAsync(file, AppBasePath, NavigationManager.BaseUri)); + } + return resolved; + } + private Task Callback() { ApplicationState.PersistAsJson(PrerenderedKey, StyleFiles); diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/ComponentBundleUrlBuilder.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/ComponentBundleUrlBuilder.cs new file mode 100644 index 0000000000..98c003e485 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/ComponentBundleUrlBuilder.cs @@ -0,0 +1,62 @@ +using System; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Bundling; + +public class ComponentBundleUrlBuilder : IComponentBundleUrlBuilder, ITransientDependency +{ + public virtual Task BuildAsync( + [NotNull] string fileName, + [CanBeNull] string? appBasePath, + [CanBeNull] string? navigationBaseUri) + { + Check.NotNull(fileName, nameof(fileName)); + + if (IsExternalUrl(fileName)) + { + return Task.FromResult(fileName); + } + + var pathBase = !string.IsNullOrWhiteSpace(appBasePath) + ? appBasePath + : ExtractPathBaseFromNavigationBaseUri(navigationBaseUri); + + if (string.IsNullOrWhiteSpace(pathBase)) + { + return Task.FromResult(fileName); + } + + var normalized = pathBase.EnsureEndsWith('/'); + if (normalized == "/") + { + return Task.FromResult(fileName); + } + + if (fileName.StartsWith(normalized, StringComparison.Ordinal)) + { + return Task.FromResult(fileName); + } + + return Task.FromResult(normalized + fileName.RemovePreFix("/")); + } + + protected virtual bool IsExternalUrl([NotNull] string fileName) + { + return fileName.StartsWith("//", StringComparison.Ordinal) || + (fileName.Contains(':') && Uri.TryCreate(fileName, UriKind.Absolute, out _)); + } + + protected virtual string? ExtractPathBaseFromNavigationBaseUri([CanBeNull] string? navigationBaseUri) + { + if (string.IsNullOrWhiteSpace(navigationBaseUri)) + { + return null; + } + + return Uri.TryCreate(navigationBaseUri, UriKind.Absolute, out var uri) + ? uri.AbsolutePath + : null; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/IComponentBundleUrlBuilder.cs b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/IComponentBundleUrlBuilder.cs new file mode 100644 index 0000000000..6db6915781 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.Web.Theming/Bundling/IComponentBundleUrlBuilder.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Bundling; + +public interface IComponentBundleUrlBuilder +{ + Task BuildAsync(string fileName, string? appBasePath, string? navigationBaseUri); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/AbpAspNetCoreComponentsWebAssemblyThemingMudBlazorBundlingModule.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/AbpAspNetCoreComponentsWebAssemblyThemingMudBlazorBundlingModule.cs new file mode 100644 index 0000000000..aabe62e604 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/AbpAspNetCoreComponentsWebAssemblyThemingMudBlazorBundlingModule.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling; + +[DependsOn( + typeof(AbpAspNetCoreMvcUiBundlingAbstractionsModule) +)] +public class AbpAspNetCoreComponentsWebAssemblyThemingMudBlazorBundlingModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.GlobalAssets.Enabled = true; + options.GlobalAssets.GlobalStyleBundleName = BlazorWebAssemblyMudBlazorStandardBundles.Styles.Global; + options.GlobalAssets.GlobalScriptBundleName = BlazorWebAssemblyMudBlazorStandardBundles.Scripts.Global; + + options + .StyleBundles + .Add(BlazorWebAssemblyMudBlazorStandardBundles.Styles.Global, bundle => + { + bundle.AddContributors(typeof(BlazorWebAssemblyMudBlazorStyleContributor)); + }); + + options + .ScriptBundles + .Add(BlazorWebAssemblyMudBlazorStandardBundles.Scripts.Global, bundle => + { + bundle.AddContributors(typeof(BlazorWebAssemblyMudBlazorScriptContributor)); + }); + + options.MinificationIgnoredFiles.Add("_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"); + }); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/BlazorWebAssemblyMudBlazorScriptContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/BlazorWebAssemblyMudBlazorScriptContributor.cs new file mode 100644 index 0000000000..246f32f15b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/BlazorWebAssemblyMudBlazorScriptContributor.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling; + +public class BlazorWebAssemblyMudBlazorScriptContributor : BundleContributor +{ + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"); + context.Files.AddIfNotContains("_content/MudBlazor/MudBlazor.min.js"); + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/abp.js"); + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/lang-utils.js"); + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/js/authentication-state-listener.js"); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/BlazorWebAssemblyMudBlazorStandardBundles.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/BlazorWebAssemblyMudBlazorStandardBundles.cs new file mode 100644 index 0000000000..de5b72cc2e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/BlazorWebAssemblyMudBlazorStandardBundles.cs @@ -0,0 +1,14 @@ +namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling; + +public class BlazorWebAssemblyMudBlazorStandardBundles +{ + public static class Styles + { + public static string Global = "BlazorWebAssemblyMudBlazor.Global"; + } + + public static class Scripts + { + public static string Global = "BlazorWebAssemblyMudBlazor.Global"; + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/BlazorWebAssemblyMudBlazorStyleContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/BlazorWebAssemblyMudBlazorStyleContributor.cs new file mode 100644 index 0000000000..32d6563d74 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/BlazorWebAssemblyMudBlazorStyleContributor.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling; + +public class BlazorWebAssemblyMudBlazorStyleContributor : BundleContributor +{ + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/libs/fontawesome/css/all.css"); + context.Files.AddIfNotContains("_content/MudBlazor/MudBlazor.min.css"); + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web/libs/abp/css/abp.css"); + context.Files.AddIfNotContains("_content/Volo.Abp.MudBlazorUI/volo.abp.mudblazorui.css"); + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/libs/flag-icon/css/flag-icon.css"); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/FodyWeavers.xml b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/FodyWeavers.xml new file mode 100644 index 0000000000..7e9f94ead6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/FodyWeavers.xsd b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/FodyWeavers.xsd new file mode 100644 index 0000000000..e6bb89a4a1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling.csproj b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling.csproj new file mode 100644 index 0000000000..adb6c6260d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling.csproj @@ -0,0 +1,16 @@ + + + + + + + net10.0 + enable + Nullable + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/AbpAspNetCoreComponentsWebAssemblyThemingMudBlazorModule.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/AbpAspNetCoreComponentsWebAssemblyThemingMudBlazorModule.cs new file mode 100644 index 0000000000..6476687904 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/AbpAspNetCoreComponentsWebAssemblyThemingMudBlazorModule.cs @@ -0,0 +1,35 @@ +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor; +using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling; +using Volo.Abp.AspNetCore.Components.WebAssembly; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor; + +[DependsOn( + typeof(AbpAspNetCoreComponentsWebAssemblyThemingMudBlazorBundlingModule), + typeof(AbpAspNetCoreComponentsWebThemingMudBlazorModule), + typeof(AbpAspNetCoreComponentsWebAssemblyModule) +)] +public class AbpAspNetCoreComponentsWebAssemblyThemingMudBlazorModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options + .StyleBundles + .Add(BlazorWebAssemblyMudBlazorStandardBundles.Styles.Global, bundle => + { + bundle.AddContributors(typeof(BlazorWebAssemblyMudBlazorStyleContributor)); + }); + + options + .ScriptBundles + .Add(BlazorWebAssemblyMudBlazorStandardBundles.Scripts.Global, bundle => + { + bundle.AddContributors(typeof(BlazorWebAssemblyMudBlazorScriptContributor)); + }); + }); + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/CultureAwareRedirectToLoginHelper.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/CultureAwareRedirectToLoginHelper.cs new file mode 100644 index 0000000000..2ad0bdea79 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/CultureAwareRedirectToLoginHelper.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; +using Microsoft.Extensions.Options; +using Volo.Abp.AspNetCore.Components.Web; +using Volo.Abp.AspNetCore.Components.WebAssembly; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor; + +public static class CultureAwareRedirectToLoginHelper +{ + public static async Task RedirectAsync( + NavigationManager navigation, + string loginUrl, + IRouteBasedCultureUrlHelper cultureUrlHelper, + IOptions webOptions) + { + var cultureLoginUrl = await cultureUrlHelper.PrependCulturePrefixAsync(loginUrl); + if (webOptions.Value.IsBlazorWebApp) + { + navigation.NavigateTo(cultureLoginUrl, forceLoad: true); + } + else + { + navigation.NavigateToLogin(cultureLoginUrl); + } + } +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/FodyWeavers.xml b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/FodyWeavers.xml new file mode 100644 index 0000000000..7e9f94ead6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/FodyWeavers.xsd b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/FodyWeavers.xsd new file mode 100644 index 0000000000..ffa6fc4b78 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.abppkg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.abppkg new file mode 100644 index 0000000000..e0c33eaab1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.abppkg @@ -0,0 +1,3 @@ +{ + "role": "lib.framework" +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.csproj b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.csproj new file mode 100644 index 0000000000..1f830fa162 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.csproj @@ -0,0 +1,18 @@ + + + + + + + net10.0 + enable + Nullable + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/WebAssemblyMudBlazorComponentBundleManager.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/WebAssemblyMudBlazorComponentBundleManager.cs new file mode 100644 index 0000000000..c6a3a446f3 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/WebAssemblyMudBlazorComponentBundleManager.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Bundling; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor; + +public class WebAssemblyMudBlazorComponentBundleManager: IComponentBundleManager, ITransientDependency +{ + public virtual Task> GetStyleBundleFilesAsync(string bundleName) + { + return Task.FromResult>(new List()); + } + + public virtual Task> GetScriptBundleFilesAsync(string bundleName) + { + return Task.FromResult>(new List()); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/css/flag-icon.css b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/css/flag-icon.css new file mode 100644 index 0000000000..b54375090b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/css/flag-icon.css @@ -0,0 +1,1568 @@ +.flag-icon-background { + background-size: contain; + background-position: 50%; + background-repeat: no-repeat; +} +.flag-icon { + background-size: contain; + background-position: 50%; + background-repeat: no-repeat; + position: relative; + display: inline-block; + width: 1.33333333em; + line-height: 1em; +} +.flag-icon:before { + content: "\00a0"; +} +.flag-icon.flag-icon-squared { + width: 1em; +} +.flag-icon-ad { + background-image: url(../flags/4x3/ad.svg); +} +.flag-icon-ad.flag-icon-squared { + background-image: url(../flags/1x1/ad.svg); +} +.flag-icon-ae { + background-image: url(../flags/4x3/ae.svg); +} +.flag-icon-ae.flag-icon-squared { + background-image: url(../flags/1x1/ae.svg); +} +.flag-icon-af { + background-image: url(../flags/4x3/af.svg); +} +.flag-icon-af.flag-icon-squared { + background-image: url(../flags/1x1/af.svg); +} +.flag-icon-ag { + background-image: url(../flags/4x3/ag.svg); +} +.flag-icon-ag.flag-icon-squared { + background-image: url(../flags/1x1/ag.svg); +} +.flag-icon-ai { + background-image: url(../flags/4x3/ai.svg); +} +.flag-icon-ai.flag-icon-squared { + background-image: url(../flags/1x1/ai.svg); +} +.flag-icon-al { + background-image: url(../flags/4x3/al.svg); +} +.flag-icon-al.flag-icon-squared { + background-image: url(../flags/1x1/al.svg); +} +.flag-icon-am { + background-image: url(../flags/4x3/am.svg); +} +.flag-icon-am.flag-icon-squared { + background-image: url(../flags/1x1/am.svg); +} +.flag-icon-ao { + background-image: url(../flags/4x3/ao.svg); +} +.flag-icon-ao.flag-icon-squared { + background-image: url(../flags/1x1/ao.svg); +} +.flag-icon-aq { + background-image: url(../flags/4x3/aq.svg); +} +.flag-icon-aq.flag-icon-squared { + background-image: url(../flags/1x1/aq.svg); +} +.flag-icon-ar { + background-image: url(../flags/4x3/ar.svg); +} +.flag-icon-ar.flag-icon-squared { + background-image: url(../flags/1x1/ar.svg); +} +.flag-icon-as { + background-image: url(../flags/4x3/as.svg); +} +.flag-icon-as.flag-icon-squared { + background-image: url(../flags/1x1/as.svg); +} +.flag-icon-at { + background-image: url(../flags/4x3/at.svg); +} +.flag-icon-at.flag-icon-squared { + background-image: url(../flags/1x1/at.svg); +} +.flag-icon-au { + background-image: url(../flags/4x3/au.svg); +} +.flag-icon-au.flag-icon-squared { + background-image: url(../flags/1x1/au.svg); +} +.flag-icon-aw { + background-image: url(../flags/4x3/aw.svg); +} +.flag-icon-aw.flag-icon-squared { + background-image: url(../flags/1x1/aw.svg); +} +.flag-icon-ax { + background-image: url(../flags/4x3/ax.svg); +} +.flag-icon-ax.flag-icon-squared { + background-image: url(../flags/1x1/ax.svg); +} +.flag-icon-az { + background-image: url(../flags/4x3/az.svg); +} +.flag-icon-az.flag-icon-squared { + background-image: url(../flags/1x1/az.svg); +} +.flag-icon-ba { + background-image: url(../flags/4x3/ba.svg); +} +.flag-icon-ba.flag-icon-squared { + background-image: url(../flags/1x1/ba.svg); +} +.flag-icon-bb { + background-image: url(../flags/4x3/bb.svg); +} +.flag-icon-bb.flag-icon-squared { + background-image: url(../flags/1x1/bb.svg); +} +.flag-icon-bd { + background-image: url(../flags/4x3/bd.svg); +} +.flag-icon-bd.flag-icon-squared { + background-image: url(../flags/1x1/bd.svg); +} +.flag-icon-be { + background-image: url(../flags/4x3/be.svg); +} +.flag-icon-be.flag-icon-squared { + background-image: url(../flags/1x1/be.svg); +} +.flag-icon-bf { + background-image: url(../flags/4x3/bf.svg); +} +.flag-icon-bf.flag-icon-squared { + background-image: url(../flags/1x1/bf.svg); +} +.flag-icon-bg { + background-image: url(../flags/4x3/bg.svg); +} +.flag-icon-bg.flag-icon-squared { + background-image: url(../flags/1x1/bg.svg); +} +.flag-icon-bh { + background-image: url(../flags/4x3/bh.svg); +} +.flag-icon-bh.flag-icon-squared { + background-image: url(../flags/1x1/bh.svg); +} +.flag-icon-bi { + background-image: url(../flags/4x3/bi.svg); +} +.flag-icon-bi.flag-icon-squared { + background-image: url(../flags/1x1/bi.svg); +} +.flag-icon-bj { + background-image: url(../flags/4x3/bj.svg); +} +.flag-icon-bj.flag-icon-squared { + background-image: url(../flags/1x1/bj.svg); +} +.flag-icon-bl { + background-image: url(../flags/4x3/bl.svg); +} +.flag-icon-bl.flag-icon-squared { + background-image: url(../flags/1x1/bl.svg); +} +.flag-icon-bm { + background-image: url(../flags/4x3/bm.svg); +} +.flag-icon-bm.flag-icon-squared { + background-image: url(../flags/1x1/bm.svg); +} +.flag-icon-bn { + background-image: url(../flags/4x3/bn.svg); +} +.flag-icon-bn.flag-icon-squared { + background-image: url(../flags/1x1/bn.svg); +} +.flag-icon-bo { + background-image: url(../flags/4x3/bo.svg); +} +.flag-icon-bo.flag-icon-squared { + background-image: url(../flags/1x1/bo.svg); +} +.flag-icon-bq { + background-image: url(../flags/4x3/bq.svg); +} +.flag-icon-bq.flag-icon-squared { + background-image: url(../flags/1x1/bq.svg); +} +.flag-icon-br { + background-image: url(../flags/4x3/br.svg); +} +.flag-icon-br.flag-icon-squared { + background-image: url(../flags/1x1/br.svg); +} +.flag-icon-bs { + background-image: url(../flags/4x3/bs.svg); +} +.flag-icon-bs.flag-icon-squared { + background-image: url(../flags/1x1/bs.svg); +} +.flag-icon-bt { + background-image: url(../flags/4x3/bt.svg); +} +.flag-icon-bt.flag-icon-squared { + background-image: url(../flags/1x1/bt.svg); +} +.flag-icon-bv { + background-image: url(../flags/4x3/bv.svg); +} +.flag-icon-bv.flag-icon-squared { + background-image: url(../flags/1x1/bv.svg); +} +.flag-icon-bw { + background-image: url(../flags/4x3/bw.svg); +} +.flag-icon-bw.flag-icon-squared { + background-image: url(../flags/1x1/bw.svg); +} +.flag-icon-by { + background-image: url(../flags/4x3/by.svg); +} +.flag-icon-by.flag-icon-squared { + background-image: url(../flags/1x1/by.svg); +} +.flag-icon-bz { + background-image: url(../flags/4x3/bz.svg); +} +.flag-icon-bz.flag-icon-squared { + background-image: url(../flags/1x1/bz.svg); +} +.flag-icon-ca { + background-image: url(../flags/4x3/ca.svg); +} +.flag-icon-ca.flag-icon-squared { + background-image: url(../flags/1x1/ca.svg); +} +.flag-icon-cc { + background-image: url(../flags/4x3/cc.svg); +} +.flag-icon-cc.flag-icon-squared { + background-image: url(../flags/1x1/cc.svg); +} +.flag-icon-cd { + background-image: url(../flags/4x3/cd.svg); +} +.flag-icon-cd.flag-icon-squared { + background-image: url(../flags/1x1/cd.svg); +} +.flag-icon-cf { + background-image: url(../flags/4x3/cf.svg); +} +.flag-icon-cf.flag-icon-squared { + background-image: url(../flags/1x1/cf.svg); +} +.flag-icon-cg { + background-image: url(../flags/4x3/cg.svg); +} +.flag-icon-cg.flag-icon-squared { + background-image: url(../flags/1x1/cg.svg); +} +.flag-icon-ch { + background-image: url(../flags/4x3/ch.svg); +} +.flag-icon-ch.flag-icon-squared { + background-image: url(../flags/1x1/ch.svg); +} +.flag-icon-ci { + background-image: url(../flags/4x3/ci.svg); +} +.flag-icon-ci.flag-icon-squared { + background-image: url(../flags/1x1/ci.svg); +} +.flag-icon-ck { + background-image: url(../flags/4x3/ck.svg); +} +.flag-icon-ck.flag-icon-squared { + background-image: url(../flags/1x1/ck.svg); +} +.flag-icon-cl { + background-image: url(../flags/4x3/cl.svg); +} +.flag-icon-cl.flag-icon-squared { + background-image: url(../flags/1x1/cl.svg); +} +.flag-icon-cm { + background-image: url(../flags/4x3/cm.svg); +} +.flag-icon-cm.flag-icon-squared { + background-image: url(../flags/1x1/cm.svg); +} +.flag-icon-cn { + background-image: url(../flags/4x3/cn.svg); +} +.flag-icon-cn.flag-icon-squared { + background-image: url(../flags/1x1/cn.svg); +} +.flag-icon-co { + background-image: url(../flags/4x3/co.svg); +} +.flag-icon-co.flag-icon-squared { + background-image: url(../flags/1x1/co.svg); +} +.flag-icon-cr { + background-image: url(../flags/4x3/cr.svg); +} +.flag-icon-cr.flag-icon-squared { + background-image: url(../flags/1x1/cr.svg); +} +.flag-icon-cu { + background-image: url(../flags/4x3/cu.svg); +} +.flag-icon-cu.flag-icon-squared { + background-image: url(../flags/1x1/cu.svg); +} +.flag-icon-cv { + background-image: url(../flags/4x3/cv.svg); +} +.flag-icon-cv.flag-icon-squared { + background-image: url(../flags/1x1/cv.svg); +} +.flag-icon-cw { + background-image: url(../flags/4x3/cw.svg); +} +.flag-icon-cw.flag-icon-squared { + background-image: url(../flags/1x1/cw.svg); +} +.flag-icon-cx { + background-image: url(../flags/4x3/cx.svg); +} +.flag-icon-cx.flag-icon-squared { + background-image: url(../flags/1x1/cx.svg); +} +.flag-icon-cy { + background-image: url(../flags/4x3/cy.svg); +} +.flag-icon-cy.flag-icon-squared { + background-image: url(../flags/1x1/cy.svg); +} +.flag-icon-cz { + background-image: url(../flags/4x3/cz.svg); +} +.flag-icon-cz.flag-icon-squared { + background-image: url(../flags/1x1/cz.svg); +} +.flag-icon-de { + background-image: url(../flags/4x3/de.svg); +} +.flag-icon-de.flag-icon-squared { + background-image: url(../flags/1x1/de.svg); +} +.flag-icon-dj { + background-image: url(../flags/4x3/dj.svg); +} +.flag-icon-dj.flag-icon-squared { + background-image: url(../flags/1x1/dj.svg); +} +.flag-icon-dk { + background-image: url(../flags/4x3/dk.svg); +} +.flag-icon-dk.flag-icon-squared { + background-image: url(../flags/1x1/dk.svg); +} +.flag-icon-dm { + background-image: url(../flags/4x3/dm.svg); +} +.flag-icon-dm.flag-icon-squared { + background-image: url(../flags/1x1/dm.svg); +} +.flag-icon-do { + background-image: url(../flags/4x3/do.svg); +} +.flag-icon-do.flag-icon-squared { + background-image: url(../flags/1x1/do.svg); +} +.flag-icon-dz { + background-image: url(../flags/4x3/dz.svg); +} +.flag-icon-dz.flag-icon-squared { + background-image: url(../flags/1x1/dz.svg); +} +.flag-icon-ec { + background-image: url(../flags/4x3/ec.svg); +} +.flag-icon-ec.flag-icon-squared { + background-image: url(../flags/1x1/ec.svg); +} +.flag-icon-ee { + background-image: url(../flags/4x3/ee.svg); +} +.flag-icon-ee.flag-icon-squared { + background-image: url(../flags/1x1/ee.svg); +} +.flag-icon-eg { + background-image: url(../flags/4x3/eg.svg); +} +.flag-icon-eg.flag-icon-squared { + background-image: url(../flags/1x1/eg.svg); +} +.flag-icon-eh { + background-image: url(../flags/4x3/eh.svg); +} +.flag-icon-eh.flag-icon-squared { + background-image: url(../flags/1x1/eh.svg); +} +.flag-icon-er { + background-image: url(../flags/4x3/er.svg); +} +.flag-icon-er.flag-icon-squared { + background-image: url(../flags/1x1/er.svg); +} +.flag-icon-es { + background-image: url(../flags/4x3/es.svg); +} +.flag-icon-es.flag-icon-squared { + background-image: url(../flags/1x1/es.svg); +} +.flag-icon-et { + background-image: url(../flags/4x3/et.svg); +} +.flag-icon-et.flag-icon-squared { + background-image: url(../flags/1x1/et.svg); +} +.flag-icon-fi { + background-image: url(../flags/4x3/fi.svg); +} +.flag-icon-fi.flag-icon-squared { + background-image: url(../flags/1x1/fi.svg); +} +.flag-icon-fj { + background-image: url(../flags/4x3/fj.svg); +} +.flag-icon-fj.flag-icon-squared { + background-image: url(../flags/1x1/fj.svg); +} +.flag-icon-fk { + background-image: url(../flags/4x3/fk.svg); +} +.flag-icon-fk.flag-icon-squared { + background-image: url(../flags/1x1/fk.svg); +} +.flag-icon-fm { + background-image: url(../flags/4x3/fm.svg); +} +.flag-icon-fm.flag-icon-squared { + background-image: url(../flags/1x1/fm.svg); +} +.flag-icon-fo { + background-image: url(../flags/4x3/fo.svg); +} +.flag-icon-fo.flag-icon-squared { + background-image: url(../flags/1x1/fo.svg); +} +.flag-icon-fr { + background-image: url(../flags/4x3/fr.svg); +} +.flag-icon-fr.flag-icon-squared { + background-image: url(../flags/1x1/fr.svg); +} +.flag-icon-ga { + background-image: url(../flags/4x3/ga.svg); +} +.flag-icon-ga.flag-icon-squared { + background-image: url(../flags/1x1/ga.svg); +} +.flag-icon-gb { + background-image: url(../flags/4x3/gb.svg); +} +.flag-icon-gb.flag-icon-squared { + background-image: url(../flags/1x1/gb.svg); +} +.flag-icon-gd { + background-image: url(../flags/4x3/gd.svg); +} +.flag-icon-gd.flag-icon-squared { + background-image: url(../flags/1x1/gd.svg); +} +.flag-icon-ge { + background-image: url(../flags/4x3/ge.svg); +} +.flag-icon-ge.flag-icon-squared { + background-image: url(../flags/1x1/ge.svg); +} +.flag-icon-gf { + background-image: url(../flags/4x3/gf.svg); +} +.flag-icon-gf.flag-icon-squared { + background-image: url(../flags/1x1/gf.svg); +} +.flag-icon-gg { + background-image: url(../flags/4x3/gg.svg); +} +.flag-icon-gg.flag-icon-squared { + background-image: url(../flags/1x1/gg.svg); +} +.flag-icon-gh { + background-image: url(../flags/4x3/gh.svg); +} +.flag-icon-gh.flag-icon-squared { + background-image: url(../flags/1x1/gh.svg); +} +.flag-icon-gi { + background-image: url(../flags/4x3/gi.svg); +} +.flag-icon-gi.flag-icon-squared { + background-image: url(../flags/1x1/gi.svg); +} +.flag-icon-gl { + background-image: url(../flags/4x3/gl.svg); +} +.flag-icon-gl.flag-icon-squared { + background-image: url(../flags/1x1/gl.svg); +} +.flag-icon-gm { + background-image: url(../flags/4x3/gm.svg); +} +.flag-icon-gm.flag-icon-squared { + background-image: url(../flags/1x1/gm.svg); +} +.flag-icon-gn { + background-image: url(../flags/4x3/gn.svg); +} +.flag-icon-gn.flag-icon-squared { + background-image: url(../flags/1x1/gn.svg); +} +.flag-icon-gp { + background-image: url(../flags/4x3/gp.svg); +} +.flag-icon-gp.flag-icon-squared { + background-image: url(../flags/1x1/gp.svg); +} +.flag-icon-gq { + background-image: url(../flags/4x3/gq.svg); +} +.flag-icon-gq.flag-icon-squared { + background-image: url(../flags/1x1/gq.svg); +} +.flag-icon-gr { + background-image: url(../flags/4x3/gr.svg); +} +.flag-icon-gr.flag-icon-squared { + background-image: url(../flags/1x1/gr.svg); +} +.flag-icon-gs { + background-image: url(../flags/4x3/gs.svg); +} +.flag-icon-gs.flag-icon-squared { + background-image: url(../flags/1x1/gs.svg); +} +.flag-icon-gt { + background-image: url(../flags/4x3/gt.svg); +} +.flag-icon-gt.flag-icon-squared { + background-image: url(../flags/1x1/gt.svg); +} +.flag-icon-gu { + background-image: url(../flags/4x3/gu.svg); +} +.flag-icon-gu.flag-icon-squared { + background-image: url(../flags/1x1/gu.svg); +} +.flag-icon-gw { + background-image: url(../flags/4x3/gw.svg); +} +.flag-icon-gw.flag-icon-squared { + background-image: url(../flags/1x1/gw.svg); +} +.flag-icon-gy { + background-image: url(../flags/4x3/gy.svg); +} +.flag-icon-gy.flag-icon-squared { + background-image: url(../flags/1x1/gy.svg); +} +.flag-icon-hk { + background-image: url(../flags/4x3/hk.svg); +} +.flag-icon-hk.flag-icon-squared { + background-image: url(../flags/1x1/hk.svg); +} +.flag-icon-hm { + background-image: url(../flags/4x3/hm.svg); +} +.flag-icon-hm.flag-icon-squared { + background-image: url(../flags/1x1/hm.svg); +} +.flag-icon-hn { + background-image: url(../flags/4x3/hn.svg); +} +.flag-icon-hn.flag-icon-squared { + background-image: url(../flags/1x1/hn.svg); +} +.flag-icon-hr { + background-image: url(../flags/4x3/hr.svg); +} +.flag-icon-hr.flag-icon-squared { + background-image: url(../flags/1x1/hr.svg); +} +.flag-icon-ht { + background-image: url(../flags/4x3/ht.svg); +} +.flag-icon-ht.flag-icon-squared { + background-image: url(../flags/1x1/ht.svg); +} +.flag-icon-hu { + background-image: url(../flags/4x3/hu.svg); +} +.flag-icon-hu.flag-icon-squared { + background-image: url(../flags/1x1/hu.svg); +} +.flag-icon-id { + background-image: url(../flags/4x3/id.svg); +} +.flag-icon-id.flag-icon-squared { + background-image: url(../flags/1x1/id.svg); +} +.flag-icon-ie { + background-image: url(../flags/4x3/ie.svg); +} +.flag-icon-ie.flag-icon-squared { + background-image: url(../flags/1x1/ie.svg); +} +.flag-icon-il { + background-image: url(../flags/4x3/il.svg); +} +.flag-icon-il.flag-icon-squared { + background-image: url(../flags/1x1/il.svg); +} +.flag-icon-im { + background-image: url(../flags/4x3/im.svg); +} +.flag-icon-im.flag-icon-squared { + background-image: url(../flags/1x1/im.svg); +} +.flag-icon-in { + background-image: url(../flags/4x3/in.svg); +} +.flag-icon-in.flag-icon-squared { + background-image: url(../flags/1x1/in.svg); +} +.flag-icon-io { + background-image: url(../flags/4x3/io.svg); +} +.flag-icon-io.flag-icon-squared { + background-image: url(../flags/1x1/io.svg); +} +.flag-icon-iq { + background-image: url(../flags/4x3/iq.svg); +} +.flag-icon-iq.flag-icon-squared { + background-image: url(../flags/1x1/iq.svg); +} +.flag-icon-ir { + background-image: url(../flags/4x3/ir.svg); +} +.flag-icon-ir.flag-icon-squared { + background-image: url(../flags/1x1/ir.svg); +} +.flag-icon-is { + background-image: url(../flags/4x3/is.svg); +} +.flag-icon-is.flag-icon-squared { + background-image: url(../flags/1x1/is.svg); +} +.flag-icon-it { + background-image: url(../flags/4x3/it.svg); +} +.flag-icon-it.flag-icon-squared { + background-image: url(../flags/1x1/it.svg); +} +.flag-icon-je { + background-image: url(../flags/4x3/je.svg); +} +.flag-icon-je.flag-icon-squared { + background-image: url(../flags/1x1/je.svg); +} +.flag-icon-jm { + background-image: url(../flags/4x3/jm.svg); +} +.flag-icon-jm.flag-icon-squared { + background-image: url(../flags/1x1/jm.svg); +} +.flag-icon-jo { + background-image: url(../flags/4x3/jo.svg); +} +.flag-icon-jo.flag-icon-squared { + background-image: url(../flags/1x1/jo.svg); +} +.flag-icon-jp { + background-image: url(../flags/4x3/jp.svg); +} +.flag-icon-jp.flag-icon-squared { + background-image: url(../flags/1x1/jp.svg); +} +.flag-icon-ke { + background-image: url(../flags/4x3/ke.svg); +} +.flag-icon-ke.flag-icon-squared { + background-image: url(../flags/1x1/ke.svg); +} +.flag-icon-kg { + background-image: url(../flags/4x3/kg.svg); +} +.flag-icon-kg.flag-icon-squared { + background-image: url(../flags/1x1/kg.svg); +} +.flag-icon-kh { + background-image: url(../flags/4x3/kh.svg); +} +.flag-icon-kh.flag-icon-squared { + background-image: url(../flags/1x1/kh.svg); +} +.flag-icon-ki { + background-image: url(../flags/4x3/ki.svg); +} +.flag-icon-ki.flag-icon-squared { + background-image: url(../flags/1x1/ki.svg); +} +.flag-icon-km { + background-image: url(../flags/4x3/km.svg); +} +.flag-icon-km.flag-icon-squared { + background-image: url(../flags/1x1/km.svg); +} +.flag-icon-kn { + background-image: url(../flags/4x3/kn.svg); +} +.flag-icon-kn.flag-icon-squared { + background-image: url(../flags/1x1/kn.svg); +} +.flag-icon-kp { + background-image: url(../flags/4x3/kp.svg); +} +.flag-icon-kp.flag-icon-squared { + background-image: url(../flags/1x1/kp.svg); +} +.flag-icon-kr { + background-image: url(../flags/4x3/kr.svg); +} +.flag-icon-kr.flag-icon-squared { + background-image: url(../flags/1x1/kr.svg); +} +.flag-icon-kw { + background-image: url(../flags/4x3/kw.svg); +} +.flag-icon-kw.flag-icon-squared { + background-image: url(../flags/1x1/kw.svg); +} +.flag-icon-ky { + background-image: url(../flags/4x3/ky.svg); +} +.flag-icon-ky.flag-icon-squared { + background-image: url(../flags/1x1/ky.svg); +} +.flag-icon-kz { + background-image: url(../flags/4x3/kz.svg); +} +.flag-icon-kz.flag-icon-squared { + background-image: url(../flags/1x1/kz.svg); +} +.flag-icon-la { + background-image: url(../flags/4x3/la.svg); +} +.flag-icon-la.flag-icon-squared { + background-image: url(../flags/1x1/la.svg); +} +.flag-icon-lb { + background-image: url(../flags/4x3/lb.svg); +} +.flag-icon-lb.flag-icon-squared { + background-image: url(../flags/1x1/lb.svg); +} +.flag-icon-lc { + background-image: url(../flags/4x3/lc.svg); +} +.flag-icon-lc.flag-icon-squared { + background-image: url(../flags/1x1/lc.svg); +} +.flag-icon-li { + background-image: url(../flags/4x3/li.svg); +} +.flag-icon-li.flag-icon-squared { + background-image: url(../flags/1x1/li.svg); +} +.flag-icon-lk { + background-image: url(../flags/4x3/lk.svg); +} +.flag-icon-lk.flag-icon-squared { + background-image: url(../flags/1x1/lk.svg); +} +.flag-icon-lr { + background-image: url(../flags/4x3/lr.svg); +} +.flag-icon-lr.flag-icon-squared { + background-image: url(../flags/1x1/lr.svg); +} +.flag-icon-ls { + background-image: url(../flags/4x3/ls.svg); +} +.flag-icon-ls.flag-icon-squared { + background-image: url(../flags/1x1/ls.svg); +} +.flag-icon-lt { + background-image: url(../flags/4x3/lt.svg); +} +.flag-icon-lt.flag-icon-squared { + background-image: url(../flags/1x1/lt.svg); +} +.flag-icon-lu { + background-image: url(../flags/4x3/lu.svg); +} +.flag-icon-lu.flag-icon-squared { + background-image: url(../flags/1x1/lu.svg); +} +.flag-icon-lv { + background-image: url(../flags/4x3/lv.svg); +} +.flag-icon-lv.flag-icon-squared { + background-image: url(../flags/1x1/lv.svg); +} +.flag-icon-ly { + background-image: url(../flags/4x3/ly.svg); +} +.flag-icon-ly.flag-icon-squared { + background-image: url(../flags/1x1/ly.svg); +} +.flag-icon-ma { + background-image: url(../flags/4x3/ma.svg); +} +.flag-icon-ma.flag-icon-squared { + background-image: url(../flags/1x1/ma.svg); +} +.flag-icon-mc { + background-image: url(../flags/4x3/mc.svg); +} +.flag-icon-mc.flag-icon-squared { + background-image: url(../flags/1x1/mc.svg); +} +.flag-icon-md { + background-image: url(../flags/4x3/md.svg); +} +.flag-icon-md.flag-icon-squared { + background-image: url(../flags/1x1/md.svg); +} +.flag-icon-me { + background-image: url(../flags/4x3/me.svg); +} +.flag-icon-me.flag-icon-squared { + background-image: url(../flags/1x1/me.svg); +} +.flag-icon-mf { + background-image: url(../flags/4x3/mf.svg); +} +.flag-icon-mf.flag-icon-squared { + background-image: url(../flags/1x1/mf.svg); +} +.flag-icon-mg { + background-image: url(../flags/4x3/mg.svg); +} +.flag-icon-mg.flag-icon-squared { + background-image: url(../flags/1x1/mg.svg); +} +.flag-icon-mh { + background-image: url(../flags/4x3/mh.svg); +} +.flag-icon-mh.flag-icon-squared { + background-image: url(../flags/1x1/mh.svg); +} +.flag-icon-mk { + background-image: url(../flags/4x3/mk.svg); +} +.flag-icon-mk.flag-icon-squared { + background-image: url(../flags/1x1/mk.svg); +} +.flag-icon-ml { + background-image: url(../flags/4x3/ml.svg); +} +.flag-icon-ml.flag-icon-squared { + background-image: url(../flags/1x1/ml.svg); +} +.flag-icon-mm { + background-image: url(../flags/4x3/mm.svg); +} +.flag-icon-mm.flag-icon-squared { + background-image: url(../flags/1x1/mm.svg); +} +.flag-icon-mn { + background-image: url(../flags/4x3/mn.svg); +} +.flag-icon-mn.flag-icon-squared { + background-image: url(../flags/1x1/mn.svg); +} +.flag-icon-mo { + background-image: url(../flags/4x3/mo.svg); +} +.flag-icon-mo.flag-icon-squared { + background-image: url(../flags/1x1/mo.svg); +} +.flag-icon-mp { + background-image: url(../flags/4x3/mp.svg); +} +.flag-icon-mp.flag-icon-squared { + background-image: url(../flags/1x1/mp.svg); +} +.flag-icon-mq { + background-image: url(../flags/4x3/mq.svg); +} +.flag-icon-mq.flag-icon-squared { + background-image: url(../flags/1x1/mq.svg); +} +.flag-icon-mr { + background-image: url(../flags/4x3/mr.svg); +} +.flag-icon-mr.flag-icon-squared { + background-image: url(../flags/1x1/mr.svg); +} +.flag-icon-ms { + background-image: url(../flags/4x3/ms.svg); +} +.flag-icon-ms.flag-icon-squared { + background-image: url(../flags/1x1/ms.svg); +} +.flag-icon-mt { + background-image: url(../flags/4x3/mt.svg); +} +.flag-icon-mt.flag-icon-squared { + background-image: url(../flags/1x1/mt.svg); +} +.flag-icon-mu { + background-image: url(../flags/4x3/mu.svg); +} +.flag-icon-mu.flag-icon-squared { + background-image: url(../flags/1x1/mu.svg); +} +.flag-icon-mv { + background-image: url(../flags/4x3/mv.svg); +} +.flag-icon-mv.flag-icon-squared { + background-image: url(../flags/1x1/mv.svg); +} +.flag-icon-mw { + background-image: url(../flags/4x3/mw.svg); +} +.flag-icon-mw.flag-icon-squared { + background-image: url(../flags/1x1/mw.svg); +} +.flag-icon-mx { + background-image: url(../flags/4x3/mx.svg); +} +.flag-icon-mx.flag-icon-squared { + background-image: url(../flags/1x1/mx.svg); +} +.flag-icon-my { + background-image: url(../flags/4x3/my.svg); +} +.flag-icon-my.flag-icon-squared { + background-image: url(../flags/1x1/my.svg); +} +.flag-icon-mz { + background-image: url(../flags/4x3/mz.svg); +} +.flag-icon-mz.flag-icon-squared { + background-image: url(../flags/1x1/mz.svg); +} +.flag-icon-na { + background-image: url(../flags/4x3/na.svg); +} +.flag-icon-na.flag-icon-squared { + background-image: url(../flags/1x1/na.svg); +} +.flag-icon-nc { + background-image: url(../flags/4x3/nc.svg); +} +.flag-icon-nc.flag-icon-squared { + background-image: url(../flags/1x1/nc.svg); +} +.flag-icon-ne { + background-image: url(../flags/4x3/ne.svg); +} +.flag-icon-ne.flag-icon-squared { + background-image: url(../flags/1x1/ne.svg); +} +.flag-icon-nf { + background-image: url(../flags/4x3/nf.svg); +} +.flag-icon-nf.flag-icon-squared { + background-image: url(../flags/1x1/nf.svg); +} +.flag-icon-ng { + background-image: url(../flags/4x3/ng.svg); +} +.flag-icon-ng.flag-icon-squared { + background-image: url(../flags/1x1/ng.svg); +} +.flag-icon-ni { + background-image: url(../flags/4x3/ni.svg); +} +.flag-icon-ni.flag-icon-squared { + background-image: url(../flags/1x1/ni.svg); +} +.flag-icon-nl { + background-image: url(../flags/4x3/nl.svg); +} +.flag-icon-nl.flag-icon-squared { + background-image: url(../flags/1x1/nl.svg); +} +.flag-icon-no { + background-image: url(../flags/4x3/no.svg); +} +.flag-icon-no.flag-icon-squared { + background-image: url(../flags/1x1/no.svg); +} +.flag-icon-np { + background-image: url(../flags/4x3/np.svg); +} +.flag-icon-np.flag-icon-squared { + background-image: url(../flags/1x1/np.svg); +} +.flag-icon-nr { + background-image: url(../flags/4x3/nr.svg); +} +.flag-icon-nr.flag-icon-squared { + background-image: url(../flags/1x1/nr.svg); +} +.flag-icon-nu { + background-image: url(../flags/4x3/nu.svg); +} +.flag-icon-nu.flag-icon-squared { + background-image: url(../flags/1x1/nu.svg); +} +.flag-icon-nz { + background-image: url(../flags/4x3/nz.svg); +} +.flag-icon-nz.flag-icon-squared { + background-image: url(../flags/1x1/nz.svg); +} +.flag-icon-om { + background-image: url(../flags/4x3/om.svg); +} +.flag-icon-om.flag-icon-squared { + background-image: url(../flags/1x1/om.svg); +} +.flag-icon-pa { + background-image: url(../flags/4x3/pa.svg); +} +.flag-icon-pa.flag-icon-squared { + background-image: url(../flags/1x1/pa.svg); +} +.flag-icon-pe { + background-image: url(../flags/4x3/pe.svg); +} +.flag-icon-pe.flag-icon-squared { + background-image: url(../flags/1x1/pe.svg); +} +.flag-icon-pf { + background-image: url(../flags/4x3/pf.svg); +} +.flag-icon-pf.flag-icon-squared { + background-image: url(../flags/1x1/pf.svg); +} +.flag-icon-pg { + background-image: url(../flags/4x3/pg.svg); +} +.flag-icon-pg.flag-icon-squared { + background-image: url(../flags/1x1/pg.svg); +} +.flag-icon-ph { + background-image: url(../flags/4x3/ph.svg); +} +.flag-icon-ph.flag-icon-squared { + background-image: url(../flags/1x1/ph.svg); +} +.flag-icon-pk { + background-image: url(../flags/4x3/pk.svg); +} +.flag-icon-pk.flag-icon-squared { + background-image: url(../flags/1x1/pk.svg); +} +.flag-icon-pl { + background-image: url(../flags/4x3/pl.svg); +} +.flag-icon-pl.flag-icon-squared { + background-image: url(../flags/1x1/pl.svg); +} +.flag-icon-pm { + background-image: url(../flags/4x3/pm.svg); +} +.flag-icon-pm.flag-icon-squared { + background-image: url(../flags/1x1/pm.svg); +} +.flag-icon-pn { + background-image: url(../flags/4x3/pn.svg); +} +.flag-icon-pn.flag-icon-squared { + background-image: url(../flags/1x1/pn.svg); +} +.flag-icon-pr { + background-image: url(../flags/4x3/pr.svg); +} +.flag-icon-pr.flag-icon-squared { + background-image: url(../flags/1x1/pr.svg); +} +.flag-icon-ps { + background-image: url(../flags/4x3/ps.svg); +} +.flag-icon-ps.flag-icon-squared { + background-image: url(../flags/1x1/ps.svg); +} +.flag-icon-pt { + background-image: url(../flags/4x3/pt.svg); +} +.flag-icon-pt.flag-icon-squared { + background-image: url(../flags/1x1/pt.svg); +} +.flag-icon-pw { + background-image: url(../flags/4x3/pw.svg); +} +.flag-icon-pw.flag-icon-squared { + background-image: url(../flags/1x1/pw.svg); +} +.flag-icon-py { + background-image: url(../flags/4x3/py.svg); +} +.flag-icon-py.flag-icon-squared { + background-image: url(../flags/1x1/py.svg); +} +.flag-icon-qa { + background-image: url(../flags/4x3/qa.svg); +} +.flag-icon-qa.flag-icon-squared { + background-image: url(../flags/1x1/qa.svg); +} +.flag-icon-re { + background-image: url(../flags/4x3/re.svg); +} +.flag-icon-re.flag-icon-squared { + background-image: url(../flags/1x1/re.svg); +} +.flag-icon-ro { + background-image: url(../flags/4x3/ro.svg); +} +.flag-icon-ro.flag-icon-squared { + background-image: url(../flags/1x1/ro.svg); +} +.flag-icon-rs { + background-image: url(../flags/4x3/rs.svg); +} +.flag-icon-rs.flag-icon-squared { + background-image: url(../flags/1x1/rs.svg); +} +.flag-icon-ru { + background-image: url(../flags/4x3/ru.svg); +} +.flag-icon-ru.flag-icon-squared { + background-image: url(../flags/1x1/ru.svg); +} +.flag-icon-rw { + background-image: url(../flags/4x3/rw.svg); +} +.flag-icon-rw.flag-icon-squared { + background-image: url(../flags/1x1/rw.svg); +} +.flag-icon-sa { + background-image: url(../flags/4x3/sa.svg); +} +.flag-icon-sa.flag-icon-squared { + background-image: url(../flags/1x1/sa.svg); +} +.flag-icon-sb { + background-image: url(../flags/4x3/sb.svg); +} +.flag-icon-sb.flag-icon-squared { + background-image: url(../flags/1x1/sb.svg); +} +.flag-icon-sc { + background-image: url(../flags/4x3/sc.svg); +} +.flag-icon-sc.flag-icon-squared { + background-image: url(../flags/1x1/sc.svg); +} +.flag-icon-sd { + background-image: url(../flags/4x3/sd.svg); +} +.flag-icon-sd.flag-icon-squared { + background-image: url(../flags/1x1/sd.svg); +} +.flag-icon-se { + background-image: url(../flags/4x3/se.svg); +} +.flag-icon-se.flag-icon-squared { + background-image: url(../flags/1x1/se.svg); +} +.flag-icon-sg { + background-image: url(../flags/4x3/sg.svg); +} +.flag-icon-sg.flag-icon-squared { + background-image: url(../flags/1x1/sg.svg); +} +.flag-icon-sh { + background-image: url(../flags/4x3/sh.svg); +} +.flag-icon-sh.flag-icon-squared { + background-image: url(../flags/1x1/sh.svg); +} +.flag-icon-si { + background-image: url(../flags/4x3/si.svg); +} +.flag-icon-si.flag-icon-squared { + background-image: url(../flags/1x1/si.svg); +} +.flag-icon-sj { + background-image: url(../flags/4x3/sj.svg); +} +.flag-icon-sj.flag-icon-squared { + background-image: url(../flags/1x1/sj.svg); +} +.flag-icon-sk { + background-image: url(../flags/4x3/sk.svg); +} +.flag-icon-sk.flag-icon-squared { + background-image: url(../flags/1x1/sk.svg); +} +.flag-icon-sl { + background-image: url(../flags/4x3/sl.svg); +} +.flag-icon-sl.flag-icon-squared { + background-image: url(../flags/1x1/sl.svg); +} +.flag-icon-sm { + background-image: url(../flags/4x3/sm.svg); +} +.flag-icon-sm.flag-icon-squared { + background-image: url(../flags/1x1/sm.svg); +} +.flag-icon-sn { + background-image: url(../flags/4x3/sn.svg); +} +.flag-icon-sn.flag-icon-squared { + background-image: url(../flags/1x1/sn.svg); +} +.flag-icon-so { + background-image: url(../flags/4x3/so.svg); +} +.flag-icon-so.flag-icon-squared { + background-image: url(../flags/1x1/so.svg); +} +.flag-icon-sr { + background-image: url(../flags/4x3/sr.svg); +} +.flag-icon-sr.flag-icon-squared { + background-image: url(../flags/1x1/sr.svg); +} +.flag-icon-ss { + background-image: url(../flags/4x3/ss.svg); +} +.flag-icon-ss.flag-icon-squared { + background-image: url(../flags/1x1/ss.svg); +} +.flag-icon-st { + background-image: url(../flags/4x3/st.svg); +} +.flag-icon-st.flag-icon-squared { + background-image: url(../flags/1x1/st.svg); +} +.flag-icon-sv { + background-image: url(../flags/4x3/sv.svg); +} +.flag-icon-sv.flag-icon-squared { + background-image: url(../flags/1x1/sv.svg); +} +.flag-icon-sx { + background-image: url(../flags/4x3/sx.svg); +} +.flag-icon-sx.flag-icon-squared { + background-image: url(../flags/1x1/sx.svg); +} +.flag-icon-sy { + background-image: url(../flags/4x3/sy.svg); +} +.flag-icon-sy.flag-icon-squared { + background-image: url(../flags/1x1/sy.svg); +} +.flag-icon-sz { + background-image: url(../flags/4x3/sz.svg); +} +.flag-icon-sz.flag-icon-squared { + background-image: url(../flags/1x1/sz.svg); +} +.flag-icon-tc { + background-image: url(../flags/4x3/tc.svg); +} +.flag-icon-tc.flag-icon-squared { + background-image: url(../flags/1x1/tc.svg); +} +.flag-icon-td { + background-image: url(../flags/4x3/td.svg); +} +.flag-icon-td.flag-icon-squared { + background-image: url(../flags/1x1/td.svg); +} +.flag-icon-tf { + background-image: url(../flags/4x3/tf.svg); +} +.flag-icon-tf.flag-icon-squared { + background-image: url(../flags/1x1/tf.svg); +} +.flag-icon-tg { + background-image: url(../flags/4x3/tg.svg); +} +.flag-icon-tg.flag-icon-squared { + background-image: url(../flags/1x1/tg.svg); +} +.flag-icon-th { + background-image: url(../flags/4x3/th.svg); +} +.flag-icon-th.flag-icon-squared { + background-image: url(../flags/1x1/th.svg); +} +.flag-icon-tj { + background-image: url(../flags/4x3/tj.svg); +} +.flag-icon-tj.flag-icon-squared { + background-image: url(../flags/1x1/tj.svg); +} +.flag-icon-tk { + background-image: url(../flags/4x3/tk.svg); +} +.flag-icon-tk.flag-icon-squared { + background-image: url(../flags/1x1/tk.svg); +} +.flag-icon-tl { + background-image: url(../flags/4x3/tl.svg); +} +.flag-icon-tl.flag-icon-squared { + background-image: url(../flags/1x1/tl.svg); +} +.flag-icon-tm { + background-image: url(../flags/4x3/tm.svg); +} +.flag-icon-tm.flag-icon-squared { + background-image: url(../flags/1x1/tm.svg); +} +.flag-icon-tn { + background-image: url(../flags/4x3/tn.svg); +} +.flag-icon-tn.flag-icon-squared { + background-image: url(../flags/1x1/tn.svg); +} +.flag-icon-to { + background-image: url(../flags/4x3/to.svg); +} +.flag-icon-to.flag-icon-squared { + background-image: url(../flags/1x1/to.svg); +} +.flag-icon-tr { + background-image: url(../flags/4x3/tr.svg); +} +.flag-icon-tr.flag-icon-squared { + background-image: url(../flags/1x1/tr.svg); +} +.flag-icon-tt { + background-image: url(../flags/4x3/tt.svg); +} +.flag-icon-tt.flag-icon-squared { + background-image: url(../flags/1x1/tt.svg); +} +.flag-icon-tv { + background-image: url(../flags/4x3/tv.svg); +} +.flag-icon-tv.flag-icon-squared { + background-image: url(../flags/1x1/tv.svg); +} +.flag-icon-tw { + background-image: url(../flags/4x3/tw.svg); +} +.flag-icon-tw.flag-icon-squared { + background-image: url(../flags/1x1/tw.svg); +} +.flag-icon-tz { + background-image: url(../flags/4x3/tz.svg); +} +.flag-icon-tz.flag-icon-squared { + background-image: url(../flags/1x1/tz.svg); +} +.flag-icon-ua { + background-image: url(../flags/4x3/ua.svg); +} +.flag-icon-ua.flag-icon-squared { + background-image: url(../flags/1x1/ua.svg); +} +.flag-icon-ug { + background-image: url(../flags/4x3/ug.svg); +} +.flag-icon-ug.flag-icon-squared { + background-image: url(../flags/1x1/ug.svg); +} +.flag-icon-um { + background-image: url(../flags/4x3/um.svg); +} +.flag-icon-um.flag-icon-squared { + background-image: url(../flags/1x1/um.svg); +} +.flag-icon-us { + background-image: url(../flags/4x3/us.svg); +} +.flag-icon-us.flag-icon-squared { + background-image: url(../flags/1x1/us.svg); +} +.flag-icon-uy { + background-image: url(../flags/4x3/uy.svg); +} +.flag-icon-uy.flag-icon-squared { + background-image: url(../flags/1x1/uy.svg); +} +.flag-icon-uz { + background-image: url(../flags/4x3/uz.svg); +} +.flag-icon-uz.flag-icon-squared { + background-image: url(../flags/1x1/uz.svg); +} +.flag-icon-va { + background-image: url(../flags/4x3/va.svg); +} +.flag-icon-va.flag-icon-squared { + background-image: url(../flags/1x1/va.svg); +} +.flag-icon-vc { + background-image: url(../flags/4x3/vc.svg); +} +.flag-icon-vc.flag-icon-squared { + background-image: url(../flags/1x1/vc.svg); +} +.flag-icon-ve { + background-image: url(../flags/4x3/ve.svg); +} +.flag-icon-ve.flag-icon-squared { + background-image: url(../flags/1x1/ve.svg); +} +.flag-icon-vg { + background-image: url(../flags/4x3/vg.svg); +} +.flag-icon-vg.flag-icon-squared { + background-image: url(../flags/1x1/vg.svg); +} +.flag-icon-vi { + background-image: url(../flags/4x3/vi.svg); +} +.flag-icon-vi.flag-icon-squared { + background-image: url(../flags/1x1/vi.svg); +} +.flag-icon-vn { + background-image: url(../flags/4x3/vn.svg); +} +.flag-icon-vn.flag-icon-squared { + background-image: url(../flags/1x1/vn.svg); +} +.flag-icon-vu { + background-image: url(../flags/4x3/vu.svg); +} +.flag-icon-vu.flag-icon-squared { + background-image: url(../flags/1x1/vu.svg); +} +.flag-icon-wf { + background-image: url(../flags/4x3/wf.svg); +} +.flag-icon-wf.flag-icon-squared { + background-image: url(../flags/1x1/wf.svg); +} +.flag-icon-ws { + background-image: url(../flags/4x3/ws.svg); +} +.flag-icon-ws.flag-icon-squared { + background-image: url(../flags/1x1/ws.svg); +} +.flag-icon-ye { + background-image: url(../flags/4x3/ye.svg); +} +.flag-icon-ye.flag-icon-squared { + background-image: url(../flags/1x1/ye.svg); +} +.flag-icon-yt { + background-image: url(../flags/4x3/yt.svg); +} +.flag-icon-yt.flag-icon-squared { + background-image: url(../flags/1x1/yt.svg); +} +.flag-icon-za { + background-image: url(../flags/4x3/za.svg); +} +.flag-icon-za.flag-icon-squared { + background-image: url(../flags/1x1/za.svg); +} +.flag-icon-zm { + background-image: url(../flags/4x3/zm.svg); +} +.flag-icon-zm.flag-icon-squared { + background-image: url(../flags/1x1/zm.svg); +} +.flag-icon-zw { + background-image: url(../flags/4x3/zw.svg); +} +.flag-icon-zw.flag-icon-squared { + background-image: url(../flags/1x1/zw.svg); +} +.flag-icon-es-ca { + background-image: url(../flags/4x3/es-ca.svg); +} +.flag-icon-es-ca.flag-icon-squared { + background-image: url(../flags/1x1/es-ca.svg); +} +.flag-icon-es-ga { + background-image: url(../flags/4x3/es-ga.svg); +} +.flag-icon-es-ga.flag-icon-squared { + background-image: url(../flags/1x1/es-ga.svg); +} +.flag-icon-eu { + background-image: url(../flags/4x3/eu.svg); +} +.flag-icon-eu.flag-icon-squared { + background-image: url(../flags/1x1/eu.svg); +} +.flag-icon-gb-eng { + background-image: url(../flags/4x3/gb-eng.svg); +} +.flag-icon-gb-eng.flag-icon-squared { + background-image: url(../flags/1x1/gb-eng.svg); +} +.flag-icon-gb-nir { + background-image: url(../flags/4x3/gb-nir.svg); +} +.flag-icon-gb-nir.flag-icon-squared { + background-image: url(../flags/1x1/gb-nir.svg); +} +.flag-icon-gb-sct { + background-image: url(../flags/4x3/gb-sct.svg); +} +.flag-icon-gb-sct.flag-icon-squared { + background-image: url(../flags/1x1/gb-sct.svg); +} +.flag-icon-gb-wls { + background-image: url(../flags/4x3/gb-wls.svg); +} +.flag-icon-gb-wls.flag-icon-squared { + background-image: url(../flags/1x1/gb-wls.svg); +} +.flag-icon-un { + background-image: url(../flags/4x3/un.svg); +} +.flag-icon-un.flag-icon-squared { + background-image: url(../flags/1x1/un.svg); +} +.flag-icon-xk { + background-image: url(../flags/4x3/xk.svg); +} +.flag-icon-xk.flag-icon-squared { + background-image: url(../flags/1x1/xk.svg); +} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/css/flag-icon.min.css b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/css/flag-icon.min.css new file mode 100644 index 0000000000..3d049b3953 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/css/flag-icon.min.css @@ -0,0 +1 @@ +.flag-icon-background{background-size:contain;background-position:50%;background-repeat:no-repeat}.flag-icon{background-size:contain;background-position:50%;background-repeat:no-repeat;position:relative;display:inline-block;width:1.33333333em;line-height:1em}.flag-icon:before{content:'\00a0'}.flag-icon.flag-icon-squared{width:1em}.flag-icon-ad{background-image:url(../flags/4x3/ad.svg)}.flag-icon-ad.flag-icon-squared{background-image:url(../flags/1x1/ad.svg)}.flag-icon-ae{background-image:url(../flags/4x3/ae.svg)}.flag-icon-ae.flag-icon-squared{background-image:url(../flags/1x1/ae.svg)}.flag-icon-af{background-image:url(../flags/4x3/af.svg)}.flag-icon-af.flag-icon-squared{background-image:url(../flags/1x1/af.svg)}.flag-icon-ag{background-image:url(../flags/4x3/ag.svg)}.flag-icon-ag.flag-icon-squared{background-image:url(../flags/1x1/ag.svg)}.flag-icon-ai{background-image:url(../flags/4x3/ai.svg)}.flag-icon-ai.flag-icon-squared{background-image:url(../flags/1x1/ai.svg)}.flag-icon-al{background-image:url(../flags/4x3/al.svg)}.flag-icon-al.flag-icon-squared{background-image:url(../flags/1x1/al.svg)}.flag-icon-am{background-image:url(../flags/4x3/am.svg)}.flag-icon-am.flag-icon-squared{background-image:url(../flags/1x1/am.svg)}.flag-icon-ao{background-image:url(../flags/4x3/ao.svg)}.flag-icon-ao.flag-icon-squared{background-image:url(../flags/1x1/ao.svg)}.flag-icon-aq{background-image:url(../flags/4x3/aq.svg)}.flag-icon-aq.flag-icon-squared{background-image:url(../flags/1x1/aq.svg)}.flag-icon-ar{background-image:url(../flags/4x3/ar.svg)}.flag-icon-ar.flag-icon-squared{background-image:url(../flags/1x1/ar.svg)}.flag-icon-as{background-image:url(../flags/4x3/as.svg)}.flag-icon-as.flag-icon-squared{background-image:url(../flags/1x1/as.svg)}.flag-icon-at{background-image:url(../flags/4x3/at.svg)}.flag-icon-at.flag-icon-squared{background-image:url(../flags/1x1/at.svg)}.flag-icon-au{background-image:url(../flags/4x3/au.svg)}.flag-icon-au.flag-icon-squared{background-image:url(../flags/1x1/au.svg)}.flag-icon-aw{background-image:url(../flags/4x3/aw.svg)}.flag-icon-aw.flag-icon-squared{background-image:url(../flags/1x1/aw.svg)}.flag-icon-ax{background-image:url(../flags/4x3/ax.svg)}.flag-icon-ax.flag-icon-squared{background-image:url(../flags/1x1/ax.svg)}.flag-icon-az{background-image:url(../flags/4x3/az.svg)}.flag-icon-az.flag-icon-squared{background-image:url(../flags/1x1/az.svg)}.flag-icon-ba{background-image:url(../flags/4x3/ba.svg)}.flag-icon-ba.flag-icon-squared{background-image:url(../flags/1x1/ba.svg)}.flag-icon-bb{background-image:url(../flags/4x3/bb.svg)}.flag-icon-bb.flag-icon-squared{background-image:url(../flags/1x1/bb.svg)}.flag-icon-bd{background-image:url(../flags/4x3/bd.svg)}.flag-icon-bd.flag-icon-squared{background-image:url(../flags/1x1/bd.svg)}.flag-icon-be{background-image:url(../flags/4x3/be.svg)}.flag-icon-be.flag-icon-squared{background-image:url(../flags/1x1/be.svg)}.flag-icon-bf{background-image:url(../flags/4x3/bf.svg)}.flag-icon-bf.flag-icon-squared{background-image:url(../flags/1x1/bf.svg)}.flag-icon-bg{background-image:url(../flags/4x3/bg.svg)}.flag-icon-bg.flag-icon-squared{background-image:url(../flags/1x1/bg.svg)}.flag-icon-bh{background-image:url(../flags/4x3/bh.svg)}.flag-icon-bh.flag-icon-squared{background-image:url(../flags/1x1/bh.svg)}.flag-icon-bi{background-image:url(../flags/4x3/bi.svg)}.flag-icon-bi.flag-icon-squared{background-image:url(../flags/1x1/bi.svg)}.flag-icon-bj{background-image:url(../flags/4x3/bj.svg)}.flag-icon-bj.flag-icon-squared{background-image:url(../flags/1x1/bj.svg)}.flag-icon-bl{background-image:url(../flags/4x3/bl.svg)}.flag-icon-bl.flag-icon-squared{background-image:url(../flags/1x1/bl.svg)}.flag-icon-bm{background-image:url(../flags/4x3/bm.svg)}.flag-icon-bm.flag-icon-squared{background-image:url(../flags/1x1/bm.svg)}.flag-icon-bn{background-image:url(../flags/4x3/bn.svg)}.flag-icon-bn.flag-icon-squared{background-image:url(../flags/1x1/bn.svg)}.flag-icon-bo{background-image:url(../flags/4x3/bo.svg)}.flag-icon-bo.flag-icon-squared{background-image:url(../flags/1x1/bo.svg)}.flag-icon-bq{background-image:url(../flags/4x3/bq.svg)}.flag-icon-bq.flag-icon-squared{background-image:url(../flags/1x1/bq.svg)}.flag-icon-br{background-image:url(../flags/4x3/br.svg)}.flag-icon-br.flag-icon-squared{background-image:url(../flags/1x1/br.svg)}.flag-icon-bs{background-image:url(../flags/4x3/bs.svg)}.flag-icon-bs.flag-icon-squared{background-image:url(../flags/1x1/bs.svg)}.flag-icon-bt{background-image:url(../flags/4x3/bt.svg)}.flag-icon-bt.flag-icon-squared{background-image:url(../flags/1x1/bt.svg)}.flag-icon-bv{background-image:url(../flags/4x3/bv.svg)}.flag-icon-bv.flag-icon-squared{background-image:url(../flags/1x1/bv.svg)}.flag-icon-bw{background-image:url(../flags/4x3/bw.svg)}.flag-icon-bw.flag-icon-squared{background-image:url(../flags/1x1/bw.svg)}.flag-icon-by{background-image:url(../flags/4x3/by.svg)}.flag-icon-by.flag-icon-squared{background-image:url(../flags/1x1/by.svg)}.flag-icon-bz{background-image:url(../flags/4x3/bz.svg)}.flag-icon-bz.flag-icon-squared{background-image:url(../flags/1x1/bz.svg)}.flag-icon-ca{background-image:url(../flags/4x3/ca.svg)}.flag-icon-ca.flag-icon-squared{background-image:url(../flags/1x1/ca.svg)}.flag-icon-cc{background-image:url(../flags/4x3/cc.svg)}.flag-icon-cc.flag-icon-squared{background-image:url(../flags/1x1/cc.svg)}.flag-icon-cd{background-image:url(../flags/4x3/cd.svg)}.flag-icon-cd.flag-icon-squared{background-image:url(../flags/1x1/cd.svg)}.flag-icon-cf{background-image:url(../flags/4x3/cf.svg)}.flag-icon-cf.flag-icon-squared{background-image:url(../flags/1x1/cf.svg)}.flag-icon-cg{background-image:url(../flags/4x3/cg.svg)}.flag-icon-cg.flag-icon-squared{background-image:url(../flags/1x1/cg.svg)}.flag-icon-ch{background-image:url(../flags/4x3/ch.svg)}.flag-icon-ch.flag-icon-squared{background-image:url(../flags/1x1/ch.svg)}.flag-icon-ci{background-image:url(../flags/4x3/ci.svg)}.flag-icon-ci.flag-icon-squared{background-image:url(../flags/1x1/ci.svg)}.flag-icon-ck{background-image:url(../flags/4x3/ck.svg)}.flag-icon-ck.flag-icon-squared{background-image:url(../flags/1x1/ck.svg)}.flag-icon-cl{background-image:url(../flags/4x3/cl.svg)}.flag-icon-cl.flag-icon-squared{background-image:url(../flags/1x1/cl.svg)}.flag-icon-cm{background-image:url(../flags/4x3/cm.svg)}.flag-icon-cm.flag-icon-squared{background-image:url(../flags/1x1/cm.svg)}.flag-icon-cn{background-image:url(../flags/4x3/cn.svg)}.flag-icon-cn.flag-icon-squared{background-image:url(../flags/1x1/cn.svg)}.flag-icon-co{background-image:url(../flags/4x3/co.svg)}.flag-icon-co.flag-icon-squared{background-image:url(../flags/1x1/co.svg)}.flag-icon-cr{background-image:url(../flags/4x3/cr.svg)}.flag-icon-cr.flag-icon-squared{background-image:url(../flags/1x1/cr.svg)}.flag-icon-cu{background-image:url(../flags/4x3/cu.svg)}.flag-icon-cu.flag-icon-squared{background-image:url(../flags/1x1/cu.svg)}.flag-icon-cv{background-image:url(../flags/4x3/cv.svg)}.flag-icon-cv.flag-icon-squared{background-image:url(../flags/1x1/cv.svg)}.flag-icon-cw{background-image:url(../flags/4x3/cw.svg)}.flag-icon-cw.flag-icon-squared{background-image:url(../flags/1x1/cw.svg)}.flag-icon-cx{background-image:url(../flags/4x3/cx.svg)}.flag-icon-cx.flag-icon-squared{background-image:url(../flags/1x1/cx.svg)}.flag-icon-cy{background-image:url(../flags/4x3/cy.svg)}.flag-icon-cy.flag-icon-squared{background-image:url(../flags/1x1/cy.svg)}.flag-icon-cz{background-image:url(../flags/4x3/cz.svg)}.flag-icon-cz.flag-icon-squared{background-image:url(../flags/1x1/cz.svg)}.flag-icon-de{background-image:url(../flags/4x3/de.svg)}.flag-icon-de.flag-icon-squared{background-image:url(../flags/1x1/de.svg)}.flag-icon-dj{background-image:url(../flags/4x3/dj.svg)}.flag-icon-dj.flag-icon-squared{background-image:url(../flags/1x1/dj.svg)}.flag-icon-dk{background-image:url(../flags/4x3/dk.svg)}.flag-icon-dk.flag-icon-squared{background-image:url(../flags/1x1/dk.svg)}.flag-icon-dm{background-image:url(../flags/4x3/dm.svg)}.flag-icon-dm.flag-icon-squared{background-image:url(../flags/1x1/dm.svg)}.flag-icon-do{background-image:url(../flags/4x3/do.svg)}.flag-icon-do.flag-icon-squared{background-image:url(../flags/1x1/do.svg)}.flag-icon-dz{background-image:url(../flags/4x3/dz.svg)}.flag-icon-dz.flag-icon-squared{background-image:url(../flags/1x1/dz.svg)}.flag-icon-ec{background-image:url(../flags/4x3/ec.svg)}.flag-icon-ec.flag-icon-squared{background-image:url(../flags/1x1/ec.svg)}.flag-icon-ee{background-image:url(../flags/4x3/ee.svg)}.flag-icon-ee.flag-icon-squared{background-image:url(../flags/1x1/ee.svg)}.flag-icon-eg{background-image:url(../flags/4x3/eg.svg)}.flag-icon-eg.flag-icon-squared{background-image:url(../flags/1x1/eg.svg)}.flag-icon-eh{background-image:url(../flags/4x3/eh.svg)}.flag-icon-eh.flag-icon-squared{background-image:url(../flags/1x1/eh.svg)}.flag-icon-er{background-image:url(../flags/4x3/er.svg)}.flag-icon-er.flag-icon-squared{background-image:url(../flags/1x1/er.svg)}.flag-icon-es{background-image:url(../flags/4x3/es.svg)}.flag-icon-es.flag-icon-squared{background-image:url(../flags/1x1/es.svg)}.flag-icon-et{background-image:url(../flags/4x3/et.svg)}.flag-icon-et.flag-icon-squared{background-image:url(../flags/1x1/et.svg)}.flag-icon-fi{background-image:url(../flags/4x3/fi.svg)}.flag-icon-fi.flag-icon-squared{background-image:url(../flags/1x1/fi.svg)}.flag-icon-fj{background-image:url(../flags/4x3/fj.svg)}.flag-icon-fj.flag-icon-squared{background-image:url(../flags/1x1/fj.svg)}.flag-icon-fk{background-image:url(../flags/4x3/fk.svg)}.flag-icon-fk.flag-icon-squared{background-image:url(../flags/1x1/fk.svg)}.flag-icon-fm{background-image:url(../flags/4x3/fm.svg)}.flag-icon-fm.flag-icon-squared{background-image:url(../flags/1x1/fm.svg)}.flag-icon-fo{background-image:url(../flags/4x3/fo.svg)}.flag-icon-fo.flag-icon-squared{background-image:url(../flags/1x1/fo.svg)}.flag-icon-fr{background-image:url(../flags/4x3/fr.svg)}.flag-icon-fr.flag-icon-squared{background-image:url(../flags/1x1/fr.svg)}.flag-icon-ga{background-image:url(../flags/4x3/ga.svg)}.flag-icon-ga.flag-icon-squared{background-image:url(../flags/1x1/ga.svg)}.flag-icon-gb{background-image:url(../flags/4x3/gb.svg)}.flag-icon-gb.flag-icon-squared{background-image:url(../flags/1x1/gb.svg)}.flag-icon-gd{background-image:url(../flags/4x3/gd.svg)}.flag-icon-gd.flag-icon-squared{background-image:url(../flags/1x1/gd.svg)}.flag-icon-ge{background-image:url(../flags/4x3/ge.svg)}.flag-icon-ge.flag-icon-squared{background-image:url(../flags/1x1/ge.svg)}.flag-icon-gf{background-image:url(../flags/4x3/gf.svg)}.flag-icon-gf.flag-icon-squared{background-image:url(../flags/1x1/gf.svg)}.flag-icon-gg{background-image:url(../flags/4x3/gg.svg)}.flag-icon-gg.flag-icon-squared{background-image:url(../flags/1x1/gg.svg)}.flag-icon-gh{background-image:url(../flags/4x3/gh.svg)}.flag-icon-gh.flag-icon-squared{background-image:url(../flags/1x1/gh.svg)}.flag-icon-gi{background-image:url(../flags/4x3/gi.svg)}.flag-icon-gi.flag-icon-squared{background-image:url(../flags/1x1/gi.svg)}.flag-icon-gl{background-image:url(../flags/4x3/gl.svg)}.flag-icon-gl.flag-icon-squared{background-image:url(../flags/1x1/gl.svg)}.flag-icon-gm{background-image:url(../flags/4x3/gm.svg)}.flag-icon-gm.flag-icon-squared{background-image:url(../flags/1x1/gm.svg)}.flag-icon-gn{background-image:url(../flags/4x3/gn.svg)}.flag-icon-gn.flag-icon-squared{background-image:url(../flags/1x1/gn.svg)}.flag-icon-gp{background-image:url(../flags/4x3/gp.svg)}.flag-icon-gp.flag-icon-squared{background-image:url(../flags/1x1/gp.svg)}.flag-icon-gq{background-image:url(../flags/4x3/gq.svg)}.flag-icon-gq.flag-icon-squared{background-image:url(../flags/1x1/gq.svg)}.flag-icon-gr{background-image:url(../flags/4x3/gr.svg)}.flag-icon-gr.flag-icon-squared{background-image:url(../flags/1x1/gr.svg)}.flag-icon-gs{background-image:url(../flags/4x3/gs.svg)}.flag-icon-gs.flag-icon-squared{background-image:url(../flags/1x1/gs.svg)}.flag-icon-gt{background-image:url(../flags/4x3/gt.svg)}.flag-icon-gt.flag-icon-squared{background-image:url(../flags/1x1/gt.svg)}.flag-icon-gu{background-image:url(../flags/4x3/gu.svg)}.flag-icon-gu.flag-icon-squared{background-image:url(../flags/1x1/gu.svg)}.flag-icon-gw{background-image:url(../flags/4x3/gw.svg)}.flag-icon-gw.flag-icon-squared{background-image:url(../flags/1x1/gw.svg)}.flag-icon-gy{background-image:url(../flags/4x3/gy.svg)}.flag-icon-gy.flag-icon-squared{background-image:url(../flags/1x1/gy.svg)}.flag-icon-hk{background-image:url(../flags/4x3/hk.svg)}.flag-icon-hk.flag-icon-squared{background-image:url(../flags/1x1/hk.svg)}.flag-icon-hm{background-image:url(../flags/4x3/hm.svg)}.flag-icon-hm.flag-icon-squared{background-image:url(../flags/1x1/hm.svg)}.flag-icon-hn{background-image:url(../flags/4x3/hn.svg)}.flag-icon-hn.flag-icon-squared{background-image:url(../flags/1x1/hn.svg)}.flag-icon-hr{background-image:url(../flags/4x3/hr.svg)}.flag-icon-hr.flag-icon-squared{background-image:url(../flags/1x1/hr.svg)}.flag-icon-ht{background-image:url(../flags/4x3/ht.svg)}.flag-icon-ht.flag-icon-squared{background-image:url(../flags/1x1/ht.svg)}.flag-icon-hu{background-image:url(../flags/4x3/hu.svg)}.flag-icon-hu.flag-icon-squared{background-image:url(../flags/1x1/hu.svg)}.flag-icon-id{background-image:url(../flags/4x3/id.svg)}.flag-icon-id.flag-icon-squared{background-image:url(../flags/1x1/id.svg)}.flag-icon-ie{background-image:url(../flags/4x3/ie.svg)}.flag-icon-ie.flag-icon-squared{background-image:url(../flags/1x1/ie.svg)}.flag-icon-il{background-image:url(../flags/4x3/il.svg)}.flag-icon-il.flag-icon-squared{background-image:url(../flags/1x1/il.svg)}.flag-icon-im{background-image:url(../flags/4x3/im.svg)}.flag-icon-im.flag-icon-squared{background-image:url(../flags/1x1/im.svg)}.flag-icon-in{background-image:url(../flags/4x3/in.svg)}.flag-icon-in.flag-icon-squared{background-image:url(../flags/1x1/in.svg)}.flag-icon-io{background-image:url(../flags/4x3/io.svg)}.flag-icon-io.flag-icon-squared{background-image:url(../flags/1x1/io.svg)}.flag-icon-iq{background-image:url(../flags/4x3/iq.svg)}.flag-icon-iq.flag-icon-squared{background-image:url(../flags/1x1/iq.svg)}.flag-icon-ir{background-image:url(../flags/4x3/ir.svg)}.flag-icon-ir.flag-icon-squared{background-image:url(../flags/1x1/ir.svg)}.flag-icon-is{background-image:url(../flags/4x3/is.svg)}.flag-icon-is.flag-icon-squared{background-image:url(../flags/1x1/is.svg)}.flag-icon-it{background-image:url(../flags/4x3/it.svg)}.flag-icon-it.flag-icon-squared{background-image:url(../flags/1x1/it.svg)}.flag-icon-je{background-image:url(../flags/4x3/je.svg)}.flag-icon-je.flag-icon-squared{background-image:url(../flags/1x1/je.svg)}.flag-icon-jm{background-image:url(../flags/4x3/jm.svg)}.flag-icon-jm.flag-icon-squared{background-image:url(../flags/1x1/jm.svg)}.flag-icon-jo{background-image:url(../flags/4x3/jo.svg)}.flag-icon-jo.flag-icon-squared{background-image:url(../flags/1x1/jo.svg)}.flag-icon-jp{background-image:url(../flags/4x3/jp.svg)}.flag-icon-jp.flag-icon-squared{background-image:url(../flags/1x1/jp.svg)}.flag-icon-ke{background-image:url(../flags/4x3/ke.svg)}.flag-icon-ke.flag-icon-squared{background-image:url(../flags/1x1/ke.svg)}.flag-icon-kg{background-image:url(../flags/4x3/kg.svg)}.flag-icon-kg.flag-icon-squared{background-image:url(../flags/1x1/kg.svg)}.flag-icon-kh{background-image:url(../flags/4x3/kh.svg)}.flag-icon-kh.flag-icon-squared{background-image:url(../flags/1x1/kh.svg)}.flag-icon-ki{background-image:url(../flags/4x3/ki.svg)}.flag-icon-ki.flag-icon-squared{background-image:url(../flags/1x1/ki.svg)}.flag-icon-km{background-image:url(../flags/4x3/km.svg)}.flag-icon-km.flag-icon-squared{background-image:url(../flags/1x1/km.svg)}.flag-icon-kn{background-image:url(../flags/4x3/kn.svg)}.flag-icon-kn.flag-icon-squared{background-image:url(../flags/1x1/kn.svg)}.flag-icon-kp{background-image:url(../flags/4x3/kp.svg)}.flag-icon-kp.flag-icon-squared{background-image:url(../flags/1x1/kp.svg)}.flag-icon-kr{background-image:url(../flags/4x3/kr.svg)}.flag-icon-kr.flag-icon-squared{background-image:url(../flags/1x1/kr.svg)}.flag-icon-kw{background-image:url(../flags/4x3/kw.svg)}.flag-icon-kw.flag-icon-squared{background-image:url(../flags/1x1/kw.svg)}.flag-icon-ky{background-image:url(../flags/4x3/ky.svg)}.flag-icon-ky.flag-icon-squared{background-image:url(../flags/1x1/ky.svg)}.flag-icon-kz{background-image:url(../flags/4x3/kz.svg)}.flag-icon-kz.flag-icon-squared{background-image:url(../flags/1x1/kz.svg)}.flag-icon-la{background-image:url(../flags/4x3/la.svg)}.flag-icon-la.flag-icon-squared{background-image:url(../flags/1x1/la.svg)}.flag-icon-lb{background-image:url(../flags/4x3/lb.svg)}.flag-icon-lb.flag-icon-squared{background-image:url(../flags/1x1/lb.svg)}.flag-icon-lc{background-image:url(../flags/4x3/lc.svg)}.flag-icon-lc.flag-icon-squared{background-image:url(../flags/1x1/lc.svg)}.flag-icon-li{background-image:url(../flags/4x3/li.svg)}.flag-icon-li.flag-icon-squared{background-image:url(../flags/1x1/li.svg)}.flag-icon-lk{background-image:url(../flags/4x3/lk.svg)}.flag-icon-lk.flag-icon-squared{background-image:url(../flags/1x1/lk.svg)}.flag-icon-lr{background-image:url(../flags/4x3/lr.svg)}.flag-icon-lr.flag-icon-squared{background-image:url(../flags/1x1/lr.svg)}.flag-icon-ls{background-image:url(../flags/4x3/ls.svg)}.flag-icon-ls.flag-icon-squared{background-image:url(../flags/1x1/ls.svg)}.flag-icon-lt{background-image:url(../flags/4x3/lt.svg)}.flag-icon-lt.flag-icon-squared{background-image:url(../flags/1x1/lt.svg)}.flag-icon-lu{background-image:url(../flags/4x3/lu.svg)}.flag-icon-lu.flag-icon-squared{background-image:url(../flags/1x1/lu.svg)}.flag-icon-lv{background-image:url(../flags/4x3/lv.svg)}.flag-icon-lv.flag-icon-squared{background-image:url(../flags/1x1/lv.svg)}.flag-icon-ly{background-image:url(../flags/4x3/ly.svg)}.flag-icon-ly.flag-icon-squared{background-image:url(../flags/1x1/ly.svg)}.flag-icon-ma{background-image:url(../flags/4x3/ma.svg)}.flag-icon-ma.flag-icon-squared{background-image:url(../flags/1x1/ma.svg)}.flag-icon-mc{background-image:url(../flags/4x3/mc.svg)}.flag-icon-mc.flag-icon-squared{background-image:url(../flags/1x1/mc.svg)}.flag-icon-md{background-image:url(../flags/4x3/md.svg)}.flag-icon-md.flag-icon-squared{background-image:url(../flags/1x1/md.svg)}.flag-icon-me{background-image:url(../flags/4x3/me.svg)}.flag-icon-me.flag-icon-squared{background-image:url(../flags/1x1/me.svg)}.flag-icon-mf{background-image:url(../flags/4x3/mf.svg)}.flag-icon-mf.flag-icon-squared{background-image:url(../flags/1x1/mf.svg)}.flag-icon-mg{background-image:url(../flags/4x3/mg.svg)}.flag-icon-mg.flag-icon-squared{background-image:url(../flags/1x1/mg.svg)}.flag-icon-mh{background-image:url(../flags/4x3/mh.svg)}.flag-icon-mh.flag-icon-squared{background-image:url(../flags/1x1/mh.svg)}.flag-icon-mk{background-image:url(../flags/4x3/mk.svg)}.flag-icon-mk.flag-icon-squared{background-image:url(../flags/1x1/mk.svg)}.flag-icon-ml{background-image:url(../flags/4x3/ml.svg)}.flag-icon-ml.flag-icon-squared{background-image:url(../flags/1x1/ml.svg)}.flag-icon-mm{background-image:url(../flags/4x3/mm.svg)}.flag-icon-mm.flag-icon-squared{background-image:url(../flags/1x1/mm.svg)}.flag-icon-mn{background-image:url(../flags/4x3/mn.svg)}.flag-icon-mn.flag-icon-squared{background-image:url(../flags/1x1/mn.svg)}.flag-icon-mo{background-image:url(../flags/4x3/mo.svg)}.flag-icon-mo.flag-icon-squared{background-image:url(../flags/1x1/mo.svg)}.flag-icon-mp{background-image:url(../flags/4x3/mp.svg)}.flag-icon-mp.flag-icon-squared{background-image:url(../flags/1x1/mp.svg)}.flag-icon-mq{background-image:url(../flags/4x3/mq.svg)}.flag-icon-mq.flag-icon-squared{background-image:url(../flags/1x1/mq.svg)}.flag-icon-mr{background-image:url(../flags/4x3/mr.svg)}.flag-icon-mr.flag-icon-squared{background-image:url(../flags/1x1/mr.svg)}.flag-icon-ms{background-image:url(../flags/4x3/ms.svg)}.flag-icon-ms.flag-icon-squared{background-image:url(../flags/1x1/ms.svg)}.flag-icon-mt{background-image:url(../flags/4x3/mt.svg)}.flag-icon-mt.flag-icon-squared{background-image:url(../flags/1x1/mt.svg)}.flag-icon-mu{background-image:url(../flags/4x3/mu.svg)}.flag-icon-mu.flag-icon-squared{background-image:url(../flags/1x1/mu.svg)}.flag-icon-mv{background-image:url(../flags/4x3/mv.svg)}.flag-icon-mv.flag-icon-squared{background-image:url(../flags/1x1/mv.svg)}.flag-icon-mw{background-image:url(../flags/4x3/mw.svg)}.flag-icon-mw.flag-icon-squared{background-image:url(../flags/1x1/mw.svg)}.flag-icon-mx{background-image:url(../flags/4x3/mx.svg)}.flag-icon-mx.flag-icon-squared{background-image:url(../flags/1x1/mx.svg)}.flag-icon-my{background-image:url(../flags/4x3/my.svg)}.flag-icon-my.flag-icon-squared{background-image:url(../flags/1x1/my.svg)}.flag-icon-mz{background-image:url(../flags/4x3/mz.svg)}.flag-icon-mz.flag-icon-squared{background-image:url(../flags/1x1/mz.svg)}.flag-icon-na{background-image:url(../flags/4x3/na.svg)}.flag-icon-na.flag-icon-squared{background-image:url(../flags/1x1/na.svg)}.flag-icon-nc{background-image:url(../flags/4x3/nc.svg)}.flag-icon-nc.flag-icon-squared{background-image:url(../flags/1x1/nc.svg)}.flag-icon-ne{background-image:url(../flags/4x3/ne.svg)}.flag-icon-ne.flag-icon-squared{background-image:url(../flags/1x1/ne.svg)}.flag-icon-nf{background-image:url(../flags/4x3/nf.svg)}.flag-icon-nf.flag-icon-squared{background-image:url(../flags/1x1/nf.svg)}.flag-icon-ng{background-image:url(../flags/4x3/ng.svg)}.flag-icon-ng.flag-icon-squared{background-image:url(../flags/1x1/ng.svg)}.flag-icon-ni{background-image:url(../flags/4x3/ni.svg)}.flag-icon-ni.flag-icon-squared{background-image:url(../flags/1x1/ni.svg)}.flag-icon-nl{background-image:url(../flags/4x3/nl.svg)}.flag-icon-nl.flag-icon-squared{background-image:url(../flags/1x1/nl.svg)}.flag-icon-no{background-image:url(../flags/4x3/no.svg)}.flag-icon-no.flag-icon-squared{background-image:url(../flags/1x1/no.svg)}.flag-icon-np{background-image:url(../flags/4x3/np.svg)}.flag-icon-np.flag-icon-squared{background-image:url(../flags/1x1/np.svg)}.flag-icon-nr{background-image:url(../flags/4x3/nr.svg)}.flag-icon-nr.flag-icon-squared{background-image:url(../flags/1x1/nr.svg)}.flag-icon-nu{background-image:url(../flags/4x3/nu.svg)}.flag-icon-nu.flag-icon-squared{background-image:url(../flags/1x1/nu.svg)}.flag-icon-nz{background-image:url(../flags/4x3/nz.svg)}.flag-icon-nz.flag-icon-squared{background-image:url(../flags/1x1/nz.svg)}.flag-icon-om{background-image:url(../flags/4x3/om.svg)}.flag-icon-om.flag-icon-squared{background-image:url(../flags/1x1/om.svg)}.flag-icon-pa{background-image:url(../flags/4x3/pa.svg)}.flag-icon-pa.flag-icon-squared{background-image:url(../flags/1x1/pa.svg)}.flag-icon-pe{background-image:url(../flags/4x3/pe.svg)}.flag-icon-pe.flag-icon-squared{background-image:url(../flags/1x1/pe.svg)}.flag-icon-pf{background-image:url(../flags/4x3/pf.svg)}.flag-icon-pf.flag-icon-squared{background-image:url(../flags/1x1/pf.svg)}.flag-icon-pg{background-image:url(../flags/4x3/pg.svg)}.flag-icon-pg.flag-icon-squared{background-image:url(../flags/1x1/pg.svg)}.flag-icon-ph{background-image:url(../flags/4x3/ph.svg)}.flag-icon-ph.flag-icon-squared{background-image:url(../flags/1x1/ph.svg)}.flag-icon-pk{background-image:url(../flags/4x3/pk.svg)}.flag-icon-pk.flag-icon-squared{background-image:url(../flags/1x1/pk.svg)}.flag-icon-pl{background-image:url(../flags/4x3/pl.svg)}.flag-icon-pl.flag-icon-squared{background-image:url(../flags/1x1/pl.svg)}.flag-icon-pm{background-image:url(../flags/4x3/pm.svg)}.flag-icon-pm.flag-icon-squared{background-image:url(../flags/1x1/pm.svg)}.flag-icon-pn{background-image:url(../flags/4x3/pn.svg)}.flag-icon-pn.flag-icon-squared{background-image:url(../flags/1x1/pn.svg)}.flag-icon-pr{background-image:url(../flags/4x3/pr.svg)}.flag-icon-pr.flag-icon-squared{background-image:url(../flags/1x1/pr.svg)}.flag-icon-ps{background-image:url(../flags/4x3/ps.svg)}.flag-icon-ps.flag-icon-squared{background-image:url(../flags/1x1/ps.svg)}.flag-icon-pt{background-image:url(../flags/4x3/pt.svg)}.flag-icon-pt.flag-icon-squared{background-image:url(../flags/1x1/pt.svg)}.flag-icon-pw{background-image:url(../flags/4x3/pw.svg)}.flag-icon-pw.flag-icon-squared{background-image:url(../flags/1x1/pw.svg)}.flag-icon-py{background-image:url(../flags/4x3/py.svg)}.flag-icon-py.flag-icon-squared{background-image:url(../flags/1x1/py.svg)}.flag-icon-qa{background-image:url(../flags/4x3/qa.svg)}.flag-icon-qa.flag-icon-squared{background-image:url(../flags/1x1/qa.svg)}.flag-icon-re{background-image:url(../flags/4x3/re.svg)}.flag-icon-re.flag-icon-squared{background-image:url(../flags/1x1/re.svg)}.flag-icon-ro{background-image:url(../flags/4x3/ro.svg)}.flag-icon-ro.flag-icon-squared{background-image:url(../flags/1x1/ro.svg)}.flag-icon-rs{background-image:url(../flags/4x3/rs.svg)}.flag-icon-rs.flag-icon-squared{background-image:url(../flags/1x1/rs.svg)}.flag-icon-ru{background-image:url(../flags/4x3/ru.svg)}.flag-icon-ru.flag-icon-squared{background-image:url(../flags/1x1/ru.svg)}.flag-icon-rw{background-image:url(../flags/4x3/rw.svg)}.flag-icon-rw.flag-icon-squared{background-image:url(../flags/1x1/rw.svg)}.flag-icon-sa{background-image:url(../flags/4x3/sa.svg)}.flag-icon-sa.flag-icon-squared{background-image:url(../flags/1x1/sa.svg)}.flag-icon-sb{background-image:url(../flags/4x3/sb.svg)}.flag-icon-sb.flag-icon-squared{background-image:url(../flags/1x1/sb.svg)}.flag-icon-sc{background-image:url(../flags/4x3/sc.svg)}.flag-icon-sc.flag-icon-squared{background-image:url(../flags/1x1/sc.svg)}.flag-icon-sd{background-image:url(../flags/4x3/sd.svg)}.flag-icon-sd.flag-icon-squared{background-image:url(../flags/1x1/sd.svg)}.flag-icon-se{background-image:url(../flags/4x3/se.svg)}.flag-icon-se.flag-icon-squared{background-image:url(../flags/1x1/se.svg)}.flag-icon-sg{background-image:url(../flags/4x3/sg.svg)}.flag-icon-sg.flag-icon-squared{background-image:url(../flags/1x1/sg.svg)}.flag-icon-sh{background-image:url(../flags/4x3/sh.svg)}.flag-icon-sh.flag-icon-squared{background-image:url(../flags/1x1/sh.svg)}.flag-icon-si{background-image:url(../flags/4x3/si.svg)}.flag-icon-si.flag-icon-squared{background-image:url(../flags/1x1/si.svg)}.flag-icon-sj{background-image:url(../flags/4x3/sj.svg)}.flag-icon-sj.flag-icon-squared{background-image:url(../flags/1x1/sj.svg)}.flag-icon-sk{background-image:url(../flags/4x3/sk.svg)}.flag-icon-sk.flag-icon-squared{background-image:url(../flags/1x1/sk.svg)}.flag-icon-sl{background-image:url(../flags/4x3/sl.svg)}.flag-icon-sl.flag-icon-squared{background-image:url(../flags/1x1/sl.svg)}.flag-icon-sm{background-image:url(../flags/4x3/sm.svg)}.flag-icon-sm.flag-icon-squared{background-image:url(../flags/1x1/sm.svg)}.flag-icon-sn{background-image:url(../flags/4x3/sn.svg)}.flag-icon-sn.flag-icon-squared{background-image:url(../flags/1x1/sn.svg)}.flag-icon-so{background-image:url(../flags/4x3/so.svg)}.flag-icon-so.flag-icon-squared{background-image:url(../flags/1x1/so.svg)}.flag-icon-sr{background-image:url(../flags/4x3/sr.svg)}.flag-icon-sr.flag-icon-squared{background-image:url(../flags/1x1/sr.svg)}.flag-icon-ss{background-image:url(../flags/4x3/ss.svg)}.flag-icon-ss.flag-icon-squared{background-image:url(../flags/1x1/ss.svg)}.flag-icon-st{background-image:url(../flags/4x3/st.svg)}.flag-icon-st.flag-icon-squared{background-image:url(../flags/1x1/st.svg)}.flag-icon-sv{background-image:url(../flags/4x3/sv.svg)}.flag-icon-sv.flag-icon-squared{background-image:url(../flags/1x1/sv.svg)}.flag-icon-sx{background-image:url(../flags/4x3/sx.svg)}.flag-icon-sx.flag-icon-squared{background-image:url(../flags/1x1/sx.svg)}.flag-icon-sy{background-image:url(../flags/4x3/sy.svg)}.flag-icon-sy.flag-icon-squared{background-image:url(../flags/1x1/sy.svg)}.flag-icon-sz{background-image:url(../flags/4x3/sz.svg)}.flag-icon-sz.flag-icon-squared{background-image:url(../flags/1x1/sz.svg)}.flag-icon-tc{background-image:url(../flags/4x3/tc.svg)}.flag-icon-tc.flag-icon-squared{background-image:url(../flags/1x1/tc.svg)}.flag-icon-td{background-image:url(../flags/4x3/td.svg)}.flag-icon-td.flag-icon-squared{background-image:url(../flags/1x1/td.svg)}.flag-icon-tf{background-image:url(../flags/4x3/tf.svg)}.flag-icon-tf.flag-icon-squared{background-image:url(../flags/1x1/tf.svg)}.flag-icon-tg{background-image:url(../flags/4x3/tg.svg)}.flag-icon-tg.flag-icon-squared{background-image:url(../flags/1x1/tg.svg)}.flag-icon-th{background-image:url(../flags/4x3/th.svg)}.flag-icon-th.flag-icon-squared{background-image:url(../flags/1x1/th.svg)}.flag-icon-tj{background-image:url(../flags/4x3/tj.svg)}.flag-icon-tj.flag-icon-squared{background-image:url(../flags/1x1/tj.svg)}.flag-icon-tk{background-image:url(../flags/4x3/tk.svg)}.flag-icon-tk.flag-icon-squared{background-image:url(../flags/1x1/tk.svg)}.flag-icon-tl{background-image:url(../flags/4x3/tl.svg)}.flag-icon-tl.flag-icon-squared{background-image:url(../flags/1x1/tl.svg)}.flag-icon-tm{background-image:url(../flags/4x3/tm.svg)}.flag-icon-tm.flag-icon-squared{background-image:url(../flags/1x1/tm.svg)}.flag-icon-tn{background-image:url(../flags/4x3/tn.svg)}.flag-icon-tn.flag-icon-squared{background-image:url(../flags/1x1/tn.svg)}.flag-icon-to{background-image:url(../flags/4x3/to.svg)}.flag-icon-to.flag-icon-squared{background-image:url(../flags/1x1/to.svg)}.flag-icon-tr{background-image:url(../flags/4x3/tr.svg)}.flag-icon-tr.flag-icon-squared{background-image:url(../flags/1x1/tr.svg)}.flag-icon-tt{background-image:url(../flags/4x3/tt.svg)}.flag-icon-tt.flag-icon-squared{background-image:url(../flags/1x1/tt.svg)}.flag-icon-tv{background-image:url(../flags/4x3/tv.svg)}.flag-icon-tv.flag-icon-squared{background-image:url(../flags/1x1/tv.svg)}.flag-icon-tw{background-image:url(../flags/4x3/tw.svg)}.flag-icon-tw.flag-icon-squared{background-image:url(../flags/1x1/tw.svg)}.flag-icon-tz{background-image:url(../flags/4x3/tz.svg)}.flag-icon-tz.flag-icon-squared{background-image:url(../flags/1x1/tz.svg)}.flag-icon-ua{background-image:url(../flags/4x3/ua.svg)}.flag-icon-ua.flag-icon-squared{background-image:url(../flags/1x1/ua.svg)}.flag-icon-ug{background-image:url(../flags/4x3/ug.svg)}.flag-icon-ug.flag-icon-squared{background-image:url(../flags/1x1/ug.svg)}.flag-icon-um{background-image:url(../flags/4x3/um.svg)}.flag-icon-um.flag-icon-squared{background-image:url(../flags/1x1/um.svg)}.flag-icon-us{background-image:url(../flags/4x3/us.svg)}.flag-icon-us.flag-icon-squared{background-image:url(../flags/1x1/us.svg)}.flag-icon-uy{background-image:url(../flags/4x3/uy.svg)}.flag-icon-uy.flag-icon-squared{background-image:url(../flags/1x1/uy.svg)}.flag-icon-uz{background-image:url(../flags/4x3/uz.svg)}.flag-icon-uz.flag-icon-squared{background-image:url(../flags/1x1/uz.svg)}.flag-icon-va{background-image:url(../flags/4x3/va.svg)}.flag-icon-va.flag-icon-squared{background-image:url(../flags/1x1/va.svg)}.flag-icon-vc{background-image:url(../flags/4x3/vc.svg)}.flag-icon-vc.flag-icon-squared{background-image:url(../flags/1x1/vc.svg)}.flag-icon-ve{background-image:url(../flags/4x3/ve.svg)}.flag-icon-ve.flag-icon-squared{background-image:url(../flags/1x1/ve.svg)}.flag-icon-vg{background-image:url(../flags/4x3/vg.svg)}.flag-icon-vg.flag-icon-squared{background-image:url(../flags/1x1/vg.svg)}.flag-icon-vi{background-image:url(../flags/4x3/vi.svg)}.flag-icon-vi.flag-icon-squared{background-image:url(../flags/1x1/vi.svg)}.flag-icon-vn{background-image:url(../flags/4x3/vn.svg)}.flag-icon-vn.flag-icon-squared{background-image:url(../flags/1x1/vn.svg)}.flag-icon-vu{background-image:url(../flags/4x3/vu.svg)}.flag-icon-vu.flag-icon-squared{background-image:url(../flags/1x1/vu.svg)}.flag-icon-wf{background-image:url(../flags/4x3/wf.svg)}.flag-icon-wf.flag-icon-squared{background-image:url(../flags/1x1/wf.svg)}.flag-icon-ws{background-image:url(../flags/4x3/ws.svg)}.flag-icon-ws.flag-icon-squared{background-image:url(../flags/1x1/ws.svg)}.flag-icon-ye{background-image:url(../flags/4x3/ye.svg)}.flag-icon-ye.flag-icon-squared{background-image:url(../flags/1x1/ye.svg)}.flag-icon-yt{background-image:url(../flags/4x3/yt.svg)}.flag-icon-yt.flag-icon-squared{background-image:url(../flags/1x1/yt.svg)}.flag-icon-za{background-image:url(../flags/4x3/za.svg)}.flag-icon-za.flag-icon-squared{background-image:url(../flags/1x1/za.svg)}.flag-icon-zm{background-image:url(../flags/4x3/zm.svg)}.flag-icon-zm.flag-icon-squared{background-image:url(../flags/1x1/zm.svg)}.flag-icon-zw{background-image:url(../flags/4x3/zw.svg)}.flag-icon-zw.flag-icon-squared{background-image:url(../flags/1x1/zw.svg)}.flag-icon-es-ca{background-image:url(../flags/4x3/es-ca.svg)}.flag-icon-es-ca.flag-icon-squared{background-image:url(../flags/1x1/es-ca.svg)}.flag-icon-es-ga{background-image:url(../flags/4x3/es-ga.svg)}.flag-icon-es-ga.flag-icon-squared{background-image:url(../flags/1x1/es-ga.svg)}.flag-icon-eu{background-image:url(../flags/4x3/eu.svg)}.flag-icon-eu.flag-icon-squared{background-image:url(../flags/1x1/eu.svg)}.flag-icon-gb-eng{background-image:url(../flags/4x3/gb-eng.svg)}.flag-icon-gb-eng.flag-icon-squared{background-image:url(../flags/1x1/gb-eng.svg)}.flag-icon-gb-nir{background-image:url(../flags/4x3/gb-nir.svg)}.flag-icon-gb-nir.flag-icon-squared{background-image:url(../flags/1x1/gb-nir.svg)}.flag-icon-gb-sct{background-image:url(../flags/4x3/gb-sct.svg)}.flag-icon-gb-sct.flag-icon-squared{background-image:url(../flags/1x1/gb-sct.svg)}.flag-icon-gb-wls{background-image:url(../flags/4x3/gb-wls.svg)}.flag-icon-gb-wls.flag-icon-squared{background-image:url(../flags/1x1/gb-wls.svg)}.flag-icon-un{background-image:url(../flags/4x3/un.svg)}.flag-icon-un.flag-icon-squared{background-image:url(../flags/1x1/un.svg)}.flag-icon-xk{background-image:url(../flags/4x3/xk.svg)}.flag-icon-xk.flag-icon-squared{background-image:url(../flags/1x1/xk.svg)} diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ad.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ad.svg new file mode 100644 index 0000000000..e7fc56abbc --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ad.svg @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ae.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ae.svg new file mode 100644 index 0000000000..739c5d4646 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ae.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/af.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/af.svg new file mode 100644 index 0000000000..90c34b8d5a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/af.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ag.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ag.svg new file mode 100644 index 0000000000..d0b2a83cbe --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ag.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ai.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ai.svg new file mode 100644 index 0000000000..472be200e7 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ai.svg @@ -0,0 +1,763 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/al.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/al.svg new file mode 100644 index 0000000000..75995ecde6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/al.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/am.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/am.svg new file mode 100644 index 0000000000..1198be035f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/am.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ao.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ao.svg new file mode 100644 index 0000000000..a5a25bf107 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ao.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/aq.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/aq.svg new file mode 100644 index 0000000000..80e682abd6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/aq.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ar.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ar.svg new file mode 100644 index 0000000000..1730ecac9e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ar.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/as.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/as.svg new file mode 100644 index 0000000000..b8d8162d0a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/as.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/at.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/at.svg new file mode 100644 index 0000000000..649d6efe0a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/at.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/au.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/au.svg new file mode 100644 index 0000000000..ca5d607ea0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/au.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/aw.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/aw.svg new file mode 100644 index 0000000000..248a08d69b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/aw.svg @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ax.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ax.svg new file mode 100644 index 0000000000..cdeb07e273 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ax.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/az.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/az.svg new file mode 100644 index 0000000000..0119e1ab4e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/az.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ba.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ba.svg new file mode 100644 index 0000000000..5b92b0ab54 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ba.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bb.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bb.svg new file mode 100644 index 0000000000..9d627842f1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bb.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bd.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bd.svg new file mode 100644 index 0000000000..4cb38cf503 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bd.svg @@ -0,0 +1,4 @@ + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/be.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/be.svg new file mode 100644 index 0000000000..01496c3cac --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/be.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bf.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bf.svg new file mode 100644 index 0000000000..a3c7c44a4f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bf.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bg.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bg.svg new file mode 100644 index 0000000000..5abe67f642 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bg.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bh.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bh.svg new file mode 100644 index 0000000000..22fba621a9 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bh.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bi.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bi.svg new file mode 100644 index 0000000000..cc11dcff2c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bi.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bj.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bj.svg new file mode 100644 index 0000000000..07c4c117ca --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bj.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bl.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bl.svg new file mode 100644 index 0000000000..0fa74e1c3c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bl.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bm.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bm.svg new file mode 100644 index 0000000000..a7057d24c0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bm.svg @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bn.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bn.svg new file mode 100644 index 0000000000..2e93aea326 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bn.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bo.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bo.svg new file mode 100644 index 0000000000..52a534fe2b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bo.svg @@ -0,0 +1,678 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bq.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bq.svg new file mode 100644 index 0000000000..cc872ef1b8 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bq.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/br.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/br.svg new file mode 100644 index 0000000000..8353e823fc --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/br.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bs.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bs.svg new file mode 100644 index 0000000000..decdebb1ef --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bs.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bt.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bt.svg new file mode 100644 index 0000000000..3bbbfaac74 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bt.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bv.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bv.svg new file mode 100644 index 0000000000..01c9ee147d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bv.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bw.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bw.svg new file mode 100644 index 0000000000..0bc5d3d34f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bw.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/by.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/by.svg new file mode 100644 index 0000000000..73e14f7a23 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/by.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bz.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bz.svg new file mode 100644 index 0000000000..0e9a27ca6a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/bz.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ca.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ca.svg new file mode 100644 index 0000000000..6882f6da65 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ca.svg @@ -0,0 +1,4 @@ + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cc.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cc.svg new file mode 100644 index 0000000000..dd6e20003e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cc.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cd.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cd.svg new file mode 100644 index 0000000000..5da2a96892 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cd.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cf.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cf.svg new file mode 100644 index 0000000000..e924621528 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cf.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cg.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cg.svg new file mode 100644 index 0000000000..a52ba7e211 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cg.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ch.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ch.svg new file mode 100644 index 0000000000..773cdc8a3e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ch.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ci.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ci.svg new file mode 100644 index 0000000000..bd1e3f4141 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ci.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ck.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ck.svg new file mode 100644 index 0000000000..f2df0dbe34 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ck.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cl.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cl.svg new file mode 100644 index 0000000000..b808896721 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cl.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cm.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cm.svg new file mode 100644 index 0000000000..08b710bb87 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cm.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cn.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cn.svg new file mode 100644 index 0000000000..7873c1b4fa --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cn.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/co.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/co.svg new file mode 100644 index 0000000000..18d1c5f450 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/co.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cr.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cr.svg new file mode 100644 index 0000000000..a60a6dd614 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cr.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cu.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cu.svg new file mode 100644 index 0000000000..396817620d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cu.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cv.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cv.svg new file mode 100644 index 0000000000..a8311b2f83 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cv.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cw.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cw.svg new file mode 100644 index 0000000000..d7ba21865b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cw.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cx.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cx.svg new file mode 100644 index 0000000000..ef82c45363 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cx.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cy.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cy.svg new file mode 100644 index 0000000000..ba2b0f8908 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cy.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cz.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cz.svg new file mode 100644 index 0000000000..9557b6e88d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/cz.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/de.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/de.svg new file mode 100644 index 0000000000..b9ea8a6134 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/de.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dj.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dj.svg new file mode 100644 index 0000000000..3f6b2e4f35 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dj.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dk.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dk.svg new file mode 100644 index 0000000000..51ff69feb0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dk.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dm.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dm.svg new file mode 100644 index 0000000000..405a4b6b96 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dm.svg @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/do.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/do.svg new file mode 100644 index 0000000000..03d3f3547b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/do.svg @@ -0,0 +1,6745 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dz.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dz.svg new file mode 100644 index 0000000000..37df0c8bba --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/dz.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ec.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ec.svg new file mode 100644 index 0000000000..65fd0bad2e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ec.svg @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ee.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ee.svg new file mode 100644 index 0000000000..fbc9e33943 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ee.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/eg.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/eg.svg new file mode 100644 index 0000000000..2965b6afa0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/eg.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/eh.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/eh.svg new file mode 100644 index 0000000000..4c3feba1b3 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/eh.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/er.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/er.svg new file mode 100644 index 0000000000..86343349e3 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/er.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/es-ca.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/es-ca.svg new file mode 100644 index 0000000000..2a50685ddb --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/es-ca.svg @@ -0,0 +1,4 @@ + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/es-ga.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/es-ga.svg new file mode 100644 index 0000000000..5c55ff855b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/es-ga.svg @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/es.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/es.svg new file mode 100644 index 0000000000..d7030eb292 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/es.svg @@ -0,0 +1,547 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/et.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/et.svg new file mode 100644 index 0000000000..8b02f6b7ca --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/et.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/eu.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/eu.svg new file mode 100644 index 0000000000..b031d2d336 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/eu.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fi.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fi.svg new file mode 100644 index 0000000000..aff1304c6b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fi.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fj.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fj.svg new file mode 100644 index 0000000000..9d9c3029e1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fj.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fk.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fk.svg new file mode 100644 index 0000000000..12a34c46dc --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fk.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fm.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fm.svg new file mode 100644 index 0000000000..791fde9929 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fm.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fo.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fo.svg new file mode 100644 index 0000000000..b28915c013 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fr.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fr.svg new file mode 100644 index 0000000000..f8e3ca0d57 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/fr.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ga.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ga.svg new file mode 100644 index 0000000000..16c81b3029 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ga.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-eng.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-eng.svg new file mode 100644 index 0000000000..18026d2941 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-eng.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-nir.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-nir.svg new file mode 100644 index 0000000000..6d8a3a3974 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-nir.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-sct.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-sct.svg new file mode 100644 index 0000000000..6987b0886b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-sct.svg @@ -0,0 +1,4 @@ + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-wls.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-wls.svg new file mode 100644 index 0000000000..3931a17954 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb-wls.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb.svg new file mode 100644 index 0000000000..ef048dc2a1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gb.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gd.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gd.svg new file mode 100644 index 0000000000..cca37ba483 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gd.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ge.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ge.svg new file mode 100644 index 0000000000..ac1d87bb49 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ge.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gf.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gf.svg new file mode 100644 index 0000000000..c00a576342 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gf.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gg.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gg.svg new file mode 100644 index 0000000000..2d06a9f86f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gg.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gh.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gh.svg new file mode 100644 index 0000000000..4b6446d645 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gh.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gi.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gi.svg new file mode 100644 index 0000000000..39f5b2770c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gi.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gl.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gl.svg new file mode 100644 index 0000000000..7a026d995f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gl.svg @@ -0,0 +1,4 @@ + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gm.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gm.svg new file mode 100644 index 0000000000..b06ab6cb58 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gm.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gn.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gn.svg new file mode 100644 index 0000000000..8f8855da4b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gn.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gp.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gp.svg new file mode 100644 index 0000000000..0a5bdb0056 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gp.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gq.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gq.svg new file mode 100644 index 0000000000..8149406de4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gq.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gr.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gr.svg new file mode 100644 index 0000000000..4bc68fc1fa --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gr.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gs.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gs.svg new file mode 100644 index 0000000000..48f68b8b67 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gs.svg @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + L + + + E + + + O + + + T + + + E + + + R + + + R + + + R + + + R + + + R + + + E + + + O + + + O + + + A + + + A + + + A + + + M + + + P + + + P + + + P + + + I + + + T + + + T + + + M + + + G + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gt.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gt.svg new file mode 100644 index 0000000000..761801ca47 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gt.svg @@ -0,0 +1,204 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gu.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gu.svg new file mode 100644 index 0000000000..d6f5d535ca --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gu.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + G + + + U + + + A + + + M + + + + + + + + G + + + U + + + A + + + M + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gw.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gw.svg new file mode 100644 index 0000000000..064a593473 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gw.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gy.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gy.svg new file mode 100644 index 0000000000..57eb520903 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/gy.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hk.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hk.svg new file mode 100644 index 0000000000..024c070555 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hk.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hm.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hm.svg new file mode 100644 index 0000000000..7e1f7e7e64 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hm.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hn.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hn.svg new file mode 100644 index 0000000000..c7c4c4acfd --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hn.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hr.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hr.svg new file mode 100644 index 0000000000..7ea00410d5 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hr.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ht.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ht.svg new file mode 100644 index 0000000000..920833a66e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ht.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hu.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hu.svg new file mode 100644 index 0000000000..94bc29f134 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/hu.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/id.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/id.svg new file mode 100644 index 0000000000..6d2cf0941e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/id.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ie.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ie.svg new file mode 100644 index 0000000000..60448a9ded --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ie.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/il.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/il.svg new file mode 100644 index 0000000000..6cb4b1c1f7 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/il.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/im.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/im.svg new file mode 100644 index 0000000000..0f487f67d7 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/im.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/in.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/in.svg new file mode 100644 index 0000000000..e6557cd01a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/in.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/io.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/io.svg new file mode 100644 index 0000000000..4d809e03e6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/io.svg @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/iq.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/iq.svg new file mode 100644 index 0000000000..6b96774d97 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/iq.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ir.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ir.svg new file mode 100644 index 0000000000..79f66324f1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ir.svg @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/is.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/is.svg new file mode 100644 index 0000000000..08d1e68383 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/is.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/it.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/it.svg new file mode 100644 index 0000000000..615c58fb8f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/it.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/je.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/je.svg new file mode 100644 index 0000000000..c63ccb29ec --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/je.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/jm.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/jm.svg new file mode 100644 index 0000000000..c261da0971 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/jm.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/jo.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/jo.svg new file mode 100644 index 0000000000..ab1c62aaa1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/jo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/jp.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/jp.svg new file mode 100644 index 0000000000..dc7a64a5c7 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/jp.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ke.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ke.svg new file mode 100644 index 0000000000..0b82f3a6c3 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ke.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kg.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kg.svg new file mode 100644 index 0000000000..71ee7b8df5 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kg.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kh.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kh.svg new file mode 100644 index 0000000000..8c888f1681 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kh.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ki.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ki.svg new file mode 100644 index 0000000000..bfc5ccab46 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ki.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/km.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/km.svg new file mode 100644 index 0000000000..8f842ea8c0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/km.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kn.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kn.svg new file mode 100644 index 0000000000..4b2a2488be --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kn.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kp.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kp.svg new file mode 100644 index 0000000000..8eda6be874 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kp.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kr.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kr.svg new file mode 100644 index 0000000000..2db51b02a6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kr.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kw.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kw.svg new file mode 100644 index 0000000000..3d4047fff5 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kw.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ky.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ky.svg new file mode 100644 index 0000000000..b4ae00aa86 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ky.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kz.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kz.svg new file mode 100644 index 0000000000..f17bd6e0f8 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/kz.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/la.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/la.svg new file mode 100644 index 0000000000..1e7686a935 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/la.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lb.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lb.svg new file mode 100644 index 0000000000..a047b0b948 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lb.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lc.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lc.svg new file mode 100644 index 0000000000..b13b8852aa --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lc.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/li.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/li.svg new file mode 100644 index 0000000000..cbed5cc84c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/li.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lk.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lk.svg new file mode 100644 index 0000000000..2b112155f6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lk.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lr.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lr.svg new file mode 100644 index 0000000000..0ae34e7282 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lr.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ls.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ls.svg new file mode 100644 index 0000000000..e71bb5bb70 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ls.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lt.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lt.svg new file mode 100644 index 0000000000..aa96cf3236 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lt.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lu.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lu.svg new file mode 100644 index 0000000000..6293671602 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lu.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lv.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lv.svg new file mode 100644 index 0000000000..5556de1a3d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/lv.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ly.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ly.svg new file mode 100644 index 0000000000..fe0ed81b60 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ly.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ma.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ma.svg new file mode 100644 index 0000000000..85c99b3b9e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ma.svg @@ -0,0 +1,4 @@ + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mc.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mc.svg new file mode 100644 index 0000000000..d38822ddac --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mc.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/md.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/md.svg new file mode 100644 index 0000000000..86b2a96149 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/md.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/me.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/me.svg new file mode 100644 index 0000000000..56a19ed0a0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/me.svg @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mf.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mf.svg new file mode 100644 index 0000000000..310afce407 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mf.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mg.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mg.svg new file mode 100644 index 0000000000..f0375cc6be --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mg.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mh.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mh.svg new file mode 100644 index 0000000000..97f34631af --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mh.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mk.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mk.svg new file mode 100644 index 0000000000..da2e9a4c40 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mk.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ml.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ml.svg new file mode 100644 index 0000000000..1e4d98900c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ml.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mm.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mm.svg new file mode 100644 index 0000000000..5076184c32 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mm.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mn.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mn.svg new file mode 100644 index 0000000000..568fda088a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mn.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mo.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mo.svg new file mode 100644 index 0000000000..83d04ea28a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mp.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mp.svg new file mode 100644 index 0000000000..54a0ede9f2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mp.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mq.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mq.svg new file mode 100644 index 0000000000..7a69fb5842 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mq.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mr.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mr.svg new file mode 100644 index 0000000000..7da23e5f4e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mr.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ms.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ms.svg new file mode 100644 index 0000000000..d7d910d020 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ms.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mt.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mt.svg new file mode 100644 index 0000000000..96acc15e73 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mt.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mu.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mu.svg new file mode 100644 index 0000000000..773d3d59ee --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mu.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mv.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mv.svg new file mode 100644 index 0000000000..aa5ed5333e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mv.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mw.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mw.svg new file mode 100644 index 0000000000..a9521a08c1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mw.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mx.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mx.svg new file mode 100644 index 0000000000..a440656807 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mx.svg @@ -0,0 +1,378 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/my.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/my.svg new file mode 100644 index 0000000000..7ebe064b74 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/my.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mz.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mz.svg new file mode 100644 index 0000000000..113a205792 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/mz.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/na.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/na.svg new file mode 100644 index 0000000000..b934fc157c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/na.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nc.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nc.svg new file mode 100644 index 0000000000..2bdf6ee5b5 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nc.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ne.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ne.svg new file mode 100644 index 0000000000..e76e44c0bb --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ne.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nf.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nf.svg new file mode 100644 index 0000000000..21495222f6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nf.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ng.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ng.svg new file mode 100644 index 0000000000..57d65d380a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ng.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ni.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ni.svg new file mode 100644 index 0000000000..8f68d42255 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ni.svg @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nl.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nl.svg new file mode 100644 index 0000000000..9db233dd4b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nl.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/no.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/no.svg new file mode 100644 index 0000000000..08ea5728d2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/no.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/np.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/np.svg new file mode 100644 index 0000000000..f34ee8c638 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/np.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nr.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nr.svg new file mode 100644 index 0000000000..282d80fa14 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nr.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nu.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nu.svg new file mode 100644 index 0000000000..aced440dc1 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nu.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nz.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nz.svg new file mode 100644 index 0000000000..5283a96ed4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/nz.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/om.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/om.svg new file mode 100644 index 0000000000..055d1e6986 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/om.svg @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pa.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pa.svg new file mode 100644 index 0000000000..57965b9ea2 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pa.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pe.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pe.svg new file mode 100644 index 0000000000..40b87badbb --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pe.svg @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pf.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pf.svg new file mode 100644 index 0000000000..94ff90cb2c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pf.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pg.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pg.svg new file mode 100644 index 0000000000..7397754182 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pg.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ph.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ph.svg new file mode 100644 index 0000000000..681cf23ff5 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ph.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pk.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pk.svg new file mode 100644 index 0000000000..06b6022f0c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pk.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pl.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pl.svg new file mode 100644 index 0000000000..f7c12a1843 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pl.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pm.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pm.svg new file mode 100644 index 0000000000..3d4014a414 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pm.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pn.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pn.svg new file mode 100644 index 0000000000..47b0749a0a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pn.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pr.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pr.svg new file mode 100644 index 0000000000..79cf4c0991 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pr.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ps.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ps.svg new file mode 100644 index 0000000000..7c1ea3f9a6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ps.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pt.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pt.svg new file mode 100644 index 0000000000..425515054a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pt.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pw.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pw.svg new file mode 100644 index 0000000000..83bc3f71d5 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/pw.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/py.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/py.svg new file mode 100644 index 0000000000..88f55e6f02 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/py.svg @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/qa.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/qa.svg new file mode 100644 index 0000000000..0bf30ea37f --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/qa.svg @@ -0,0 +1,4 @@ + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/re.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/re.svg new file mode 100644 index 0000000000..027c9f32ce --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/re.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ro.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ro.svg new file mode 100644 index 0000000000..994992ab66 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ro.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/rs.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/rs.svg new file mode 100644 index 0000000000..3270f304c4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/rs.svg @@ -0,0 +1,296 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ru.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ru.svg new file mode 100644 index 0000000000..d643087430 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ru.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/rw.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/rw.svg new file mode 100644 index 0000000000..26e41f6ad7 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/rw.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sa.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sa.svg new file mode 100644 index 0000000000..1cc41b80bf --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sa.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sb.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sb.svg new file mode 100644 index 0000000000..f61bafd6a9 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sb.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sc.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sc.svg new file mode 100644 index 0000000000..65d8943d64 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sc.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sd.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sd.svg new file mode 100644 index 0000000000..72a729708b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sd.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/se.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/se.svg new file mode 100644 index 0000000000..e824395ebb --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/se.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sg.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sg.svg new file mode 100644 index 0000000000..1444e5b6ab --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sg.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sh.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sh.svg new file mode 100644 index 0000000000..599a09d73b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sh.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/si.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/si.svg new file mode 100644 index 0000000000..6de77ef473 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/si.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sj.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sj.svg new file mode 100644 index 0000000000..f4e5829735 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sj.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sk.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sk.svg new file mode 100644 index 0000000000..fd728680b0 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sk.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sl.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sl.svg new file mode 100644 index 0000000000..18c76d781b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sl.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sm.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sm.svg new file mode 100644 index 0000000000..1ed18eed6c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sm.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + L + + + I + + + B + + + E + + + R + + + T + + + A + + + S + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sn.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sn.svg new file mode 100644 index 0000000000..fbbad5e026 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sn.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/so.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/so.svg new file mode 100644 index 0000000000..96d88a992a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/so.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sr.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sr.svg new file mode 100644 index 0000000000..a0ca03e367 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sr.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ss.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ss.svg new file mode 100644 index 0000000000..7e6a47ffd9 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ss.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/st.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/st.svg new file mode 100644 index 0000000000..da5df2990d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/st.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sv.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sv.svg new file mode 100644 index 0000000000..65b44baeec --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sv.svg @@ -0,0 +1,596 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sx.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sx.svg new file mode 100644 index 0000000000..5d9fadfa2e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sx.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sy.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sy.svg new file mode 100644 index 0000000000..904dc1f65c --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sy.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sz.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sz.svg new file mode 100644 index 0000000000..b3009e4d92 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/sz.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tc.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tc.svg new file mode 100644 index 0000000000..1029615f55 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tc.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/td.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/td.svg new file mode 100644 index 0000000000..e3e81ce2fd --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/td.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tf.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tf.svg new file mode 100644 index 0000000000..2061867c96 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tf.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tg.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tg.svg new file mode 100644 index 0000000000..2c1fd98fd4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tg.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/th.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/th.svg new file mode 100644 index 0000000000..86850f5fbf --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/th.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tj.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tj.svg new file mode 100644 index 0000000000..853a4a4555 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tj.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tk.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tk.svg new file mode 100644 index 0000000000..c5ff6b4cb4 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tk.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tl.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tl.svg new file mode 100644 index 0000000000..ec6d44bd13 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tl.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tm.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tm.svg new file mode 100644 index 0000000000..d856424fe6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tm.svg @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tn.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tn.svg new file mode 100644 index 0000000000..d46a1cd9aa --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tn.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/to.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/to.svg new file mode 100644 index 0000000000..201d6bc39d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/to.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tr.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tr.svg new file mode 100644 index 0000000000..861d4ea580 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tr.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tt.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tt.svg new file mode 100644 index 0000000000..87e439a07d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tt.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tv.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tv.svg new file mode 100644 index 0000000000..f30c8f3710 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tv.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tw.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tw.svg new file mode 100644 index 0000000000..5f284fc612 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tw.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tz.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tz.svg new file mode 100644 index 0000000000..f993ff15d6 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/tz.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ua.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ua.svg new file mode 100644 index 0000000000..18ebe0d49d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ua.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ug.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ug.svg new file mode 100644 index 0000000000..d9be945945 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ug.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/um.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/um.svg new file mode 100644 index 0000000000..25b08ce6ab --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/um.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/un.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/un.svg new file mode 100644 index 0000000000..1d50ea9250 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/un.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/us.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/us.svg new file mode 100644 index 0000000000..31f90c6c3a --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/us.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/uy.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/uy.svg new file mode 100644 index 0000000000..0194a7cb0b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/uy.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/uz.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/uz.svg new file mode 100644 index 0000000000..641af1b826 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/uz.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/va.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/va.svg new file mode 100644 index 0000000000..7da6d3e739 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/va.svg @@ -0,0 +1,479 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vc.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vc.svg new file mode 100644 index 0000000000..ee72f781ac --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vc.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ve.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ve.svg new file mode 100644 index 0000000000..205fe848a3 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ve.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vg.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vg.svg new file mode 100644 index 0000000000..9572de3480 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vg.svg @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vi.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vi.svg new file mode 100644 index 0000000000..2740f24ffa --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vi.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vn.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vn.svg new file mode 100644 index 0000000000..6b158145ad --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vn.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vu.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vu.svg new file mode 100644 index 0000000000..397d30e60b --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/vu.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/wf.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/wf.svg new file mode 100644 index 0000000000..bb726a7c2e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/wf.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ws.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ws.svg new file mode 100644 index 0000000000..155ad7b558 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ws.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/xk.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/xk.svg new file mode 100644 index 0000000000..69146ca449 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/xk.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ye.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ye.svg new file mode 100644 index 0000000000..d49d2c414e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/ye.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/yt.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/yt.svg new file mode 100644 index 0000000000..7bf3837379 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/yt.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/za.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/za.svg new file mode 100644 index 0000000000..9bae96fe3e --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/za.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/zm.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/zm.svg new file mode 100644 index 0000000000..105f1076ad --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/zm.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/zw.svg b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/zw.svg new file mode 100644 index 0000000000..ca5b7a20f9 --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/flag-icon/flags/1x1/zw.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/css/all.css b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/css/all.css new file mode 100644 index 0000000000..1893247a7d --- /dev/null +++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/css/all.css @@ -0,0 +1,8003 @@ +/*! + * Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2023 Fonticons, Inc. + */ +.fa { + font-family: var(--fa-style-family, "Font Awesome 6 Free"); + font-weight: var(--fa-style, 900); } + +.fa, +.fa-classic, +.fa-sharp, +.fas, +.fa-solid, +.far, +.fa-regular, +.fab, +.fa-brands { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: var(--fa-display, inline-block); + font-style: normal; + font-variant: normal; + line-height: 1; + text-rendering: auto; } + +.fas, +.fa-classic, +.fa-solid, +.far, +.fa-regular { + font-family: 'Font Awesome 6 Free'; } + +.fab, +.fa-brands { + font-family: 'Font Awesome 6 Brands'; } + +.fa-1x { + font-size: 1em; } + +.fa-2x { + font-size: 2em; } + +.fa-3x { + font-size: 3em; } + +.fa-4x { + font-size: 4em; } + +.fa-5x { + font-size: 5em; } + +.fa-6x { + font-size: 6em; } + +.fa-7x { + font-size: 7em; } + +.fa-8x { + font-size: 8em; } + +.fa-9x { + font-size: 9em; } + +.fa-10x { + font-size: 10em; } + +.fa-2xs { + font-size: 0.625em; + line-height: 0.1em; + vertical-align: 0.225em; } + +.fa-xs { + font-size: 0.75em; + line-height: 0.08333em; + vertical-align: 0.125em; } + +.fa-sm { + font-size: 0.875em; + line-height: 0.07143em; + vertical-align: 0.05357em; } + +.fa-lg { + font-size: 1.25em; + line-height: 0.05em; + vertical-align: -0.075em; } + +.fa-xl { + font-size: 1.5em; + line-height: 0.04167em; + vertical-align: -0.125em; } + +.fa-2xl { + font-size: 2em; + line-height: 0.03125em; + vertical-align: -0.1875em; } + +.fa-fw { + text-align: center; + width: 1.25em; } + +.fa-ul { + list-style-type: none; + margin-left: var(--fa-li-margin, 2.5em); + padding-left: 0; } + .fa-ul > li { + position: relative; } + +.fa-li { + left: calc(var(--fa-li-width, 2em) * -1); + position: absolute; + text-align: center; + width: var(--fa-li-width, 2em); + line-height: inherit; } + +.fa-border { + border-color: var(--fa-border-color, #eee); + border-radius: var(--fa-border-radius, 0.1em); + border-style: var(--fa-border-style, solid); + border-width: var(--fa-border-width, 0.08em); + padding: var(--fa-border-padding, 0.2em 0.25em 0.15em); } + +.fa-pull-left { + float: left; + margin-right: var(--fa-pull-margin, 0.3em); } + +.fa-pull-right { + float: right; + margin-left: var(--fa-pull-margin, 0.3em); } + +.fa-beat { + -webkit-animation-name: fa-beat; + animation-name: fa-beat; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, ease-in-out); + animation-timing-function: var(--fa-animation-timing, ease-in-out); } + +.fa-bounce { + -webkit-animation-name: fa-bounce; + animation-name: fa-bounce; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); + animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); } + +.fa-fade { + -webkit-animation-name: fa-fade; + animation-name: fa-fade; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); + animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); } + +.fa-beat-fade { + -webkit-animation-name: fa-beat-fade; + animation-name: fa-beat-fade; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); + animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1)); } + +.fa-flip { + -webkit-animation-name: fa-flip; + animation-name: fa-flip; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, ease-in-out); + animation-timing-function: var(--fa-animation-timing, ease-in-out); } + +.fa-shake { + -webkit-animation-name: fa-shake; + animation-name: fa-shake; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, linear); + animation-timing-function: var(--fa-animation-timing, linear); } + +.fa-spin { + -webkit-animation-name: fa-spin; + animation-name: fa-spin; + -webkit-animation-delay: var(--fa-animation-delay, 0s); + animation-delay: var(--fa-animation-delay, 0s); + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 2s); + animation-duration: var(--fa-animation-duration, 2s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, linear); + animation-timing-function: var(--fa-animation-timing, linear); } + +.fa-spin-reverse { + --fa-animation-direction: reverse; } + +.fa-pulse, +.fa-spin-pulse { + -webkit-animation-name: fa-spin; + animation-name: fa-spin; + -webkit-animation-direction: var(--fa-animation-direction, normal); + animation-direction: var(--fa-animation-direction, normal); + -webkit-animation-duration: var(--fa-animation-duration, 1s); + animation-duration: var(--fa-animation-duration, 1s); + -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); + animation-iteration-count: var(--fa-animation-iteration-count, infinite); + -webkit-animation-timing-function: var(--fa-animation-timing, steps(8)); + animation-timing-function: var(--fa-animation-timing, steps(8)); } + +@media (prefers-reduced-motion: reduce) { + .fa-beat, + .fa-bounce, + .fa-fade, + .fa-beat-fade, + .fa-flip, + .fa-pulse, + .fa-shake, + .fa-spin, + .fa-spin-pulse { + -webkit-animation-delay: -1ms; + animation-delay: -1ms; + -webkit-animation-duration: 1ms; + animation-duration: 1ms; + -webkit-animation-iteration-count: 1; + animation-iteration-count: 1; + -webkit-transition-delay: 0s; + transition-delay: 0s; + -webkit-transition-duration: 0s; + transition-duration: 0s; } } + +@-webkit-keyframes fa-beat { + 0%, 90% { + -webkit-transform: scale(1); + transform: scale(1); } + 45% { + -webkit-transform: scale(var(--fa-beat-scale, 1.25)); + transform: scale(var(--fa-beat-scale, 1.25)); } } + +@keyframes fa-beat { + 0%, 90% { + -webkit-transform: scale(1); + transform: scale(1); } + 45% { + -webkit-transform: scale(var(--fa-beat-scale, 1.25)); + transform: scale(var(--fa-beat-scale, 1.25)); } } + +@-webkit-keyframes fa-bounce { + 0% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); } + 10% { + -webkit-transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); + transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); } + 30% { + -webkit-transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); + transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); } + 50% { + -webkit-transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); + transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); } + 57% { + -webkit-transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); + transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); } + 64% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); } + 100% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); } } + +@keyframes fa-bounce { + 0% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); } + 10% { + -webkit-transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); + transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0); } + 30% { + -webkit-transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); + transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em)); } + 50% { + -webkit-transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); + transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0); } + 57% { + -webkit-transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); + transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em)); } + 64% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); } + 100% { + -webkit-transform: scale(1, 1) translateY(0); + transform: scale(1, 1) translateY(0); } } + +@-webkit-keyframes fa-fade { + 50% { + opacity: var(--fa-fade-opacity, 0.4); } } + +@keyframes fa-fade { + 50% { + opacity: var(--fa-fade-opacity, 0.4); } } + +@-webkit-keyframes fa-beat-fade { + 0%, 100% { + opacity: var(--fa-beat-fade-opacity, 0.4); + -webkit-transform: scale(1); + transform: scale(1); } + 50% { + opacity: 1; + -webkit-transform: scale(var(--fa-beat-fade-scale, 1.125)); + transform: scale(var(--fa-beat-fade-scale, 1.125)); } } + +@keyframes fa-beat-fade { + 0%, 100% { + opacity: var(--fa-beat-fade-opacity, 0.4); + -webkit-transform: scale(1); + transform: scale(1); } + 50% { + opacity: 1; + -webkit-transform: scale(var(--fa-beat-fade-scale, 1.125)); + transform: scale(var(--fa-beat-fade-scale, 1.125)); } } + +@-webkit-keyframes fa-flip { + 50% { + -webkit-transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); + transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } } + +@keyframes fa-flip { + 50% { + -webkit-transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); + transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg)); } } + +@-webkit-keyframes fa-shake { + 0% { + -webkit-transform: rotate(-15deg); + transform: rotate(-15deg); } + 4% { + -webkit-transform: rotate(15deg); + transform: rotate(15deg); } + 8%, 24% { + -webkit-transform: rotate(-18deg); + transform: rotate(-18deg); } + 12%, 28% { + -webkit-transform: rotate(18deg); + transform: rotate(18deg); } + 16% { + -webkit-transform: rotate(-22deg); + transform: rotate(-22deg); } + 20% { + -webkit-transform: rotate(22deg); + transform: rotate(22deg); } + 32% { + -webkit-transform: rotate(-12deg); + transform: rotate(-12deg); } + 36% { + -webkit-transform: rotate(12deg); + transform: rotate(12deg); } + 40%, 100% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } } + +@keyframes fa-shake { + 0% { + -webkit-transform: rotate(-15deg); + transform: rotate(-15deg); } + 4% { + -webkit-transform: rotate(15deg); + transform: rotate(15deg); } + 8%, 24% { + -webkit-transform: rotate(-18deg); + transform: rotate(-18deg); } + 12%, 28% { + -webkit-transform: rotate(18deg); + transform: rotate(18deg); } + 16% { + -webkit-transform: rotate(-22deg); + transform: rotate(-22deg); } + 20% { + -webkit-transform: rotate(22deg); + transform: rotate(22deg); } + 32% { + -webkit-transform: rotate(-12deg); + transform: rotate(-12deg); } + 36% { + -webkit-transform: rotate(12deg); + transform: rotate(12deg); } + 40%, 100% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } } + +@-webkit-keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + +@keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + +.fa-rotate-90 { + -webkit-transform: rotate(90deg); + transform: rotate(90deg); } + +.fa-rotate-180 { + -webkit-transform: rotate(180deg); + transform: rotate(180deg); } + +.fa-rotate-270 { + -webkit-transform: rotate(270deg); + transform: rotate(270deg); } + +.fa-flip-horizontal { + -webkit-transform: scale(-1, 1); + transform: scale(-1, 1); } + +.fa-flip-vertical { + -webkit-transform: scale(1, -1); + transform: scale(1, -1); } + +.fa-flip-both, +.fa-flip-horizontal.fa-flip-vertical { + -webkit-transform: scale(-1, -1); + transform: scale(-1, -1); } + +.fa-rotate-by { + -webkit-transform: rotate(var(--fa-rotate-angle, none)); + transform: rotate(var(--fa-rotate-angle, none)); } + +.fa-stack { + display: inline-block; + height: 2em; + line-height: 2em; + position: relative; + vertical-align: middle; + width: 2.5em; } + +.fa-stack-1x, +.fa-stack-2x { + left: 0; + position: absolute; + text-align: center; + width: 100%; + z-index: var(--fa-stack-z-index, auto); } + +.fa-stack-1x { + line-height: inherit; } + +.fa-stack-2x { + font-size: 2em; } + +.fa-inverse { + color: var(--fa-inverse, #fff); } + +/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen +readers do not read off random characters that represent icons */ + +.fa-0::before { + content: "\30"; } + +.fa-1::before { + content: "\31"; } + +.fa-2::before { + content: "\32"; } + +.fa-3::before { + content: "\33"; } + +.fa-4::before { + content: "\34"; } + +.fa-5::before { + content: "\35"; } + +.fa-6::before { + content: "\36"; } + +.fa-7::before { + content: "\37"; } + +.fa-8::before { + content: "\38"; } + +.fa-9::before { + content: "\39"; } + +.fa-fill-drip::before { + content: "\f576"; } + +.fa-arrows-to-circle::before { + content: "\e4bd"; } + +.fa-circle-chevron-right::before { + content: "\f138"; } + +.fa-chevron-circle-right::before { + content: "\f138"; } + +.fa-at::before { + content: "\40"; } + +.fa-trash-can::before { + content: "\f2ed"; } + +.fa-trash-alt::before { + content: "\f2ed"; } + +.fa-text-height::before { + content: "\f034"; } + +.fa-user-xmark::before { + content: "\f235"; } + +.fa-user-times::before { + content: "\f235"; } + +.fa-stethoscope::before { + content: "\f0f1"; } + +.fa-message::before { + content: "\f27a"; } + +.fa-comment-alt::before { + content: "\f27a"; } + +.fa-info::before { + content: "\f129"; } + +.fa-down-left-and-up-right-to-center::before { + content: "\f422"; } + +.fa-compress-alt::before { + content: "\f422"; } + +.fa-explosion::before { + content: "\e4e9"; } + +.fa-file-lines::before { + content: "\f15c"; } + +.fa-file-alt::before { + content: "\f15c"; } + +.fa-file-text::before { + content: "\f15c"; } + +.fa-wave-square::before { + content: "\f83e"; } + +.fa-ring::before { + content: "\f70b"; } + +.fa-building-un::before { + content: "\e4d9"; } + +.fa-dice-three::before { + content: "\f527"; } + +.fa-calendar-days::before { + content: "\f073"; } + +.fa-calendar-alt::before { + content: "\f073"; } + +.fa-anchor-circle-check::before { + content: "\e4aa"; } + +.fa-building-circle-arrow-right::before { + content: "\e4d1"; } + +.fa-volleyball::before { + content: "\f45f"; } + +.fa-volleyball-ball::before { + content: "\f45f"; } + +.fa-arrows-up-to-line::before { + content: "\e4c2"; } + +.fa-sort-down::before { + content: "\f0dd"; } + +.fa-sort-desc::before { + content: "\f0dd"; } + +.fa-circle-minus::before { + content: "\f056"; } + +.fa-minus-circle::before { + content: "\f056"; } + +.fa-door-open::before { + content: "\f52b"; } + +.fa-right-from-bracket::before { + content: "\f2f5"; } + +.fa-sign-out-alt::before { + content: "\f2f5"; } + +.fa-atom::before { + content: "\f5d2"; } + +.fa-soap::before { + content: "\e06e"; } + +.fa-icons::before { + content: "\f86d"; } + +.fa-heart-music-camera-bolt::before { + content: "\f86d"; } + +.fa-microphone-lines-slash::before { + content: "\f539"; } + +.fa-microphone-alt-slash::before { + content: "\f539"; } + +.fa-bridge-circle-check::before { + content: "\e4c9"; } + +.fa-pump-medical::before { + content: "\e06a"; } + +.fa-fingerprint::before { + content: "\f577"; } + +.fa-hand-point-right::before { + content: "\f0a4"; } + +.fa-magnifying-glass-location::before { + content: "\f689"; } + +.fa-search-location::before { + content: "\f689"; } + +.fa-forward-step::before { + content: "\f051"; } + +.fa-step-forward::before { + content: "\f051"; } + +.fa-face-smile-beam::before { + content: "\f5b8"; } + +.fa-smile-beam::before { + content: "\f5b8"; } + +.fa-flag-checkered::before { + content: "\f11e"; } + +.fa-football::before { + content: "\f44e"; } + +.fa-football-ball::before { + content: "\f44e"; } + +.fa-school-circle-exclamation::before { + content: "\e56c"; } + +.fa-crop::before { + content: "\f125"; } + +.fa-angles-down::before { + content: "\f103"; } + +.fa-angle-double-down::before { + content: "\f103"; } + +.fa-users-rectangle::before { + content: "\e594"; } + +.fa-people-roof::before { + content: "\e537"; } + +.fa-people-line::before { + content: "\e534"; } + +.fa-beer-mug-empty::before { + content: "\f0fc"; } + +.fa-beer::before { + content: "\f0fc"; } + +.fa-diagram-predecessor::before { + content: "\e477"; } + +.fa-arrow-up-long::before { + content: "\f176"; } + +.fa-long-arrow-up::before { + content: "\f176"; } + +.fa-fire-flame-simple::before { + content: "\f46a"; } + +.fa-burn::before { + content: "\f46a"; } + +.fa-person::before { + content: "\f183"; } + +.fa-male::before { + content: "\f183"; } + +.fa-laptop::before { + content: "\f109"; } + +.fa-file-csv::before { + content: "\f6dd"; } + +.fa-menorah::before { + content: "\f676"; } + +.fa-truck-plane::before { + content: "\e58f"; } + +.fa-record-vinyl::before { + content: "\f8d9"; } + +.fa-face-grin-stars::before { + content: "\f587"; } + +.fa-grin-stars::before { + content: "\f587"; } + +.fa-bong::before { + content: "\f55c"; } + +.fa-spaghetti-monster-flying::before { + content: "\f67b"; } + +.fa-pastafarianism::before { + content: "\f67b"; } + +.fa-arrow-down-up-across-line::before { + content: "\e4af"; } + +.fa-spoon::before { + content: "\f2e5"; } + +.fa-utensil-spoon::before { + content: "\f2e5"; } + +.fa-jar-wheat::before { + content: "\e517"; } + +.fa-envelopes-bulk::before { + content: "\f674"; } + +.fa-mail-bulk::before { + content: "\f674"; } + +.fa-file-circle-exclamation::before { + content: "\e4eb"; } + +.fa-circle-h::before { + content: "\f47e"; } + +.fa-hospital-symbol::before { + content: "\f47e"; } + +.fa-pager::before { + content: "\f815"; } + +.fa-address-book::before { + content: "\f2b9"; } + +.fa-contact-book::before { + content: "\f2b9"; } + +.fa-strikethrough::before { + content: "\f0cc"; } + +.fa-k::before { + content: "\4b"; } + +.fa-landmark-flag::before { + content: "\e51c"; } + +.fa-pencil::before { + content: "\f303"; } + +.fa-pencil-alt::before { + content: "\f303"; } + +.fa-backward::before { + content: "\f04a"; } + +.fa-caret-right::before { + content: "\f0da"; } + +.fa-comments::before { + content: "\f086"; } + +.fa-paste::before { + content: "\f0ea"; } + +.fa-file-clipboard::before { + content: "\f0ea"; } + +.fa-code-pull-request::before { + content: "\e13c"; } + +.fa-clipboard-list::before { + content: "\f46d"; } + +.fa-truck-ramp-box::before { + content: "\f4de"; } + +.fa-truck-loading::before { + content: "\f4de"; } + +.fa-user-check::before { + content: "\f4fc"; } + +.fa-vial-virus::before { + content: "\e597"; } + +.fa-sheet-plastic::before { + content: "\e571"; } + +.fa-blog::before { + content: "\f781"; } + +.fa-user-ninja::before { + content: "\f504"; } + +.fa-person-arrow-up-from-line::before { + content: "\e539"; } + +.fa-scroll-torah::before { + content: "\f6a0"; } + +.fa-torah::before { + content: "\f6a0"; } + +.fa-broom-ball::before { + content: "\f458"; } + +.fa-quidditch::before { + content: "\f458"; } + +.fa-quidditch-broom-ball::before { + content: "\f458"; } + +.fa-toggle-off::before { + content: "\f204"; } + +.fa-box-archive::before { + content: "\f187"; } + +.fa-archive::before { + content: "\f187"; } + +.fa-person-drowning::before { + content: "\e545"; } + +.fa-arrow-down-9-1::before { + content: "\f886"; } + +.fa-sort-numeric-desc::before { + content: "\f886"; } + +.fa-sort-numeric-down-alt::before { + content: "\f886"; } + +.fa-face-grin-tongue-squint::before { + content: "\f58a"; } + +.fa-grin-tongue-squint::before { + content: "\f58a"; } + +.fa-spray-can::before { + content: "\f5bd"; } + +.fa-truck-monster::before { + content: "\f63b"; } + +.fa-w::before { + content: "\57"; } + +.fa-earth-africa::before { + content: "\f57c"; } + +.fa-globe-africa::before { + content: "\f57c"; } + +.fa-rainbow::before { + content: "\f75b"; } + +.fa-circle-notch::before { + content: "\f1ce"; } + +.fa-tablet-screen-button::before { + content: "\f3fa"; } + +.fa-tablet-alt::before { + content: "\f3fa"; } + +.fa-paw::before { + content: "\f1b0"; } + +.fa-cloud::before { + content: "\f0c2"; } + +.fa-trowel-bricks::before { + content: "\e58a"; } + +.fa-face-flushed::before { + content: "\f579"; } + +.fa-flushed::before { + content: "\f579"; } + +.fa-hospital-user::before { + content: "\f80d"; } + +.fa-tent-arrow-left-right::before { + content: "\e57f"; } + +.fa-gavel::before { + content: "\f0e3"; } + +.fa-legal::before { + content: "\f0e3"; } + +.fa-binoculars::before { + content: "\f1e5"; } + +.fa-microphone-slash::before { + content: "\f131"; } + +.fa-box-tissue::before { + content: "\e05b"; } + +.fa-motorcycle::before { + content: "\f21c"; } + +.fa-bell-concierge::before { + content: "\f562"; } + +.fa-concierge-bell::before { + content: "\f562"; } + +.fa-pen-ruler::before { + content: "\f5ae"; } + +.fa-pencil-ruler::before { + content: "\f5ae"; } + +.fa-people-arrows::before { + content: "\e068"; } + +.fa-people-arrows-left-right::before { + content: "\e068"; } + +.fa-mars-and-venus-burst::before { + content: "\e523"; } + +.fa-square-caret-right::before { + content: "\f152"; } + +.fa-caret-square-right::before { + content: "\f152"; } + +.fa-scissors::before { + content: "\f0c4"; } + +.fa-cut::before { + content: "\f0c4"; } + +.fa-sun-plant-wilt::before { + content: "\e57a"; } + +.fa-toilets-portable::before { + content: "\e584"; } + +.fa-hockey-puck::before { + content: "\f453"; } + +.fa-table::before { + content: "\f0ce"; } + +.fa-magnifying-glass-arrow-right::before { + content: "\e521"; } + +.fa-tachograph-digital::before { + content: "\f566"; } + +.fa-digital-tachograph::before { + content: "\f566"; } + +.fa-users-slash::before { + content: "\e073"; } + +.fa-clover::before { + content: "\e139"; } + +.fa-reply::before { + content: "\f3e5"; } + +.fa-mail-reply::before { + content: "\f3e5"; } + +.fa-star-and-crescent::before { + content: "\f699"; } + +.fa-house-fire::before { + content: "\e50c"; } + +.fa-square-minus::before { + content: "\f146"; } + +.fa-minus-square::before { + content: "\f146"; } + +.fa-helicopter::before { + content: "\f533"; } + +.fa-compass::before { + content: "\f14e"; } + +.fa-square-caret-down::before { + content: "\f150"; } + +.fa-caret-square-down::before { + content: "\f150"; } + +.fa-file-circle-question::before { + content: "\e4ef"; } + +.fa-laptop-code::before { + content: "\f5fc"; } + +.fa-swatchbook::before { + content: "\f5c3"; } + +.fa-prescription-bottle::before { + content: "\f485"; } + +.fa-bars::before { + content: "\f0c9"; } + +.fa-navicon::before { + content: "\f0c9"; } + +.fa-people-group::before { + content: "\e533"; } + +.fa-hourglass-end::before { + content: "\f253"; } + +.fa-hourglass-3::before { + content: "\f253"; } + +.fa-heart-crack::before { + content: "\f7a9"; } + +.fa-heart-broken::before { + content: "\f7a9"; } + +.fa-square-up-right::before { + content: "\f360"; } + +.fa-external-link-square-alt::before { + content: "\f360"; } + +.fa-face-kiss-beam::before { + content: "\f597"; } + +.fa-kiss-beam::before { + content: "\f597"; } + +.fa-film::before { + content: "\f008"; } + +.fa-ruler-horizontal::before { + content: "\f547"; } + +.fa-people-robbery::before { + content: "\e536"; } + +.fa-lightbulb::before { + content: "\f0eb"; } + +.fa-caret-left::before { + content: "\f0d9"; } + +.fa-circle-exclamation::before { + content: "\f06a"; } + +.fa-exclamation-circle::before { + content: "\f06a"; } + +.fa-school-circle-xmark::before { + content: "\e56d"; } + +.fa-arrow-right-from-bracket::before { + content: "\f08b"; } + +.fa-sign-out::before { + content: "\f08b"; } + +.fa-circle-chevron-down::before { + content: "\f13a"; } + +.fa-chevron-circle-down::before { + content: "\f13a"; } + +.fa-unlock-keyhole::before { + content: "\f13e"; } + +.fa-unlock-alt::before { + content: "\f13e"; } + +.fa-cloud-showers-heavy::before { + content: "\f740"; } + +.fa-headphones-simple::before { + content: "\f58f"; } + +.fa-headphones-alt::before { + content: "\f58f"; } + +.fa-sitemap::before { + content: "\f0e8"; } + +.fa-circle-dollar-to-slot::before { + content: "\f4b9"; } + +.fa-donate::before { + content: "\f4b9"; } + +.fa-memory::before { + content: "\f538"; } + +.fa-road-spikes::before { + content: "\e568"; } + +.fa-fire-burner::before { + content: "\e4f1"; } + +.fa-flag::before { + content: "\f024"; } + +.fa-hanukiah::before { + content: "\f6e6"; } + +.fa-feather::before { + content: "\f52d"; } + +.fa-volume-low::before { + content: "\f027"; } + +.fa-volume-down::before { + content: "\f027"; } + +.fa-comment-slash::before { + content: "\f4b3"; } + +.fa-cloud-sun-rain::before { + content: "\f743"; } + +.fa-compress::before { + content: "\f066"; } + +.fa-wheat-awn::before { + content: "\e2cd"; } + +.fa-wheat-alt::before { + content: "\e2cd"; } + +.fa-ankh::before { + content: "\f644"; } + +.fa-hands-holding-child::before { + content: "\e4fa"; } + +.fa-asterisk::before { + content: "\2a"; } + +.fa-square-check::before { + content: "\f14a"; } + +.fa-check-square::before { + content: "\f14a"; } + +.fa-peseta-sign::before { + content: "\e221"; } + +.fa-heading::before { + content: "\f1dc"; } + +.fa-header::before { + content: "\f1dc"; } + +.fa-ghost::before { + content: "\f6e2"; } + +.fa-list::before { + content: "\f03a"; } + +.fa-list-squares::before { + content: "\f03a"; } + +.fa-square-phone-flip::before { + content: "\f87b"; } + +.fa-phone-square-alt::before { + content: "\f87b"; } + +.fa-cart-plus::before { + content: "\f217"; } + +.fa-gamepad::before { + content: "\f11b"; } + +.fa-circle-dot::before { + content: "\f192"; } + +.fa-dot-circle::before { + content: "\f192"; } + +.fa-face-dizzy::before { + content: "\f567"; } + +.fa-dizzy::before { + content: "\f567"; } + +.fa-egg::before { + content: "\f7fb"; } + +.fa-house-medical-circle-xmark::before { + content: "\e513"; } + +.fa-campground::before { + content: "\f6bb"; } + +.fa-folder-plus::before { + content: "\f65e"; } + +.fa-futbol::before { + content: "\f1e3"; } + +.fa-futbol-ball::before { + content: "\f1e3"; } + +.fa-soccer-ball::before { + content: "\f1e3"; } + +.fa-paintbrush::before { + content: "\f1fc"; } + +.fa-paint-brush::before { + content: "\f1fc"; } + +.fa-lock::before { + content: "\f023"; } + +.fa-gas-pump::before { + content: "\f52f"; } + +.fa-hot-tub-person::before { + content: "\f593"; } + +.fa-hot-tub::before { + content: "\f593"; } + +.fa-map-location::before { + content: "\f59f"; } + +.fa-map-marked::before { + content: "\f59f"; } + +.fa-house-flood-water::before { + content: "\e50e"; } + +.fa-tree::before { + content: "\f1bb"; } + +.fa-bridge-lock::before { + content: "\e4cc"; } + +.fa-sack-dollar::before { + content: "\f81d"; } + +.fa-pen-to-square::before { + content: "\f044"; } + +.fa-edit::before { + content: "\f044"; } + +.fa-car-side::before { + content: "\f5e4"; } + +.fa-share-nodes::before { + content: "\f1e0"; } + +.fa-share-alt::before { + content: "\f1e0"; } + +.fa-heart-circle-minus::before { + content: "\e4ff"; } + +.fa-hourglass-half::before { + content: "\f252"; } + +.fa-hourglass-2::before { + content: "\f252"; } + +.fa-microscope::before { + content: "\f610"; } + +.fa-sink::before { + content: "\e06d"; } + +.fa-bag-shopping::before { + content: "\f290"; } + +.fa-shopping-bag::before { + content: "\f290"; } + +.fa-arrow-down-z-a::before { + content: "\f881"; } + +.fa-sort-alpha-desc::before { + content: "\f881"; } + +.fa-sort-alpha-down-alt::before { + content: "\f881"; } + +.fa-mitten::before { + content: "\f7b5"; } + +.fa-person-rays::before { + content: "\e54d"; } + +.fa-users::before { + content: "\f0c0"; } + +.fa-eye-slash::before { + content: "\f070"; } + +.fa-flask-vial::before { + content: "\e4f3"; } + +.fa-hand::before { + content: "\f256"; } + +.fa-hand-paper::before { + content: "\f256"; } + +.fa-om::before { + content: "\f679"; } + +.fa-worm::before { + content: "\e599"; } + +.fa-house-circle-xmark::before { + content: "\e50b"; } + +.fa-plug::before { + content: "\f1e6"; } + +.fa-chevron-up::before { + content: "\f077"; } + +.fa-hand-spock::before { + content: "\f259"; } + +.fa-stopwatch::before { + content: "\f2f2"; } + +.fa-face-kiss::before { + content: "\f596"; } + +.fa-kiss::before { + content: "\f596"; } + +.fa-bridge-circle-xmark::before { + content: "\e4cb"; } + +.fa-face-grin-tongue::before { + content: "\f589"; } + +.fa-grin-tongue::before { + content: "\f589"; } + +.fa-chess-bishop::before { + content: "\f43a"; } + +.fa-face-grin-wink::before { + content: "\f58c"; } + +.fa-grin-wink::before { + content: "\f58c"; } + +.fa-ear-deaf::before { + content: "\f2a4"; } + +.fa-deaf::before { + content: "\f2a4"; } + +.fa-deafness::before { + content: "\f2a4"; } + +.fa-hard-of-hearing::before { + content: "\f2a4"; } + +.fa-road-circle-check::before { + content: "\e564"; } + +.fa-dice-five::before { + content: "\f523"; } + +.fa-square-rss::before { + content: "\f143"; } + +.fa-rss-square::before { + content: "\f143"; } + +.fa-land-mine-on::before { + content: "\e51b"; } + +.fa-i-cursor::before { + content: "\f246"; } + +.fa-stamp::before { + content: "\f5bf"; } + +.fa-stairs::before { + content: "\e289"; } + +.fa-i::before { + content: "\49"; } + +.fa-hryvnia-sign::before { + content: "\f6f2"; } + +.fa-hryvnia::before { + content: "\f6f2"; } + +.fa-pills::before { + content: "\f484"; } + +.fa-face-grin-wide::before { + content: "\f581"; } + +.fa-grin-alt::before { + content: "\f581"; } + +.fa-tooth::before { + content: "\f5c9"; } + +.fa-v::before { + content: "\56"; } + +.fa-bangladeshi-taka-sign::before { + content: "\e2e6"; } + +.fa-bicycle::before { + content: "\f206"; } + +.fa-staff-snake::before { + content: "\e579"; } + +.fa-rod-asclepius::before { + content: "\e579"; } + +.fa-rod-snake::before { + content: "\e579"; } + +.fa-staff-aesculapius::before { + content: "\e579"; } + +.fa-head-side-cough-slash::before { + content: "\e062"; } + +.fa-truck-medical::before { + content: "\f0f9"; } + +.fa-ambulance::before { + content: "\f0f9"; } + +.fa-wheat-awn-circle-exclamation::before { + content: "\e598"; } + +.fa-snowman::before { + content: "\f7d0"; } + +.fa-mortar-pestle::before { + content: "\f5a7"; } + +.fa-road-barrier::before { + content: "\e562"; } + +.fa-school::before { + content: "\f549"; } + +.fa-igloo::before { + content: "\f7ae"; } + +.fa-joint::before { + content: "\f595"; } + +.fa-angle-right::before { + content: "\f105"; } + +.fa-horse::before { + content: "\f6f0"; } + +.fa-q::before { + content: "\51"; } + +.fa-g::before { + content: "\47"; } + +.fa-notes-medical::before { + content: "\f481"; } + +.fa-temperature-half::before { + content: "\f2c9"; } + +.fa-temperature-2::before { + content: "\f2c9"; } + +.fa-thermometer-2::before { + content: "\f2c9"; } + +.fa-thermometer-half::before { + content: "\f2c9"; } + +.fa-dong-sign::before { + content: "\e169"; } + +.fa-capsules::before { + content: "\f46b"; } + +.fa-poo-storm::before { + content: "\f75a"; } + +.fa-poo-bolt::before { + content: "\f75a"; } + +.fa-face-frown-open::before { + content: "\f57a"; } + +.fa-frown-open::before { + content: "\f57a"; } + +.fa-hand-point-up::before { + content: "\f0a6"; } + +.fa-money-bill::before { + content: "\f0d6"; } + +.fa-bookmark::before { + content: "\f02e"; } + +.fa-align-justify::before { + content: "\f039"; } + +.fa-umbrella-beach::before { + content: "\f5ca"; } + +.fa-helmet-un::before { + content: "\e503"; } + +.fa-bullseye::before { + content: "\f140"; } + +.fa-bacon::before { + content: "\f7e5"; } + +.fa-hand-point-down::before { + content: "\f0a7"; } + +.fa-arrow-up-from-bracket::before { + content: "\e09a"; } + +.fa-folder::before { + content: "\f07b"; } + +.fa-folder-blank::before { + content: "\f07b"; } + +.fa-file-waveform::before { + content: "\f478"; } + +.fa-file-medical-alt::before { + content: "\f478"; } + +.fa-radiation::before { + content: "\f7b9"; } + +.fa-chart-simple::before { + content: "\e473"; } + +.fa-mars-stroke::before { + content: "\f229"; } + +.fa-vial::before { + content: "\f492"; } + +.fa-gauge::before { + content: "\f624"; } + +.fa-dashboard::before { + content: "\f624"; } + +.fa-gauge-med::before { + content: "\f624"; } + +.fa-tachometer-alt-average::before { + content: "\f624"; } + +.fa-wand-magic-sparkles::before { + content: "\e2ca"; } + +.fa-magic-wand-sparkles::before { + content: "\e2ca"; } + +.fa-e::before { + content: "\45"; } + +.fa-pen-clip::before { + content: "\f305"; } + +.fa-pen-alt::before { + content: "\f305"; } + +.fa-bridge-circle-exclamation::before { + content: "\e4ca"; } + +.fa-user::before { + content: "\f007"; } + +.fa-school-circle-check::before { + content: "\e56b"; } + +.fa-dumpster::before { + content: "\f793"; } + +.fa-van-shuttle::before { + content: "\f5b6"; } + +.fa-shuttle-van::before { + content: "\f5b6"; } + +.fa-building-user::before { + content: "\e4da"; } + +.fa-square-caret-left::before { + content: "\f191"; } + +.fa-caret-square-left::before { + content: "\f191"; } + +.fa-highlighter::before { + content: "\f591"; } + +.fa-key::before { + content: "\f084"; } + +.fa-bullhorn::before { + content: "\f0a1"; } + +.fa-globe::before { + content: "\f0ac"; } + +.fa-synagogue::before { + content: "\f69b"; } + +.fa-person-half-dress::before { + content: "\e548"; } + +.fa-road-bridge::before { + content: "\e563"; } + +.fa-location-arrow::before { + content: "\f124"; } + +.fa-c::before { + content: "\43"; } + +.fa-tablet-button::before { + content: "\f10a"; } + +.fa-building-lock::before { + content: "\e4d6"; } + +.fa-pizza-slice::before { + content: "\f818"; } + +.fa-money-bill-wave::before { + content: "\f53a"; } + +.fa-chart-area::before { + content: "\f1fe"; } + +.fa-area-chart::before { + content: "\f1fe"; } + +.fa-house-flag::before { + content: "\e50d"; } + +.fa-person-circle-minus::before { + content: "\e540"; } + +.fa-ban::before { + content: "\f05e"; } + +.fa-cancel::before { + content: "\f05e"; } + +.fa-camera-rotate::before { + content: "\e0d8"; } + +.fa-spray-can-sparkles::before { + content: "\f5d0"; } + +.fa-air-freshener::before { + content: "\f5d0"; } + +.fa-star::before { + content: "\f005"; } + +.fa-repeat::before { + content: "\f363"; } + +.fa-cross::before { + content: "\f654"; } + +.fa-box::before { + content: "\f466"; } + +.fa-venus-mars::before { + content: "\f228"; } + +.fa-arrow-pointer::before { + content: "\f245"; } + +.fa-mouse-pointer::before { + content: "\f245"; } + +.fa-maximize::before { + content: "\f31e"; } + +.fa-expand-arrows-alt::before { + content: "\f31e"; } + +.fa-charging-station::before { + content: "\f5e7"; } + +.fa-shapes::before { + content: "\f61f"; } + +.fa-triangle-circle-square::before { + content: "\f61f"; } + +.fa-shuffle::before { + content: "\f074"; } + +.fa-random::before { + content: "\f074"; } + +.fa-person-running::before { + content: "\f70c"; } + +.fa-running::before { + content: "\f70c"; } + +.fa-mobile-retro::before { + content: "\e527"; } + +.fa-grip-lines-vertical::before { + content: "\f7a5"; } + +.fa-spider::before { + content: "\f717"; } + +.fa-hands-bound::before { + content: "\e4f9"; } + +.fa-file-invoice-dollar::before { + content: "\f571"; } + +.fa-plane-circle-exclamation::before { + content: "\e556"; } + +.fa-x-ray::before { + content: "\f497"; } + +.fa-spell-check::before { + content: "\f891"; } + +.fa-slash::before { + content: "\f715"; } + +.fa-computer-mouse::before { + content: "\f8cc"; } + +.fa-mouse::before { + content: "\f8cc"; } + +.fa-arrow-right-to-bracket::before { + content: "\f090"; } + +.fa-sign-in::before { + content: "\f090"; } + +.fa-shop-slash::before { + content: "\e070"; } + +.fa-store-alt-slash::before { + content: "\e070"; } + +.fa-server::before { + content: "\f233"; } + +.fa-virus-covid-slash::before { + content: "\e4a9"; } + +.fa-shop-lock::before { + content: "\e4a5"; } + +.fa-hourglass-start::before { + content: "\f251"; } + +.fa-hourglass-1::before { + content: "\f251"; } + +.fa-blender-phone::before { + content: "\f6b6"; } + +.fa-building-wheat::before { + content: "\e4db"; } + +.fa-person-breastfeeding::before { + content: "\e53a"; } + +.fa-right-to-bracket::before { + content: "\f2f6"; } + +.fa-sign-in-alt::before { + content: "\f2f6"; } + +.fa-venus::before { + content: "\f221"; } + +.fa-passport::before { + content: "\f5ab"; } + +.fa-heart-pulse::before { + content: "\f21e"; } + +.fa-heartbeat::before { + content: "\f21e"; } + +.fa-people-carry-box::before { + content: "\f4ce"; } + +.fa-people-carry::before { + content: "\f4ce"; } + +.fa-temperature-high::before { + content: "\f769"; } + +.fa-microchip::before { + content: "\f2db"; } + +.fa-crown::before { + content: "\f521"; } + +.fa-weight-hanging::before { + content: "\f5cd"; } + +.fa-xmarks-lines::before { + content: "\e59a"; } + +.fa-file-prescription::before { + content: "\f572"; } + +.fa-weight-scale::before { + content: "\f496"; } + +.fa-weight::before { + content: "\f496"; } + +.fa-user-group::before { + content: "\f500"; } + +.fa-user-friends::before { + content: "\f500"; } + +.fa-arrow-up-a-z::before { + content: "\f15e"; } + +.fa-sort-alpha-up::before { + content: "\f15e"; } + +.fa-chess-knight::before { + content: "\f441"; } + +.fa-face-laugh-squint::before { + content: "\f59b"; } + +.fa-laugh-squint::before { + content: "\f59b"; } + +.fa-wheelchair::before { + content: "\f193"; } + +.fa-circle-arrow-up::before { + content: "\f0aa"; } + +.fa-arrow-circle-up::before { + content: "\f0aa"; } + +.fa-toggle-on::before { + content: "\f205"; } + +.fa-person-walking::before { + content: "\f554"; } + +.fa-walking::before { + content: "\f554"; } + +.fa-l::before { + content: "\4c"; } + +.fa-fire::before { + content: "\f06d"; } + +.fa-bed-pulse::before { + content: "\f487"; } + +.fa-procedures::before { + content: "\f487"; } + +.fa-shuttle-space::before { + content: "\f197"; } + +.fa-space-shuttle::before { + content: "\f197"; } + +.fa-face-laugh::before { + content: "\f599"; } + +.fa-laugh::before { + content: "\f599"; } + +.fa-folder-open::before { + content: "\f07c"; } + +.fa-heart-circle-plus::before { + content: "\e500"; } + +.fa-code-fork::before { + content: "\e13b"; } + +.fa-city::before { + content: "\f64f"; } + +.fa-microphone-lines::before { + content: "\f3c9"; } + +.fa-microphone-alt::before { + content: "\f3c9"; } + +.fa-pepper-hot::before { + content: "\f816"; } + +.fa-unlock::before { + content: "\f09c"; } + +.fa-colon-sign::before { + content: "\e140"; } + +.fa-headset::before { + content: "\f590"; } + +.fa-store-slash::before { + content: "\e071"; } + +.fa-road-circle-xmark::before { + content: "\e566"; } + +.fa-user-minus::before { + content: "\f503"; } + +.fa-mars-stroke-up::before { + content: "\f22a"; } + +.fa-mars-stroke-v::before { + content: "\f22a"; } + +.fa-champagne-glasses::before { + content: "\f79f"; } + +.fa-glass-cheers::before { + content: "\f79f"; } + +.fa-clipboard::before { + content: "\f328"; } + +.fa-house-circle-exclamation::before { + content: "\e50a"; } + +.fa-file-arrow-up::before { + content: "\f574"; } + +.fa-file-upload::before { + content: "\f574"; } + +.fa-wifi::before { + content: "\f1eb"; } + +.fa-wifi-3::before { + content: "\f1eb"; } + +.fa-wifi-strong::before { + content: "\f1eb"; } + +.fa-bath::before { + content: "\f2cd"; } + +.fa-bathtub::before { + content: "\f2cd"; } + +.fa-underline::before { + content: "\f0cd"; } + +.fa-user-pen::before { + content: "\f4ff"; } + +.fa-user-edit::before { + content: "\f4ff"; } + +.fa-signature::before { + content: "\f5b7"; } + +.fa-stroopwafel::before { + content: "\f551"; } + +.fa-bold::before { + content: "\f032"; } + +.fa-anchor-lock::before { + content: "\e4ad"; } + +.fa-building-ngo::before { + content: "\e4d7"; } + +.fa-manat-sign::before { + content: "\e1d5"; } + +.fa-not-equal::before { + content: "\f53e"; } + +.fa-border-top-left::before { + content: "\f853"; } + +.fa-border-style::before { + content: "\f853"; } + +.fa-map-location-dot::before { + content: "\f5a0"; } + +.fa-map-marked-alt::before { + content: "\f5a0"; } + +.fa-jedi::before { + content: "\f669"; } + +.fa-square-poll-vertical::before { + content: "\f681"; } + +.fa-poll::before { + content: "\f681"; } + +.fa-mug-hot::before { + content: "\f7b6"; } + +.fa-car-battery::before { + content: "\f5df"; } + +.fa-battery-car::before { + content: "\f5df"; } + +.fa-gift::before { + content: "\f06b"; } + +.fa-dice-two::before { + content: "\f528"; } + +.fa-chess-queen::before { + content: "\f445"; } + +.fa-glasses::before { + content: "\f530"; } + +.fa-chess-board::before { + content: "\f43c"; } + +.fa-building-circle-check::before { + content: "\e4d2"; } + +.fa-person-chalkboard::before { + content: "\e53d"; } + +.fa-mars-stroke-right::before { + content: "\f22b"; } + +.fa-mars-stroke-h::before { + content: "\f22b"; } + +.fa-hand-back-fist::before { + content: "\f255"; } + +.fa-hand-rock::before { + content: "\f255"; } + +.fa-square-caret-up::before { + content: "\f151"; } + +.fa-caret-square-up::before { + content: "\f151"; } + +.fa-cloud-showers-water::before { + content: "\e4e4"; } + +.fa-chart-bar::before { + content: "\f080"; } + +.fa-bar-chart::before { + content: "\f080"; } + +.fa-hands-bubbles::before { + content: "\e05e"; } + +.fa-hands-wash::before { + content: "\e05e"; } + +.fa-less-than-equal::before { + content: "\f537"; } + +.fa-train::before { + content: "\f238"; } + +.fa-eye-low-vision::before { + content: "\f2a8"; } + +.fa-low-vision::before { + content: "\f2a8"; } + +.fa-crow::before { + content: "\f520"; } + +.fa-sailboat::before { + content: "\e445"; } + +.fa-window-restore::before { + content: "\f2d2"; } + +.fa-square-plus::before { + content: "\f0fe"; } + +.fa-plus-square::before { + content: "\f0fe"; } + +.fa-torii-gate::before { + content: "\f6a1"; } + +.fa-frog::before { + content: "\f52e"; } + +.fa-bucket::before { + content: "\e4cf"; } + +.fa-image::before { + content: "\f03e"; } + +.fa-microphone::before { + content: "\f130"; } + +.fa-cow::before { + content: "\f6c8"; } + +.fa-caret-up::before { + content: "\f0d8"; } + +.fa-screwdriver::before { + content: "\f54a"; } + +.fa-folder-closed::before { + content: "\e185"; } + +.fa-house-tsunami::before { + content: "\e515"; } + +.fa-square-nfi::before { + content: "\e576"; } + +.fa-arrow-up-from-ground-water::before { + content: "\e4b5"; } + +.fa-martini-glass::before { + content: "\f57b"; } + +.fa-glass-martini-alt::before { + content: "\f57b"; } + +.fa-rotate-left::before { + content: "\f2ea"; } + +.fa-rotate-back::before { + content: "\f2ea"; } + +.fa-rotate-backward::before { + content: "\f2ea"; } + +.fa-undo-alt::before { + content: "\f2ea"; } + +.fa-table-columns::before { + content: "\f0db"; } + +.fa-columns::before { + content: "\f0db"; } + +.fa-lemon::before { + content: "\f094"; } + +.fa-head-side-mask::before { + content: "\e063"; } + +.fa-handshake::before { + content: "\f2b5"; } + +.fa-gem::before { + content: "\f3a5"; } + +.fa-dolly::before { + content: "\f472"; } + +.fa-dolly-box::before { + content: "\f472"; } + +.fa-smoking::before { + content: "\f48d"; } + +.fa-minimize::before { + content: "\f78c"; } + +.fa-compress-arrows-alt::before { + content: "\f78c"; } + +.fa-monument::before { + content: "\f5a6"; } + +.fa-snowplow::before { + content: "\f7d2"; } + +.fa-angles-right::before { + content: "\f101"; } + +.fa-angle-double-right::before { + content: "\f101"; } + +.fa-cannabis::before { + content: "\f55f"; } + +.fa-circle-play::before { + content: "\f144"; } + +.fa-play-circle::before { + content: "\f144"; } + +.fa-tablets::before { + content: "\f490"; } + +.fa-ethernet::before { + content: "\f796"; } + +.fa-euro-sign::before { + content: "\f153"; } + +.fa-eur::before { + content: "\f153"; } + +.fa-euro::before { + content: "\f153"; } + +.fa-chair::before { + content: "\f6c0"; } + +.fa-circle-check::before { + content: "\f058"; } + +.fa-check-circle::before { + content: "\f058"; } + +.fa-circle-stop::before { + content: "\f28d"; } + +.fa-stop-circle::before { + content: "\f28d"; } + +.fa-compass-drafting::before { + content: "\f568"; } + +.fa-drafting-compass::before { + content: "\f568"; } + +.fa-plate-wheat::before { + content: "\e55a"; } + +.fa-icicles::before { + content: "\f7ad"; } + +.fa-person-shelter::before { + content: "\e54f"; } + +.fa-neuter::before { + content: "\f22c"; } + +.fa-id-badge::before { + content: "\f2c1"; } + +.fa-marker::before { + content: "\f5a1"; } + +.fa-face-laugh-beam::before { + content: "\f59a"; } + +.fa-laugh-beam::before { + content: "\f59a"; } + +.fa-helicopter-symbol::before { + content: "\e502"; } + +.fa-universal-access::before { + content: "\f29a"; } + +.fa-circle-chevron-up::before { + content: "\f139"; } + +.fa-chevron-circle-up::before { + content: "\f139"; } + +.fa-lari-sign::before { + content: "\e1c8"; } + +.fa-volcano::before { + content: "\f770"; } + +.fa-person-walking-dashed-line-arrow-right::before { + content: "\e553"; } + +.fa-sterling-sign::before { + content: "\f154"; } + +.fa-gbp::before { + content: "\f154"; } + +.fa-pound-sign::before { + content: "\f154"; } + +.fa-viruses::before { + content: "\e076"; } + +.fa-square-person-confined::before { + content: "\e577"; } + +.fa-user-tie::before { + content: "\f508"; } + +.fa-arrow-down-long::before { + content: "\f175"; } + +.fa-long-arrow-down::before { + content: "\f175"; } + +.fa-tent-arrow-down-to-line::before { + content: "\e57e"; } + +.fa-certificate::before { + content: "\f0a3"; } + +.fa-reply-all::before { + content: "\f122"; } + +.fa-mail-reply-all::before { + content: "\f122"; } + +.fa-suitcase::before { + content: "\f0f2"; } + +.fa-person-skating::before { + content: "\f7c5"; } + +.fa-skating::before { + content: "\f7c5"; } + +.fa-filter-circle-dollar::before { + content: "\f662"; } + +.fa-funnel-dollar::before { + content: "\f662"; } + +.fa-camera-retro::before { + content: "\f083"; } + +.fa-circle-arrow-down::before { + content: "\f0ab"; } + +.fa-arrow-circle-down::before { + content: "\f0ab"; } + +.fa-file-import::before { + content: "\f56f"; } + +.fa-arrow-right-to-file::before { + content: "\f56f"; } + +.fa-square-arrow-up-right::before { + content: "\f14c"; } + +.fa-external-link-square::before { + content: "\f14c"; } + +.fa-box-open::before { + content: "\f49e"; } + +.fa-scroll::before { + content: "\f70e"; } + +.fa-spa::before { + content: "\f5bb"; } + +.fa-location-pin-lock::before { + content: "\e51f"; } + +.fa-pause::before { + content: "\f04c"; } + +.fa-hill-avalanche::before { + content: "\e507"; } + +.fa-temperature-empty::before { + content: "\f2cb"; } + +.fa-temperature-0::before { + content: "\f2cb"; } + +.fa-thermometer-0::before { + content: "\f2cb"; } + +.fa-thermometer-empty::before { + content: "\f2cb"; } + +.fa-bomb::before { + content: "\f1e2"; } + +.fa-registered::before { + content: "\f25d"; } + +.fa-address-card::before { + content: "\f2bb"; } + +.fa-contact-card::before { + content: "\f2bb"; } + +.fa-vcard::before { + content: "\f2bb"; } + +.fa-scale-unbalanced-flip::before { + content: "\f516"; } + +.fa-balance-scale-right::before { + content: "\f516"; } + +.fa-subscript::before { + content: "\f12c"; } + +.fa-diamond-turn-right::before { + content: "\f5eb"; } + +.fa-directions::before { + content: "\f5eb"; } + +.fa-burst::before { + content: "\e4dc"; } + +.fa-house-laptop::before { + content: "\e066"; } + +.fa-laptop-house::before { + content: "\e066"; } + +.fa-face-tired::before { + content: "\f5c8"; } + +.fa-tired::before { + content: "\f5c8"; } + +.fa-money-bills::before { + content: "\e1f3"; } + +.fa-smog::before { + content: "\f75f"; } + +.fa-crutch::before { + content: "\f7f7"; } + +.fa-cloud-arrow-up::before { + content: "\f0ee"; } + +.fa-cloud-upload::before { + content: "\f0ee"; } + +.fa-cloud-upload-alt::before { + content: "\f0ee"; } + +.fa-palette::before { + content: "\f53f"; } + +.fa-arrows-turn-right::before { + content: "\e4c0"; } + +.fa-vest::before { + content: "\e085"; } + +.fa-ferry::before { + content: "\e4ea"; } + +.fa-arrows-down-to-people::before { + content: "\e4b9"; } + +.fa-seedling::before { + content: "\f4d8"; } + +.fa-sprout::before { + content: "\f4d8"; } + +.fa-left-right::before { + content: "\f337"; } + +.fa-arrows-alt-h::before { + content: "\f337"; } + +.fa-boxes-packing::before { + content: "\e4c7"; } + +.fa-circle-arrow-left::before { + content: "\f0a8"; } + +.fa-arrow-circle-left::before { + content: "\f0a8"; } + +.fa-group-arrows-rotate::before { + content: "\e4f6"; } + +.fa-bowl-food::before { + content: "\e4c6"; } + +.fa-candy-cane::before { + content: "\f786"; } + +.fa-arrow-down-wide-short::before { + content: "\f160"; } + +.fa-sort-amount-asc::before { + content: "\f160"; } + +.fa-sort-amount-down::before { + content: "\f160"; } + +.fa-cloud-bolt::before { + content: "\f76c"; } + +.fa-thunderstorm::before { + content: "\f76c"; } + +.fa-text-slash::before { + content: "\f87d"; } + +.fa-remove-format::before { + content: "\f87d"; } + +.fa-face-smile-wink::before { + content: "\f4da"; } + +.fa-smile-wink::before { + content: "\f4da"; } + +.fa-file-word::before { + content: "\f1c2"; } + +.fa-file-powerpoint::before { + content: "\f1c4"; } + +.fa-arrows-left-right::before { + content: "\f07e"; } + +.fa-arrows-h::before { + content: "\f07e"; } + +.fa-house-lock::before { + content: "\e510"; } + +.fa-cloud-arrow-down::before { + content: "\f0ed"; } + +.fa-cloud-download::before { + content: "\f0ed"; } + +.fa-cloud-download-alt::before { + content: "\f0ed"; } + +.fa-children::before { + content: "\e4e1"; } + +.fa-chalkboard::before { + content: "\f51b"; } + +.fa-blackboard::before { + content: "\f51b"; } + +.fa-user-large-slash::before { + content: "\f4fa"; } + +.fa-user-alt-slash::before { + content: "\f4fa"; } + +.fa-envelope-open::before { + content: "\f2b6"; } + +.fa-handshake-simple-slash::before { + content: "\e05f"; } + +.fa-handshake-alt-slash::before { + content: "\e05f"; } + +.fa-mattress-pillow::before { + content: "\e525"; } + +.fa-guarani-sign::before { + content: "\e19a"; } + +.fa-arrows-rotate::before { + content: "\f021"; } + +.fa-refresh::before { + content: "\f021"; } + +.fa-sync::before { + content: "\f021"; } + +.fa-fire-extinguisher::before { + content: "\f134"; } + +.fa-cruzeiro-sign::before { + content: "\e152"; } + +.fa-greater-than-equal::before { + content: "\f532"; } + +.fa-shield-halved::before { + content: "\f3ed"; } + +.fa-shield-alt::before { + content: "\f3ed"; } + +.fa-book-atlas::before { + content: "\f558"; } + +.fa-atlas::before { + content: "\f558"; } + +.fa-virus::before { + content: "\e074"; } + +.fa-envelope-circle-check::before { + content: "\e4e8"; } + +.fa-layer-group::before { + content: "\f5fd"; } + +.fa-arrows-to-dot::before { + content: "\e4be"; } + +.fa-archway::before { + content: "\f557"; } + +.fa-heart-circle-check::before { + content: "\e4fd"; } + +.fa-house-chimney-crack::before { + content: "\f6f1"; } + +.fa-house-damage::before { + content: "\f6f1"; } + +.fa-file-zipper::before { + content: "\f1c6"; } + +.fa-file-archive::before { + content: "\f1c6"; } + +.fa-square::before { + content: "\f0c8"; } + +.fa-martini-glass-empty::before { + content: "\f000"; } + +.fa-glass-martini::before { + content: "\f000"; } + +.fa-couch::before { + content: "\f4b8"; } + +.fa-cedi-sign::before { + content: "\e0df"; } + +.fa-italic::before { + content: "\f033"; } + +.fa-church::before { + content: "\f51d"; } + +.fa-comments-dollar::before { + content: "\f653"; } + +.fa-democrat::before { + content: "\f747"; } + +.fa-z::before { + content: "\5a"; } + +.fa-person-skiing::before { + content: "\f7c9"; } + +.fa-skiing::before { + content: "\f7c9"; } + +.fa-road-lock::before { + content: "\e567"; } + +.fa-a::before { + content: "\41"; } + +.fa-temperature-arrow-down::before { + content: "\e03f"; } + +.fa-temperature-down::before { + content: "\e03f"; } + +.fa-feather-pointed::before { + content: "\f56b"; } + +.fa-feather-alt::before { + content: "\f56b"; } + +.fa-p::before { + content: "\50"; } + +.fa-snowflake::before { + content: "\f2dc"; } + +.fa-newspaper::before { + content: "\f1ea"; } + +.fa-rectangle-ad::before { + content: "\f641"; } + +.fa-ad::before { + content: "\f641"; } + +.fa-circle-arrow-right::before { + content: "\f0a9"; } + +.fa-arrow-circle-right::before { + content: "\f0a9"; } + +.fa-filter-circle-xmark::before { + content: "\e17b"; } + +.fa-locust::before { + content: "\e520"; } + +.fa-sort::before { + content: "\f0dc"; } + +.fa-unsorted::before { + content: "\f0dc"; } + +.fa-list-ol::before { + content: "\f0cb"; } + +.fa-list-1-2::before { + content: "\f0cb"; } + +.fa-list-numeric::before { + content: "\f0cb"; } + +.fa-person-dress-burst::before { + content: "\e544"; } + +.fa-money-check-dollar::before { + content: "\f53d"; } + +.fa-money-check-alt::before { + content: "\f53d"; } + +.fa-vector-square::before { + content: "\f5cb"; } + +.fa-bread-slice::before { + content: "\f7ec"; } + +.fa-language::before { + content: "\f1ab"; } + +.fa-face-kiss-wink-heart::before { + content: "\f598"; } + +.fa-kiss-wink-heart::before { + content: "\f598"; } + +.fa-filter::before { + content: "\f0b0"; } + +.fa-question::before { + content: "\3f"; } + +.fa-file-signature::before { + content: "\f573"; } + +.fa-up-down-left-right::before { + content: "\f0b2"; } + +.fa-arrows-alt::before { + content: "\f0b2"; } + +.fa-house-chimney-user::before { + content: "\e065"; } + +.fa-hand-holding-heart::before { + content: "\f4be"; } + +.fa-puzzle-piece::before { + content: "\f12e"; } + +.fa-money-check::before { + content: "\f53c"; } + +.fa-star-half-stroke::before { + content: "\f5c0"; } + +.fa-star-half-alt::before { + content: "\f5c0"; } + +.fa-code::before { + content: "\f121"; } + +.fa-whiskey-glass::before { + content: "\f7a0"; } + +.fa-glass-whiskey::before { + content: "\f7a0"; } + +.fa-building-circle-exclamation::before { + content: "\e4d3"; } + +.fa-magnifying-glass-chart::before { + content: "\e522"; } + +.fa-arrow-up-right-from-square::before { + content: "\f08e"; } + +.fa-external-link::before { + content: "\f08e"; } + +.fa-cubes-stacked::before { + content: "\e4e6"; } + +.fa-won-sign::before { + content: "\f159"; } + +.fa-krw::before { + content: "\f159"; } + +.fa-won::before { + content: "\f159"; } + +.fa-virus-covid::before { + content: "\e4a8"; } + +.fa-austral-sign::before { + content: "\e0a9"; } + +.fa-f::before { + content: "\46"; } + +.fa-leaf::before { + content: "\f06c"; } + +.fa-road::before { + content: "\f018"; } + +.fa-taxi::before { + content: "\f1ba"; } + +.fa-cab::before { + content: "\f1ba"; } + +.fa-person-circle-plus::before { + content: "\e541"; } + +.fa-chart-pie::before { + content: "\f200"; } + +.fa-pie-chart::before { + content: "\f200"; } + +.fa-bolt-lightning::before { + content: "\e0b7"; } + +.fa-sack-xmark::before { + content: "\e56a"; } + +.fa-file-excel::before { + content: "\f1c3"; } + +.fa-file-contract::before { + content: "\f56c"; } + +.fa-fish-fins::before { + content: "\e4f2"; } + +.fa-building-flag::before { + content: "\e4d5"; } + +.fa-face-grin-beam::before { + content: "\f582"; } + +.fa-grin-beam::before { + content: "\f582"; } + +.fa-object-ungroup::before { + content: "\f248"; } + +.fa-poop::before { + content: "\f619"; } + +.fa-location-pin::before { + content: "\f041"; } + +.fa-map-marker::before { + content: "\f041"; } + +.fa-kaaba::before { + content: "\f66b"; } + +.fa-toilet-paper::before { + content: "\f71e"; } + +.fa-helmet-safety::before { + content: "\f807"; } + +.fa-hard-hat::before { + content: "\f807"; } + +.fa-hat-hard::before { + content: "\f807"; } + +.fa-eject::before { + content: "\f052"; } + +.fa-circle-right::before { + content: "\f35a"; } + +.fa-arrow-alt-circle-right::before { + content: "\f35a"; } + +.fa-plane-circle-check::before { + content: "\e555"; } + +.fa-face-rolling-eyes::before { + content: "\f5a5"; } + +.fa-meh-rolling-eyes::before { + content: "\f5a5"; } + +.fa-object-group::before { + content: "\f247"; } + +.fa-chart-line::before { + content: "\f201"; } + +.fa-line-chart::before { + content: "\f201"; } + +.fa-mask-ventilator::before { + content: "\e524"; } + +.fa-arrow-right::before { + content: "\f061"; } + +.fa-signs-post::before { + content: "\f277"; } + +.fa-map-signs::before { + content: "\f277"; } + +.fa-cash-register::before { + content: "\f788"; } + +.fa-person-circle-question::before { + content: "\e542"; } + +.fa-h::before { + content: "\48"; } + +.fa-tarp::before { + content: "\e57b"; } + +.fa-screwdriver-wrench::before { + content: "\f7d9"; } + +.fa-tools::before { + content: "\f7d9"; } + +.fa-arrows-to-eye::before { + content: "\e4bf"; } + +.fa-plug-circle-bolt::before { + content: "\e55b"; } + +.fa-heart::before { + content: "\f004"; } + +.fa-mars-and-venus::before { + content: "\f224"; } + +.fa-house-user::before { + content: "\e1b0"; } + +.fa-home-user::before { + content: "\e1b0"; } + +.fa-dumpster-fire::before { + content: "\f794"; } + +.fa-house-crack::before { + content: "\e3b1"; } + +.fa-martini-glass-citrus::before { + content: "\f561"; } + +.fa-cocktail::before { + content: "\f561"; } + +.fa-face-surprise::before { + content: "\f5c2"; } + +.fa-surprise::before { + content: "\f5c2"; } + +.fa-bottle-water::before { + content: "\e4c5"; } + +.fa-circle-pause::before { + content: "\f28b"; } + +.fa-pause-circle::before { + content: "\f28b"; } + +.fa-toilet-paper-slash::before { + content: "\e072"; } + +.fa-apple-whole::before { + content: "\f5d1"; } + +.fa-apple-alt::before { + content: "\f5d1"; } + +.fa-kitchen-set::before { + content: "\e51a"; } + +.fa-r::before { + content: "\52"; } + +.fa-temperature-quarter::before { + content: "\f2ca"; } + +.fa-temperature-1::before { + content: "\f2ca"; } + +.fa-thermometer-1::before { + content: "\f2ca"; } + +.fa-thermometer-quarter::before { + content: "\f2ca"; } + +.fa-cube::before { + content: "\f1b2"; } + +.fa-bitcoin-sign::before { + content: "\e0b4"; } + +.fa-shield-dog::before { + content: "\e573"; } + +.fa-solar-panel::before { + content: "\f5ba"; } + +.fa-lock-open::before { + content: "\f3c1"; } + +.fa-elevator::before { + content: "\e16d"; } + +.fa-money-bill-transfer::before { + content: "\e528"; } + +.fa-money-bill-trend-up::before { + content: "\e529"; } + +.fa-house-flood-water-circle-arrow-right::before { + content: "\e50f"; } + +.fa-square-poll-horizontal::before { + content: "\f682"; } + +.fa-poll-h::before { + content: "\f682"; } + +.fa-circle::before { + content: "\f111"; } + +.fa-backward-fast::before { + content: "\f049"; } + +.fa-fast-backward::before { + content: "\f049"; } + +.fa-recycle::before { + content: "\f1b8"; } + +.fa-user-astronaut::before { + content: "\f4fb"; } + +.fa-plane-slash::before { + content: "\e069"; } + +.fa-trademark::before { + content: "\f25c"; } + +.fa-basketball::before { + content: "\f434"; } + +.fa-basketball-ball::before { + content: "\f434"; } + +.fa-satellite-dish::before { + content: "\f7c0"; } + +.fa-circle-up::before { + content: "\f35b"; } + +.fa-arrow-alt-circle-up::before { + content: "\f35b"; } + +.fa-mobile-screen-button::before { + content: "\f3cd"; } + +.fa-mobile-alt::before { + content: "\f3cd"; } + +.fa-volume-high::before { + content: "\f028"; } + +.fa-volume-up::before { + content: "\f028"; } + +.fa-users-rays::before { + content: "\e593"; } + +.fa-wallet::before { + content: "\f555"; } + +.fa-clipboard-check::before { + content: "\f46c"; } + +.fa-file-audio::before { + content: "\f1c7"; } + +.fa-burger::before { + content: "\f805"; } + +.fa-hamburger::before { + content: "\f805"; } + +.fa-wrench::before { + content: "\f0ad"; } + +.fa-bugs::before { + content: "\e4d0"; } + +.fa-rupee-sign::before { + content: "\f156"; } + +.fa-rupee::before { + content: "\f156"; } + +.fa-file-image::before { + content: "\f1c5"; } + +.fa-circle-question::before { + content: "\f059"; } + +.fa-question-circle::before { + content: "\f059"; } + +.fa-plane-departure::before { + content: "\f5b0"; } + +.fa-handshake-slash::before { + content: "\e060"; } + +.fa-book-bookmark::before { + content: "\e0bb"; } + +.fa-code-branch::before { + content: "\f126"; } + +.fa-hat-cowboy::before { + content: "\f8c0"; } + +.fa-bridge::before { + content: "\e4c8"; } + +.fa-phone-flip::before { + content: "\f879"; } + +.fa-phone-alt::before { + content: "\f879"; } + +.fa-truck-front::before { + content: "\e2b7"; } + +.fa-cat::before { + content: "\f6be"; } + +.fa-anchor-circle-exclamation::before { + content: "\e4ab"; } + +.fa-truck-field::before { + content: "\e58d"; } + +.fa-route::before { + content: "\f4d7"; } + +.fa-clipboard-question::before { + content: "\e4e3"; } + +.fa-panorama::before { + content: "\e209"; } + +.fa-comment-medical::before { + content: "\f7f5"; } + +.fa-teeth-open::before { + content: "\f62f"; } + +.fa-file-circle-minus::before { + content: "\e4ed"; } + +.fa-tags::before { + content: "\f02c"; } + +.fa-wine-glass::before { + content: "\f4e3"; } + +.fa-forward-fast::before { + content: "\f050"; } + +.fa-fast-forward::before { + content: "\f050"; } + +.fa-face-meh-blank::before { + content: "\f5a4"; } + +.fa-meh-blank::before { + content: "\f5a4"; } + +.fa-square-parking::before { + content: "\f540"; } + +.fa-parking::before { + content: "\f540"; } + +.fa-house-signal::before { + content: "\e012"; } + +.fa-bars-progress::before { + content: "\f828"; } + +.fa-tasks-alt::before { + content: "\f828"; } + +.fa-faucet-drip::before { + content: "\e006"; } + +.fa-cart-flatbed::before { + content: "\f474"; } + +.fa-dolly-flatbed::before { + content: "\f474"; } + +.fa-ban-smoking::before { + content: "\f54d"; } + +.fa-smoking-ban::before { + content: "\f54d"; } + +.fa-terminal::before { + content: "\f120"; } + +.fa-mobile-button::before { + content: "\f10b"; } + +.fa-house-medical-flag::before { + content: "\e514"; } + +.fa-basket-shopping::before { + content: "\f291"; } + +.fa-shopping-basket::before { + content: "\f291"; } + +.fa-tape::before { + content: "\f4db"; } + +.fa-bus-simple::before { + content: "\f55e"; } + +.fa-bus-alt::before { + content: "\f55e"; } + +.fa-eye::before { + content: "\f06e"; } + +.fa-face-sad-cry::before { + content: "\f5b3"; } + +.fa-sad-cry::before { + content: "\f5b3"; } + +.fa-audio-description::before { + content: "\f29e"; } + +.fa-person-military-to-person::before { + content: "\e54c"; } + +.fa-file-shield::before { + content: "\e4f0"; } + +.fa-user-slash::before { + content: "\f506"; } + +.fa-pen::before { + content: "\f304"; } + +.fa-tower-observation::before { + content: "\e586"; } + +.fa-file-code::before { + content: "\f1c9"; } + +.fa-signal::before { + content: "\f012"; } + +.fa-signal-5::before { + content: "\f012"; } + +.fa-signal-perfect::before { + content: "\f012"; } + +.fa-bus::before { + content: "\f207"; } + +.fa-heart-circle-xmark::before { + content: "\e501"; } + +.fa-house-chimney::before { + content: "\e3af"; } + +.fa-home-lg::before { + content: "\e3af"; } + +.fa-window-maximize::before { + content: "\f2d0"; } + +.fa-face-frown::before { + content: "\f119"; } + +.fa-frown::before { + content: "\f119"; } + +.fa-prescription::before { + content: "\f5b1"; } + +.fa-shop::before { + content: "\f54f"; } + +.fa-store-alt::before { + content: "\f54f"; } + +.fa-floppy-disk::before { + content: "\f0c7"; } + +.fa-save::before { + content: "\f0c7"; } + +.fa-vihara::before { + content: "\f6a7"; } + +.fa-scale-unbalanced::before { + content: "\f515"; } + +.fa-balance-scale-left::before { + content: "\f515"; } + +.fa-sort-up::before { + content: "\f0de"; } + +.fa-sort-asc::before { + content: "\f0de"; } + +.fa-comment-dots::before { + content: "\f4ad"; } + +.fa-commenting::before { + content: "\f4ad"; } + +.fa-plant-wilt::before { + content: "\e5aa"; } + +.fa-diamond::before { + content: "\f219"; } + +.fa-face-grin-squint::before { + content: "\f585"; } + +.fa-grin-squint::before { + content: "\f585"; } + +.fa-hand-holding-dollar::before { + content: "\f4c0"; } + +.fa-hand-holding-usd::before { + content: "\f4c0"; } + +.fa-bacterium::before { + content: "\e05a"; } + +.fa-hand-pointer::before { + content: "\f25a"; } + +.fa-drum-steelpan::before { + content: "\f56a"; } + +.fa-hand-scissors::before { + content: "\f257"; } + +.fa-hands-praying::before { + content: "\f684"; } + +.fa-praying-hands::before { + content: "\f684"; } + +.fa-arrow-rotate-right::before { + content: "\f01e"; } + +.fa-arrow-right-rotate::before { + content: "\f01e"; } + +.fa-arrow-rotate-forward::before { + content: "\f01e"; } + +.fa-redo::before { + content: "\f01e"; } + +.fa-biohazard::before { + content: "\f780"; } + +.fa-location-crosshairs::before { + content: "\f601"; } + +.fa-location::before { + content: "\f601"; } + +.fa-mars-double::before { + content: "\f227"; } + +.fa-child-dress::before { + content: "\e59c"; } + +.fa-users-between-lines::before { + content: "\e591"; } + +.fa-lungs-virus::before { + content: "\e067"; } + +.fa-face-grin-tears::before { + content: "\f588"; } + +.fa-grin-tears::before { + content: "\f588"; } + +.fa-phone::before { + content: "\f095"; } + +.fa-calendar-xmark::before { + content: "\f273"; } + +.fa-calendar-times::before { + content: "\f273"; } + +.fa-child-reaching::before { + content: "\e59d"; } + +.fa-head-side-virus::before { + content: "\e064"; } + +.fa-user-gear::before { + content: "\f4fe"; } + +.fa-user-cog::before { + content: "\f4fe"; } + +.fa-arrow-up-1-9::before { + content: "\f163"; } + +.fa-sort-numeric-up::before { + content: "\f163"; } + +.fa-door-closed::before { + content: "\f52a"; } + +.fa-shield-virus::before { + content: "\e06c"; } + +.fa-dice-six::before { + content: "\f526"; } + +.fa-mosquito-net::before { + content: "\e52c"; } + +.fa-bridge-water::before { + content: "\e4ce"; } + +.fa-person-booth::before { + content: "\f756"; } + +.fa-text-width::before { + content: "\f035"; } + +.fa-hat-wizard::before { + content: "\f6e8"; } + +.fa-pen-fancy::before { + content: "\f5ac"; } + +.fa-person-digging::before { + content: "\f85e"; } + +.fa-digging::before { + content: "\f85e"; } + +.fa-trash::before { + content: "\f1f8"; } + +.fa-gauge-simple::before { + content: "\f629"; } + +.fa-gauge-simple-med::before { + content: "\f629"; } + +.fa-tachometer-average::before { + content: "\f629"; } + +.fa-book-medical::before { + content: "\f7e6"; } + +.fa-poo::before { + content: "\f2fe"; } + +.fa-quote-right::before { + content: "\f10e"; } + +.fa-quote-right-alt::before { + content: "\f10e"; } + +.fa-shirt::before { + content: "\f553"; } + +.fa-t-shirt::before { + content: "\f553"; } + +.fa-tshirt::before { + content: "\f553"; } + +.fa-cubes::before { + content: "\f1b3"; } + +.fa-divide::before { + content: "\f529"; } + +.fa-tenge-sign::before { + content: "\f7d7"; } + +.fa-tenge::before { + content: "\f7d7"; } + +.fa-headphones::before { + content: "\f025"; } + +.fa-hands-holding::before { + content: "\f4c2"; } + +.fa-hands-clapping::before { + content: "\e1a8"; } + +.fa-republican::before { + content: "\f75e"; } + +.fa-arrow-left::before { + content: "\f060"; } + +.fa-person-circle-xmark::before { + content: "\e543"; } + +.fa-ruler::before { + content: "\f545"; } + +.fa-align-left::before { + content: "\f036"; } + +.fa-dice-d6::before { + content: "\f6d1"; } + +.fa-restroom::before { + content: "\f7bd"; } + +.fa-j::before { + content: "\4a"; } + +.fa-users-viewfinder::before { + content: "\e595"; } + +.fa-file-video::before { + content: "\f1c8"; } + +.fa-up-right-from-square::before { + content: "\f35d"; } + +.fa-external-link-alt::before { + content: "\f35d"; } + +.fa-table-cells::before { + content: "\f00a"; } + +.fa-th::before { + content: "\f00a"; } + +.fa-file-pdf::before { + content: "\f1c1"; } + +.fa-book-bible::before { + content: "\f647"; } + +.fa-bible::before { + content: "\f647"; } + +.fa-o::before { + content: "\4f"; } + +.fa-suitcase-medical::before { + content: "\f0fa"; } + +.fa-medkit::before { + content: "\f0fa"; } + +.fa-user-secret::before { + content: "\f21b"; } + +.fa-otter::before { + content: "\f700"; } + +.fa-person-dress::before { + content: "\f182"; } + +.fa-female::before { + content: "\f182"; } + +.fa-comment-dollar::before { + content: "\f651"; } + +.fa-business-time::before { + content: "\f64a"; } + +.fa-briefcase-clock::before { + content: "\f64a"; } + +.fa-table-cells-large::before { + content: "\f009"; } + +.fa-th-large::before { + content: "\f009"; } + +.fa-book-tanakh::before { + content: "\f827"; } + +.fa-tanakh::before { + content: "\f827"; } + +.fa-phone-volume::before { + content: "\f2a0"; } + +.fa-volume-control-phone::before { + content: "\f2a0"; } + +.fa-hat-cowboy-side::before { + content: "\f8c1"; } + +.fa-clipboard-user::before { + content: "\f7f3"; } + +.fa-child::before { + content: "\f1ae"; } + +.fa-lira-sign::before { + content: "\f195"; } + +.fa-satellite::before { + content: "\f7bf"; } + +.fa-plane-lock::before { + content: "\e558"; } + +.fa-tag::before { + content: "\f02b"; } + +.fa-comment::before { + content: "\f075"; } + +.fa-cake-candles::before { + content: "\f1fd"; } + +.fa-birthday-cake::before { + content: "\f1fd"; } + +.fa-cake::before { + content: "\f1fd"; } + +.fa-envelope::before { + content: "\f0e0"; } + +.fa-angles-up::before { + content: "\f102"; } + +.fa-angle-double-up::before { + content: "\f102"; } + +.fa-paperclip::before { + content: "\f0c6"; } + +.fa-arrow-right-to-city::before { + content: "\e4b3"; } + +.fa-ribbon::before { + content: "\f4d6"; } + +.fa-lungs::before { + content: "\f604"; } + +.fa-arrow-up-9-1::before { + content: "\f887"; } + +.fa-sort-numeric-up-alt::before { + content: "\f887"; } + +.fa-litecoin-sign::before { + content: "\e1d3"; } + +.fa-border-none::before { + content: "\f850"; } + +.fa-circle-nodes::before { + content: "\e4e2"; } + +.fa-parachute-box::before { + content: "\f4cd"; } + +.fa-indent::before { + content: "\f03c"; } + +.fa-truck-field-un::before { + content: "\e58e"; } + +.fa-hourglass::before { + content: "\f254"; } + +.fa-hourglass-empty::before { + content: "\f254"; } + +.fa-mountain::before { + content: "\f6fc"; } + +.fa-user-doctor::before { + content: "\f0f0"; } + +.fa-user-md::before { + content: "\f0f0"; } + +.fa-circle-info::before { + content: "\f05a"; } + +.fa-info-circle::before { + content: "\f05a"; } + +.fa-cloud-meatball::before { + content: "\f73b"; } + +.fa-camera::before { + content: "\f030"; } + +.fa-camera-alt::before { + content: "\f030"; } + +.fa-square-virus::before { + content: "\e578"; } + +.fa-meteor::before { + content: "\f753"; } + +.fa-car-on::before { + content: "\e4dd"; } + +.fa-sleigh::before { + content: "\f7cc"; } + +.fa-arrow-down-1-9::before { + content: "\f162"; } + +.fa-sort-numeric-asc::before { + content: "\f162"; } + +.fa-sort-numeric-down::before { + content: "\f162"; } + +.fa-hand-holding-droplet::before { + content: "\f4c1"; } + +.fa-hand-holding-water::before { + content: "\f4c1"; } + +.fa-water::before { + content: "\f773"; } + +.fa-calendar-check::before { + content: "\f274"; } + +.fa-braille::before { + content: "\f2a1"; } + +.fa-prescription-bottle-medical::before { + content: "\f486"; } + +.fa-prescription-bottle-alt::before { + content: "\f486"; } + +.fa-landmark::before { + content: "\f66f"; } + +.fa-truck::before { + content: "\f0d1"; } + +.fa-crosshairs::before { + content: "\f05b"; } + +.fa-person-cane::before { + content: "\e53c"; } + +.fa-tent::before { + content: "\e57d"; } + +.fa-vest-patches::before { + content: "\e086"; } + +.fa-check-double::before { + content: "\f560"; } + +.fa-arrow-down-a-z::before { + content: "\f15d"; } + +.fa-sort-alpha-asc::before { + content: "\f15d"; } + +.fa-sort-alpha-down::before { + content: "\f15d"; } + +.fa-money-bill-wheat::before { + content: "\e52a"; } + +.fa-cookie::before { + content: "\f563"; } + +.fa-arrow-rotate-left::before { + content: "\f0e2"; } + +.fa-arrow-left-rotate::before { + content: "\f0e2"; } + +.fa-arrow-rotate-back::before { + content: "\f0e2"; } + +.fa-arrow-rotate-backward::before { + content: "\f0e2"; } + +.fa-undo::before { + content: "\f0e2"; } + +.fa-hard-drive::before { + content: "\f0a0"; } + +.fa-hdd::before { + content: "\f0a0"; } + +.fa-face-grin-squint-tears::before { + content: "\f586"; } + +.fa-grin-squint-tears::before { + content: "\f586"; } + +.fa-dumbbell::before { + content: "\f44b"; } + +.fa-rectangle-list::before { + content: "\f022"; } + +.fa-list-alt::before { + content: "\f022"; } + +.fa-tarp-droplet::before { + content: "\e57c"; } + +.fa-house-medical-circle-check::before { + content: "\e511"; } + +.fa-person-skiing-nordic::before { + content: "\f7ca"; } + +.fa-skiing-nordic::before { + content: "\f7ca"; } + +.fa-calendar-plus::before { + content: "\f271"; } + +.fa-plane-arrival::before { + content: "\f5af"; } + +.fa-circle-left::before { + content: "\f359"; } + +.fa-arrow-alt-circle-left::before { + content: "\f359"; } + +.fa-train-subway::before { + content: "\f239"; } + +.fa-subway::before { + content: "\f239"; } + +.fa-chart-gantt::before { + content: "\e0e4"; } + +.fa-indian-rupee-sign::before { + content: "\e1bc"; } + +.fa-indian-rupee::before { + content: "\e1bc"; } + +.fa-inr::before { + content: "\e1bc"; } + +.fa-crop-simple::before { + content: "\f565"; } + +.fa-crop-alt::before { + content: "\f565"; } + +.fa-money-bill-1::before { + content: "\f3d1"; } + +.fa-money-bill-alt::before { + content: "\f3d1"; } + +.fa-left-long::before { + content: "\f30a"; } + +.fa-long-arrow-alt-left::before { + content: "\f30a"; } + +.fa-dna::before { + content: "\f471"; } + +.fa-virus-slash::before { + content: "\e075"; } + +.fa-minus::before { + content: "\f068"; } + +.fa-subtract::before { + content: "\f068"; } + +.fa-chess::before { + content: "\f439"; } + +.fa-arrow-left-long::before { + content: "\f177"; } + +.fa-long-arrow-left::before { + content: "\f177"; } + +.fa-plug-circle-check::before { + content: "\e55c"; } + +.fa-street-view::before { + content: "\f21d"; } + +.fa-franc-sign::before { + content: "\e18f"; } + +.fa-volume-off::before { + content: "\f026"; } + +.fa-hands-asl-interpreting::before { + content: "\f2a3"; } + +.fa-american-sign-language-interpreting::before { + content: "\f2a3"; } + +.fa-asl-interpreting::before { + content: "\f2a3"; } + +.fa-hands-american-sign-language-interpreting::before { + content: "\f2a3"; } + +.fa-gear::before { + content: "\f013"; } + +.fa-cog::before { + content: "\f013"; } + +.fa-droplet-slash::before { + content: "\f5c7"; } + +.fa-tint-slash::before { + content: "\f5c7"; } + +.fa-mosque::before { + content: "\f678"; } + +.fa-mosquito::before { + content: "\e52b"; } + +.fa-star-of-david::before { + content: "\f69a"; } + +.fa-person-military-rifle::before { + content: "\e54b"; } + +.fa-cart-shopping::before { + content: "\f07a"; } + +.fa-shopping-cart::before { + content: "\f07a"; } + +.fa-vials::before { + content: "\f493"; } + +.fa-plug-circle-plus::before { + content: "\e55f"; } + +.fa-place-of-worship::before { + content: "\f67f"; } + +.fa-grip-vertical::before { + content: "\f58e"; } + +.fa-arrow-turn-up::before { + content: "\f148"; } + +.fa-level-up::before { + content: "\f148"; } + +.fa-u::before { + content: "\55"; } + +.fa-square-root-variable::before { + content: "\f698"; } + +.fa-square-root-alt::before { + content: "\f698"; } + +.fa-clock::before { + content: "\f017"; } + +.fa-clock-four::before { + content: "\f017"; } + +.fa-backward-step::before { + content: "\f048"; } + +.fa-step-backward::before { + content: "\f048"; } + +.fa-pallet::before { + content: "\f482"; } + +.fa-faucet::before { + content: "\e005"; } + +.fa-baseball-bat-ball::before { + content: "\f432"; } + +.fa-s::before { + content: "\53"; } + +.fa-timeline::before { + content: "\e29c"; } + +.fa-keyboard::before { + content: "\f11c"; } + +.fa-caret-down::before { + content: "\f0d7"; } + +.fa-house-chimney-medical::before { + content: "\f7f2"; } + +.fa-clinic-medical::before { + content: "\f7f2"; } + +.fa-temperature-three-quarters::before { + content: "\f2c8"; } + +.fa-temperature-3::before { + content: "\f2c8"; } + +.fa-thermometer-3::before { + content: "\f2c8"; } + +.fa-thermometer-three-quarters::before { + content: "\f2c8"; } + +.fa-mobile-screen::before { + content: "\f3cf"; } + +.fa-mobile-android-alt::before { + content: "\f3cf"; } + +.fa-plane-up::before { + content: "\e22d"; } + +.fa-piggy-bank::before { + content: "\f4d3"; } + +.fa-battery-half::before { + content: "\f242"; } + +.fa-battery-3::before { + content: "\f242"; } + +.fa-mountain-city::before { + content: "\e52e"; } + +.fa-coins::before { + content: "\f51e"; } + +.fa-khanda::before { + content: "\f66d"; } + +.fa-sliders::before { + content: "\f1de"; } + +.fa-sliders-h::before { + content: "\f1de"; } + +.fa-folder-tree::before { + content: "\f802"; } + +.fa-network-wired::before { + content: "\f6ff"; } + +.fa-map-pin::before { + content: "\f276"; } + +.fa-hamsa::before { + content: "\f665"; } + +.fa-cent-sign::before { + content: "\e3f5"; } + +.fa-flask::before { + content: "\f0c3"; } + +.fa-person-pregnant::before { + content: "\e31e"; } + +.fa-wand-sparkles::before { + content: "\f72b"; } + +.fa-ellipsis-vertical::before { + content: "\f142"; } + +.fa-ellipsis-v::before { + content: "\f142"; } + +.fa-ticket::before { + content: "\f145"; } + +.fa-power-off::before { + content: "\f011"; } + +.fa-right-long::before { + content: "\f30b"; } + +.fa-long-arrow-alt-right::before { + content: "\f30b"; } + +.fa-flag-usa::before { + content: "\f74d"; } + +.fa-laptop-file::before { + content: "\e51d"; } + +.fa-tty::before { + content: "\f1e4"; } + +.fa-teletype::before { + content: "\f1e4"; } + +.fa-diagram-next::before { + content: "\e476"; } + +.fa-person-rifle::before { + content: "\e54e"; } + +.fa-house-medical-circle-exclamation::before { + content: "\e512"; } + +.fa-closed-captioning::before { + content: "\f20a"; } + +.fa-person-hiking::before { + content: "\f6ec"; } + +.fa-hiking::before { + content: "\f6ec"; } + +.fa-venus-double::before { + content: "\f226"; } + +.fa-images::before { + content: "\f302"; } + +.fa-calculator::before { + content: "\f1ec"; } + +.fa-people-pulling::before { + content: "\e535"; } + +.fa-n::before { + content: "\4e"; } + +.fa-cable-car::before { + content: "\f7da"; } + +.fa-tram::before { + content: "\f7da"; } + +.fa-cloud-rain::before { + content: "\f73d"; } + +.fa-building-circle-xmark::before { + content: "\e4d4"; } + +.fa-ship::before { + content: "\f21a"; } + +.fa-arrows-down-to-line::before { + content: "\e4b8"; } + +.fa-download::before { + content: "\f019"; } + +.fa-face-grin::before { + content: "\f580"; } + +.fa-grin::before { + content: "\f580"; } + +.fa-delete-left::before { + content: "\f55a"; } + +.fa-backspace::before { + content: "\f55a"; } + +.fa-eye-dropper::before { + content: "\f1fb"; } + +.fa-eye-dropper-empty::before { + content: "\f1fb"; } + +.fa-eyedropper::before { + content: "\f1fb"; } + +.fa-file-circle-check::before { + content: "\e5a0"; } + +.fa-forward::before { + content: "\f04e"; } + +.fa-mobile::before { + content: "\f3ce"; } + +.fa-mobile-android::before { + content: "\f3ce"; } + +.fa-mobile-phone::before { + content: "\f3ce"; } + +.fa-face-meh::before { + content: "\f11a"; } + +.fa-meh::before { + content: "\f11a"; } + +.fa-align-center::before { + content: "\f037"; } + +.fa-book-skull::before { + content: "\f6b7"; } + +.fa-book-dead::before { + content: "\f6b7"; } + +.fa-id-card::before { + content: "\f2c2"; } + +.fa-drivers-license::before { + content: "\f2c2"; } + +.fa-outdent::before { + content: "\f03b"; } + +.fa-dedent::before { + content: "\f03b"; } + +.fa-heart-circle-exclamation::before { + content: "\e4fe"; } + +.fa-house::before { + content: "\f015"; } + +.fa-home::before { + content: "\f015"; } + +.fa-home-alt::before { + content: "\f015"; } + +.fa-home-lg-alt::before { + content: "\f015"; } + +.fa-calendar-week::before { + content: "\f784"; } + +.fa-laptop-medical::before { + content: "\f812"; } + +.fa-b::before { + content: "\42"; } + +.fa-file-medical::before { + content: "\f477"; } + +.fa-dice-one::before { + content: "\f525"; } + +.fa-kiwi-bird::before { + content: "\f535"; } + +.fa-arrow-right-arrow-left::before { + content: "\f0ec"; } + +.fa-exchange::before { + content: "\f0ec"; } + +.fa-rotate-right::before { + content: "\f2f9"; } + +.fa-redo-alt::before { + content: "\f2f9"; } + +.fa-rotate-forward::before { + content: "\f2f9"; } + +.fa-utensils::before { + content: "\f2e7"; } + +.fa-cutlery::before { + content: "\f2e7"; } + +.fa-arrow-up-wide-short::before { + content: "\f161"; } + +.fa-sort-amount-up::before { + content: "\f161"; } + +.fa-mill-sign::before { + content: "\e1ed"; } + +.fa-bowl-rice::before { + content: "\e2eb"; } + +.fa-skull::before { + content: "\f54c"; } + +.fa-tower-broadcast::before { + content: "\f519"; } + +.fa-broadcast-tower::before { + content: "\f519"; } + +.fa-truck-pickup::before { + content: "\f63c"; } + +.fa-up-long::before { + content: "\f30c"; } + +.fa-long-arrow-alt-up::before { + content: "\f30c"; } + +.fa-stop::before { + content: "\f04d"; } + +.fa-code-merge::before { + content: "\f387"; } + +.fa-upload::before { + content: "\f093"; } + +.fa-hurricane::before { + content: "\f751"; } + +.fa-mound::before { + content: "\e52d"; } + +.fa-toilet-portable::before { + content: "\e583"; } + +.fa-compact-disc::before { + content: "\f51f"; } + +.fa-file-arrow-down::before { + content: "\f56d"; } + +.fa-file-download::before { + content: "\f56d"; } + +.fa-caravan::before { + content: "\f8ff"; } + +.fa-shield-cat::before { + content: "\e572"; } + +.fa-bolt::before { + content: "\f0e7"; } + +.fa-zap::before { + content: "\f0e7"; } + +.fa-glass-water::before { + content: "\e4f4"; } + +.fa-oil-well::before { + content: "\e532"; } + +.fa-vault::before { + content: "\e2c5"; } + +.fa-mars::before { + content: "\f222"; } + +.fa-toilet::before { + content: "\f7d8"; } + +.fa-plane-circle-xmark::before { + content: "\e557"; } + +.fa-yen-sign::before { + content: "\f157"; } + +.fa-cny::before { + content: "\f157"; } + +.fa-jpy::before { + content: "\f157"; } + +.fa-rmb::before { + content: "\f157"; } + +.fa-yen::before { + content: "\f157"; } + +.fa-ruble-sign::before { + content: "\f158"; } + +.fa-rouble::before { + content: "\f158"; } + +.fa-rub::before { + content: "\f158"; } + +.fa-ruble::before { + content: "\f158"; } + +.fa-sun::before { + content: "\f185"; } + +.fa-guitar::before { + content: "\f7a6"; } + +.fa-face-laugh-wink::before { + content: "\f59c"; } + +.fa-laugh-wink::before { + content: "\f59c"; } + +.fa-horse-head::before { + content: "\f7ab"; } + +.fa-bore-hole::before { + content: "\e4c3"; } + +.fa-industry::before { + content: "\f275"; } + +.fa-circle-down::before { + content: "\f358"; } + +.fa-arrow-alt-circle-down::before { + content: "\f358"; } + +.fa-arrows-turn-to-dots::before { + content: "\e4c1"; } + +.fa-florin-sign::before { + content: "\e184"; } + +.fa-arrow-down-short-wide::before { + content: "\f884"; } + +.fa-sort-amount-desc::before { + content: "\f884"; } + +.fa-sort-amount-down-alt::before { + content: "\f884"; } + +.fa-less-than::before { + content: "\3c"; } + +.fa-angle-down::before { + content: "\f107"; } + +.fa-car-tunnel::before { + content: "\e4de"; } + +.fa-head-side-cough::before { + content: "\e061"; } + +.fa-grip-lines::before { + content: "\f7a4"; } + +.fa-thumbs-down::before { + content: "\f165"; } + +.fa-user-lock::before { + content: "\f502"; } + +.fa-arrow-right-long::before { + content: "\f178"; } + +.fa-long-arrow-right::before { + content: "\f178"; } + +.fa-anchor-circle-xmark::before { + content: "\e4ac"; } + +.fa-ellipsis::before { + content: "\f141"; } + +.fa-ellipsis-h::before { + content: "\f141"; } + +.fa-chess-pawn::before { + content: "\f443"; } + +.fa-kit-medical::before { + content: "\f479"; } + +.fa-first-aid::before { + content: "\f479"; } + +.fa-person-through-window::before { + content: "\e5a9"; } + +.fa-toolbox::before { + content: "\f552"; } + +.fa-hands-holding-circle::before { + content: "\e4fb"; } + +.fa-bug::before { + content: "\f188"; } + +.fa-credit-card::before { + content: "\f09d"; } + +.fa-credit-card-alt::before { + content: "\f09d"; } + +.fa-car::before { + content: "\f1b9"; } + +.fa-automobile::before { + content: "\f1b9"; } + +.fa-hand-holding-hand::before { + content: "\e4f7"; } + +.fa-book-open-reader::before { + content: "\f5da"; } + +.fa-book-reader::before { + content: "\f5da"; } + +.fa-mountain-sun::before { + content: "\e52f"; } + +.fa-arrows-left-right-to-line::before { + content: "\e4ba"; } + +.fa-dice-d20::before { + content: "\f6cf"; } + +.fa-truck-droplet::before { + content: "\e58c"; } + +.fa-file-circle-xmark::before { + content: "\e5a1"; } + +.fa-temperature-arrow-up::before { + content: "\e040"; } + +.fa-temperature-up::before { + content: "\e040"; } + +.fa-medal::before { + content: "\f5a2"; } + +.fa-bed::before { + content: "\f236"; } + +.fa-square-h::before { + content: "\f0fd"; } + +.fa-h-square::before { + content: "\f0fd"; } + +.fa-podcast::before { + content: "\f2ce"; } + +.fa-temperature-full::before { + content: "\f2c7"; } + +.fa-temperature-4::before { + content: "\f2c7"; } + +.fa-thermometer-4::before { + content: "\f2c7"; } + +.fa-thermometer-full::before { + content: "\f2c7"; } + +.fa-bell::before { + content: "\f0f3"; } + +.fa-superscript::before { + content: "\f12b"; } + +.fa-plug-circle-xmark::before { + content: "\e560"; } + +.fa-star-of-life::before { + content: "\f621"; } + +.fa-phone-slash::before { + content: "\f3dd"; } + +.fa-paint-roller::before { + content: "\f5aa"; } + +.fa-handshake-angle::before { + content: "\f4c4"; } + +.fa-hands-helping::before { + content: "\f4c4"; } + +.fa-location-dot::before { + content: "\f3c5"; } + +.fa-map-marker-alt::before { + content: "\f3c5"; } + +.fa-file::before { + content: "\f15b"; } + +.fa-greater-than::before { + content: "\3e"; } + +.fa-person-swimming::before { + content: "\f5c4"; } + +.fa-swimmer::before { + content: "\f5c4"; } + +.fa-arrow-down::before { + content: "\f063"; } + +.fa-droplet::before { + content: "\f043"; } + +.fa-tint::before { + content: "\f043"; } + +.fa-eraser::before { + content: "\f12d"; } + +.fa-earth-americas::before { + content: "\f57d"; } + +.fa-earth::before { + content: "\f57d"; } + +.fa-earth-america::before { + content: "\f57d"; } + +.fa-globe-americas::before { + content: "\f57d"; } + +.fa-person-burst::before { + content: "\e53b"; } + +.fa-dove::before { + content: "\f4ba"; } + +.fa-battery-empty::before { + content: "\f244"; } + +.fa-battery-0::before { + content: "\f244"; } + +.fa-socks::before { + content: "\f696"; } + +.fa-inbox::before { + content: "\f01c"; } + +.fa-section::before { + content: "\e447"; } + +.fa-gauge-high::before { + content: "\f625"; } + +.fa-tachometer-alt::before { + content: "\f625"; } + +.fa-tachometer-alt-fast::before { + content: "\f625"; } + +.fa-envelope-open-text::before { + content: "\f658"; } + +.fa-hospital::before { + content: "\f0f8"; } + +.fa-hospital-alt::before { + content: "\f0f8"; } + +.fa-hospital-wide::before { + content: "\f0f8"; } + +.fa-wine-bottle::before { + content: "\f72f"; } + +.fa-chess-rook::before { + content: "\f447"; } + +.fa-bars-staggered::before { + content: "\f550"; } + +.fa-reorder::before { + content: "\f550"; } + +.fa-stream::before { + content: "\f550"; } + +.fa-dharmachakra::before { + content: "\f655"; } + +.fa-hotdog::before { + content: "\f80f"; } + +.fa-person-walking-with-cane::before { + content: "\f29d"; } + +.fa-blind::before { + content: "\f29d"; } + +.fa-drum::before { + content: "\f569"; } + +.fa-ice-cream::before { + content: "\f810"; } + +.fa-heart-circle-bolt::before { + content: "\e4fc"; } + +.fa-fax::before { + content: "\f1ac"; } + +.fa-paragraph::before { + content: "\f1dd"; } + +.fa-check-to-slot::before { + content: "\f772"; } + +.fa-vote-yea::before { + content: "\f772"; } + +.fa-star-half::before { + content: "\f089"; } + +.fa-boxes-stacked::before { + content: "\f468"; } + +.fa-boxes::before { + content: "\f468"; } + +.fa-boxes-alt::before { + content: "\f468"; } + +.fa-link::before { + content: "\f0c1"; } + +.fa-chain::before { + content: "\f0c1"; } + +.fa-ear-listen::before { + content: "\f2a2"; } + +.fa-assistive-listening-systems::before { + content: "\f2a2"; } + +.fa-tree-city::before { + content: "\e587"; } + +.fa-play::before { + content: "\f04b"; } + +.fa-font::before { + content: "\f031"; } + +.fa-rupiah-sign::before { + content: "\e23d"; } + +.fa-magnifying-glass::before { + content: "\f002"; } + +.fa-search::before { + content: "\f002"; } + +.fa-table-tennis-paddle-ball::before { + content: "\f45d"; } + +.fa-ping-pong-paddle-ball::before { + content: "\f45d"; } + +.fa-table-tennis::before { + content: "\f45d"; } + +.fa-person-dots-from-line::before { + content: "\f470"; } + +.fa-diagnoses::before { + content: "\f470"; } + +.fa-trash-can-arrow-up::before { + content: "\f82a"; } + +.fa-trash-restore-alt::before { + content: "\f82a"; } + +.fa-naira-sign::before { + content: "\e1f6"; } + +.fa-cart-arrow-down::before { + content: "\f218"; } + +.fa-walkie-talkie::before { + content: "\f8ef"; } + +.fa-file-pen::before { + content: "\f31c"; } + +.fa-file-edit::before { + content: "\f31c"; } + +.fa-receipt::before { + content: "\f543"; } + +.fa-square-pen::before { + content: "\f14b"; } + +.fa-pen-square::before { + content: "\f14b"; } + +.fa-pencil-square::before { + content: "\f14b"; } + +.fa-suitcase-rolling::before { + content: "\f5c1"; } + +.fa-person-circle-exclamation::before { + content: "\e53f"; } + +.fa-chevron-down::before { + content: "\f078"; } + +.fa-battery-full::before { + content: "\f240"; } + +.fa-battery::before { + content: "\f240"; } + +.fa-battery-5::before { + content: "\f240"; } + +.fa-skull-crossbones::before { + content: "\f714"; } + +.fa-code-compare::before { + content: "\e13a"; } + +.fa-list-ul::before { + content: "\f0ca"; } + +.fa-list-dots::before { + content: "\f0ca"; } + +.fa-school-lock::before { + content: "\e56f"; } + +.fa-tower-cell::before { + content: "\e585"; } + +.fa-down-long::before { + content: "\f309"; } + +.fa-long-arrow-alt-down::before { + content: "\f309"; } + +.fa-ranking-star::before { + content: "\e561"; } + +.fa-chess-king::before { + content: "\f43f"; } + +.fa-person-harassing::before { + content: "\e549"; } + +.fa-brazilian-real-sign::before { + content: "\e46c"; } + +.fa-landmark-dome::before { + content: "\f752"; } + +.fa-landmark-alt::before { + content: "\f752"; } + +.fa-arrow-up::before { + content: "\f062"; } + +.fa-tv::before { + content: "\f26c"; } + +.fa-television::before { + content: "\f26c"; } + +.fa-tv-alt::before { + content: "\f26c"; } + +.fa-shrimp::before { + content: "\e448"; } + +.fa-list-check::before { + content: "\f0ae"; } + +.fa-tasks::before { + content: "\f0ae"; } + +.fa-jug-detergent::before { + content: "\e519"; } + +.fa-circle-user::before { + content: "\f2bd"; } + +.fa-user-circle::before { + content: "\f2bd"; } + +.fa-user-shield::before { + content: "\f505"; } + +.fa-wind::before { + content: "\f72e"; } + +.fa-car-burst::before { + content: "\f5e1"; } + +.fa-car-crash::before { + content: "\f5e1"; } + +.fa-y::before { + content: "\59"; } + +.fa-person-snowboarding::before { + content: "\f7ce"; } + +.fa-snowboarding::before { + content: "\f7ce"; } + +.fa-truck-fast::before { + content: "\f48b"; } + +.fa-shipping-fast::before { + content: "\f48b"; } + +.fa-fish::before { + content: "\f578"; } + +.fa-user-graduate::before { + content: "\f501"; } + +.fa-circle-half-stroke::before { + content: "\f042"; } + +.fa-adjust::before { + content: "\f042"; } + +.fa-clapperboard::before { + content: "\e131"; } + +.fa-circle-radiation::before { + content: "\f7ba"; } + +.fa-radiation-alt::before { + content: "\f7ba"; } + +.fa-baseball::before { + content: "\f433"; } + +.fa-baseball-ball::before { + content: "\f433"; } + +.fa-jet-fighter-up::before { + content: "\e518"; } + +.fa-diagram-project::before { + content: "\f542"; } + +.fa-project-diagram::before { + content: "\f542"; } + +.fa-copy::before { + content: "\f0c5"; } + +.fa-volume-xmark::before { + content: "\f6a9"; } + +.fa-volume-mute::before { + content: "\f6a9"; } + +.fa-volume-times::before { + content: "\f6a9"; } + +.fa-hand-sparkles::before { + content: "\e05d"; } + +.fa-grip::before { + content: "\f58d"; } + +.fa-grip-horizontal::before { + content: "\f58d"; } + +.fa-share-from-square::before { + content: "\f14d"; } + +.fa-share-square::before { + content: "\f14d"; } + +.fa-child-combatant::before { + content: "\e4e0"; } + +.fa-child-rifle::before { + content: "\e4e0"; } + +.fa-gun::before { + content: "\e19b"; } + +.fa-square-phone::before { + content: "\f098"; } + +.fa-phone-square::before { + content: "\f098"; } + +.fa-plus::before { + content: "\2b"; } + +.fa-add::before { + content: "\2b"; } + +.fa-expand::before { + content: "\f065"; } + +.fa-computer::before { + content: "\e4e5"; } + +.fa-xmark::before { + content: "\f00d"; } + +.fa-close::before { + content: "\f00d"; } + +.fa-multiply::before { + content: "\f00d"; } + +.fa-remove::before { + content: "\f00d"; } + +.fa-times::before { + content: "\f00d"; } + +.fa-arrows-up-down-left-right::before { + content: "\f047"; } + +.fa-arrows::before { + content: "\f047"; } + +.fa-chalkboard-user::before { + content: "\f51c"; } + +.fa-chalkboard-teacher::before { + content: "\f51c"; } + +.fa-peso-sign::before { + content: "\e222"; } + +.fa-building-shield::before { + content: "\e4d8"; } + +.fa-baby::before { + content: "\f77c"; } + +.fa-users-line::before { + content: "\e592"; } + +.fa-quote-left::before { + content: "\f10d"; } + +.fa-quote-left-alt::before { + content: "\f10d"; } + +.fa-tractor::before { + content: "\f722"; } + +.fa-trash-arrow-up::before { + content: "\f829"; } + +.fa-trash-restore::before { + content: "\f829"; } + +.fa-arrow-down-up-lock::before { + content: "\e4b0"; } + +.fa-lines-leaning::before { + content: "\e51e"; } + +.fa-ruler-combined::before { + content: "\f546"; } + +.fa-copyright::before { + content: "\f1f9"; } + +.fa-equals::before { + content: "\3d"; } + +.fa-blender::before { + content: "\f517"; } + +.fa-teeth::before { + content: "\f62e"; } + +.fa-shekel-sign::before { + content: "\f20b"; } + +.fa-ils::before { + content: "\f20b"; } + +.fa-shekel::before { + content: "\f20b"; } + +.fa-sheqel::before { + content: "\f20b"; } + +.fa-sheqel-sign::before { + content: "\f20b"; } + +.fa-map::before { + content: "\f279"; } + +.fa-rocket::before { + content: "\f135"; } + +.fa-photo-film::before { + content: "\f87c"; } + +.fa-photo-video::before { + content: "\f87c"; } + +.fa-folder-minus::before { + content: "\f65d"; } + +.fa-store::before { + content: "\f54e"; } + +.fa-arrow-trend-up::before { + content: "\e098"; } + +.fa-plug-circle-minus::before { + content: "\e55e"; } + +.fa-sign-hanging::before { + content: "\f4d9"; } + +.fa-sign::before { + content: "\f4d9"; } + +.fa-bezier-curve::before { + content: "\f55b"; } + +.fa-bell-slash::before { + content: "\f1f6"; } + +.fa-tablet::before { + content: "\f3fb"; } + +.fa-tablet-android::before { + content: "\f3fb"; } + +.fa-school-flag::before { + content: "\e56e"; } + +.fa-fill::before { + content: "\f575"; } + +.fa-angle-up::before { + content: "\f106"; } + +.fa-drumstick-bite::before { + content: "\f6d7"; } + +.fa-holly-berry::before { + content: "\f7aa"; } + +.fa-chevron-left::before { + content: "\f053"; } + +.fa-bacteria::before { + content: "\e059"; } + +.fa-hand-lizard::before { + content: "\f258"; } + +.fa-notdef::before { + content: "\e1fe"; } + +.fa-disease::before { + content: "\f7fa"; } + +.fa-briefcase-medical::before { + content: "\f469"; } + +.fa-genderless::before { + content: "\f22d"; } + +.fa-chevron-right::before { + content: "\f054"; } + +.fa-retweet::before { + content: "\f079"; } + +.fa-car-rear::before { + content: "\f5de"; } + +.fa-car-alt::before { + content: "\f5de"; } + +.fa-pump-soap::before { + content: "\e06b"; } + +.fa-video-slash::before { + content: "\f4e2"; } + +.fa-battery-quarter::before { + content: "\f243"; } + +.fa-battery-2::before { + content: "\f243"; } + +.fa-radio::before { + content: "\f8d7"; } + +.fa-baby-carriage::before { + content: "\f77d"; } + +.fa-carriage-baby::before { + content: "\f77d"; } + +.fa-traffic-light::before { + content: "\f637"; } + +.fa-thermometer::before { + content: "\f491"; } + +.fa-vr-cardboard::before { + content: "\f729"; } + +.fa-hand-middle-finger::before { + content: "\f806"; } + +.fa-percent::before { + content: "\25"; } + +.fa-percentage::before { + content: "\25"; } + +.fa-truck-moving::before { + content: "\f4df"; } + +.fa-glass-water-droplet::before { + content: "\e4f5"; } + +.fa-display::before { + content: "\e163"; } + +.fa-face-smile::before { + content: "\f118"; } + +.fa-smile::before { + content: "\f118"; } + +.fa-thumbtack::before { + content: "\f08d"; } + +.fa-thumb-tack::before { + content: "\f08d"; } + +.fa-trophy::before { + content: "\f091"; } + +.fa-person-praying::before { + content: "\f683"; } + +.fa-pray::before { + content: "\f683"; } + +.fa-hammer::before { + content: "\f6e3"; } + +.fa-hand-peace::before { + content: "\f25b"; } + +.fa-rotate::before { + content: "\f2f1"; } + +.fa-sync-alt::before { + content: "\f2f1"; } + +.fa-spinner::before { + content: "\f110"; } + +.fa-robot::before { + content: "\f544"; } + +.fa-peace::before { + content: "\f67c"; } + +.fa-gears::before { + content: "\f085"; } + +.fa-cogs::before { + content: "\f085"; } + +.fa-warehouse::before { + content: "\f494"; } + +.fa-arrow-up-right-dots::before { + content: "\e4b7"; } + +.fa-splotch::before { + content: "\f5bc"; } + +.fa-face-grin-hearts::before { + content: "\f584"; } + +.fa-grin-hearts::before { + content: "\f584"; } + +.fa-dice-four::before { + content: "\f524"; } + +.fa-sim-card::before { + content: "\f7c4"; } + +.fa-transgender::before { + content: "\f225"; } + +.fa-transgender-alt::before { + content: "\f225"; } + +.fa-mercury::before { + content: "\f223"; } + +.fa-arrow-turn-down::before { + content: "\f149"; } + +.fa-level-down::before { + content: "\f149"; } + +.fa-person-falling-burst::before { + content: "\e547"; } + +.fa-award::before { + content: "\f559"; } + +.fa-ticket-simple::before { + content: "\f3ff"; } + +.fa-ticket-alt::before { + content: "\f3ff"; } + +.fa-building::before { + content: "\f1ad"; } + +.fa-angles-left::before { + content: "\f100"; } + +.fa-angle-double-left::before { + content: "\f100"; } + +.fa-qrcode::before { + content: "\f029"; } + +.fa-clock-rotate-left::before { + content: "\f1da"; } + +.fa-history::before { + content: "\f1da"; } + +.fa-face-grin-beam-sweat::before { + content: "\f583"; } + +.fa-grin-beam-sweat::before { + content: "\f583"; } + +.fa-file-export::before { + content: "\f56e"; } + +.fa-arrow-right-from-file::before { + content: "\f56e"; } + +.fa-shield::before { + content: "\f132"; } + +.fa-shield-blank::before { + content: "\f132"; } + +.fa-arrow-up-short-wide::before { + content: "\f885"; } + +.fa-sort-amount-up-alt::before { + content: "\f885"; } + +.fa-house-medical::before { + content: "\e3b2"; } + +.fa-golf-ball-tee::before { + content: "\f450"; } + +.fa-golf-ball::before { + content: "\f450"; } + +.fa-circle-chevron-left::before { + content: "\f137"; } + +.fa-chevron-circle-left::before { + content: "\f137"; } + +.fa-house-chimney-window::before { + content: "\e00d"; } + +.fa-pen-nib::before { + content: "\f5ad"; } + +.fa-tent-arrow-turn-left::before { + content: "\e580"; } + +.fa-tents::before { + content: "\e582"; } + +.fa-wand-magic::before { + content: "\f0d0"; } + +.fa-magic::before { + content: "\f0d0"; } + +.fa-dog::before { + content: "\f6d3"; } + +.fa-carrot::before { + content: "\f787"; } + +.fa-moon::before { + content: "\f186"; } + +.fa-wine-glass-empty::before { + content: "\f5ce"; } + +.fa-wine-glass-alt::before { + content: "\f5ce"; } + +.fa-cheese::before { + content: "\f7ef"; } + +.fa-yin-yang::before { + content: "\f6ad"; } + +.fa-music::before { + content: "\f001"; } + +.fa-code-commit::before { + content: "\f386"; } + +.fa-temperature-low::before { + content: "\f76b"; } + +.fa-person-biking::before { + content: "\f84a"; } + +.fa-biking::before { + content: "\f84a"; } + +.fa-broom::before { + content: "\f51a"; } + +.fa-shield-heart::before { + content: "\e574"; } + +.fa-gopuram::before { + content: "\f664"; } + +.fa-earth-oceania::before { + content: "\e47b"; } + +.fa-globe-oceania::before { + content: "\e47b"; } + +.fa-square-xmark::before { + content: "\f2d3"; } + +.fa-times-square::before { + content: "\f2d3"; } + +.fa-xmark-square::before { + content: "\f2d3"; } + +.fa-hashtag::before { + content: "\23"; } + +.fa-up-right-and-down-left-from-center::before { + content: "\f424"; } + +.fa-expand-alt::before { + content: "\f424"; } + +.fa-oil-can::before { + content: "\f613"; } + +.fa-t::before { + content: "\54"; } + +.fa-hippo::before { + content: "\f6ed"; } + +.fa-chart-column::before { + content: "\e0e3"; } + +.fa-infinity::before { + content: "\f534"; } + +.fa-vial-circle-check::before { + content: "\e596"; } + +.fa-person-arrow-down-to-line::before { + content: "\e538"; } + +.fa-voicemail::before { + content: "\f897"; } + +.fa-fan::before { + content: "\f863"; } + +.fa-person-walking-luggage::before { + content: "\e554"; } + +.fa-up-down::before { + content: "\f338"; } + +.fa-arrows-alt-v::before { + content: "\f338"; } + +.fa-cloud-moon-rain::before { + content: "\f73c"; } + +.fa-calendar::before { + content: "\f133"; } + +.fa-trailer::before { + content: "\e041"; } + +.fa-bahai::before { + content: "\f666"; } + +.fa-haykal::before { + content: "\f666"; } + +.fa-sd-card::before { + content: "\f7c2"; } + +.fa-dragon::before { + content: "\f6d5"; } + +.fa-shoe-prints::before { + content: "\f54b"; } + +.fa-circle-plus::before { + content: "\f055"; } + +.fa-plus-circle::before { + content: "\f055"; } + +.fa-face-grin-tongue-wink::before { + content: "\f58b"; } + +.fa-grin-tongue-wink::before { + content: "\f58b"; } + +.fa-hand-holding::before { + content: "\f4bd"; } + +.fa-plug-circle-exclamation::before { + content: "\e55d"; } + +.fa-link-slash::before { + content: "\f127"; } + +.fa-chain-broken::before { + content: "\f127"; } + +.fa-chain-slash::before { + content: "\f127"; } + +.fa-unlink::before { + content: "\f127"; } + +.fa-clone::before { + content: "\f24d"; } + +.fa-person-walking-arrow-loop-left::before { + content: "\e551"; } + +.fa-arrow-up-z-a::before { + content: "\f882"; } + +.fa-sort-alpha-up-alt::before { + content: "\f882"; } + +.fa-fire-flame-curved::before { + content: "\f7e4"; } + +.fa-fire-alt::before { + content: "\f7e4"; } + +.fa-tornado::before { + content: "\f76f"; } + +.fa-file-circle-plus::before { + content: "\e494"; } + +.fa-book-quran::before { + content: "\f687"; } + +.fa-quran::before { + content: "\f687"; } + +.fa-anchor::before { + content: "\f13d"; } + +.fa-border-all::before { + content: "\f84c"; } + +.fa-face-angry::before { + content: "\f556"; } + +.fa-angry::before { + content: "\f556"; } + +.fa-cookie-bite::before { + content: "\f564"; } + +.fa-arrow-trend-down::before { + content: "\e097"; } + +.fa-rss::before { + content: "\f09e"; } + +.fa-feed::before { + content: "\f09e"; } + +.fa-draw-polygon::before { + content: "\f5ee"; } + +.fa-scale-balanced::before { + content: "\f24e"; } + +.fa-balance-scale::before { + content: "\f24e"; } + +.fa-gauge-simple-high::before { + content: "\f62a"; } + +.fa-tachometer::before { + content: "\f62a"; } + +.fa-tachometer-fast::before { + content: "\f62a"; } + +.fa-shower::before { + content: "\f2cc"; } + +.fa-desktop::before { + content: "\f390"; } + +.fa-desktop-alt::before { + content: "\f390"; } + +.fa-m::before { + content: "\4d"; } + +.fa-table-list::before { + content: "\f00b"; } + +.fa-th-list::before { + content: "\f00b"; } + +.fa-comment-sms::before { + content: "\f7cd"; } + +.fa-sms::before { + content: "\f7cd"; } + +.fa-book::before { + content: "\f02d"; } + +.fa-user-plus::before { + content: "\f234"; } + +.fa-check::before { + content: "\f00c"; } + +.fa-battery-three-quarters::before { + content: "\f241"; } + +.fa-battery-4::before { + content: "\f241"; } + +.fa-house-circle-check::before { + content: "\e509"; } + +.fa-angle-left::before { + content: "\f104"; } + +.fa-diagram-successor::before { + content: "\e47a"; } + +.fa-truck-arrow-right::before { + content: "\e58b"; } + +.fa-arrows-split-up-and-left::before { + content: "\e4bc"; } + +.fa-hand-fist::before { + content: "\f6de"; } + +.fa-fist-raised::before { + content: "\f6de"; } + +.fa-cloud-moon::before { + content: "\f6c3"; } + +.fa-briefcase::before { + content: "\f0b1"; } + +.fa-person-falling::before { + content: "\e546"; } + +.fa-image-portrait::before { + content: "\f3e0"; } + +.fa-portrait::before { + content: "\f3e0"; } + +.fa-user-tag::before { + content: "\f507"; } + +.fa-rug::before { + content: "\e569"; } + +.fa-earth-europe::before { + content: "\f7a2"; } + +.fa-globe-europe::before { + content: "\f7a2"; } + +.fa-cart-flatbed-suitcase::before { + content: "\f59d"; } + +.fa-luggage-cart::before { + content: "\f59d"; } + +.fa-rectangle-xmark::before { + content: "\f410"; } + +.fa-rectangle-times::before { + content: "\f410"; } + +.fa-times-rectangle::before { + content: "\f410"; } + +.fa-window-close::before { + content: "\f410"; } + +.fa-baht-sign::before { + content: "\e0ac"; } + +.fa-book-open::before { + content: "\f518"; } + +.fa-book-journal-whills::before { + content: "\f66a"; } + +.fa-journal-whills::before { + content: "\f66a"; } + +.fa-handcuffs::before { + content: "\e4f8"; } + +.fa-triangle-exclamation::before { + content: "\f071"; } + +.fa-exclamation-triangle::before { + content: "\f071"; } + +.fa-warning::before { + content: "\f071"; } + +.fa-database::before { + content: "\f1c0"; } + +.fa-share::before { + content: "\f064"; } + +.fa-mail-forward::before { + content: "\f064"; } + +.fa-bottle-droplet::before { + content: "\e4c4"; } + +.fa-mask-face::before { + content: "\e1d7"; } + +.fa-hill-rockslide::before { + content: "\e508"; } + +.fa-right-left::before { + content: "\f362"; } + +.fa-exchange-alt::before { + content: "\f362"; } + +.fa-paper-plane::before { + content: "\f1d8"; } + +.fa-road-circle-exclamation::before { + content: "\e565"; } + +.fa-dungeon::before { + content: "\f6d9"; } + +.fa-align-right::before { + content: "\f038"; } + +.fa-money-bill-1-wave::before { + content: "\f53b"; } + +.fa-money-bill-wave-alt::before { + content: "\f53b"; } + +.fa-life-ring::before { + content: "\f1cd"; } + +.fa-hands::before { + content: "\f2a7"; } + +.fa-sign-language::before { + content: "\f2a7"; } + +.fa-signing::before { + content: "\f2a7"; } + +.fa-calendar-day::before { + content: "\f783"; } + +.fa-water-ladder::before { + content: "\f5c5"; } + +.fa-ladder-water::before { + content: "\f5c5"; } + +.fa-swimming-pool::before { + content: "\f5c5"; } + +.fa-arrows-up-down::before { + content: "\f07d"; } + +.fa-arrows-v::before { + content: "\f07d"; } + +.fa-face-grimace::before { + content: "\f57f"; } + +.fa-grimace::before { + content: "\f57f"; } + +.fa-wheelchair-move::before { + content: "\e2ce"; } + +.fa-wheelchair-alt::before { + content: "\e2ce"; } + +.fa-turn-down::before { + content: "\f3be"; } + +.fa-level-down-alt::before { + content: "\f3be"; } + +.fa-person-walking-arrow-right::before { + content: "\e552"; } + +.fa-square-envelope::before { + content: "\f199"; } + +.fa-envelope-square::before { + content: "\f199"; } + +.fa-dice::before { + content: "\f522"; } + +.fa-bowling-ball::before { + content: "\f436"; } + +.fa-brain::before { + content: "\f5dc"; } + +.fa-bandage::before { + content: "\f462"; } + +.fa-band-aid::before { + content: "\f462"; } + +.fa-calendar-minus::before { + content: "\f272"; } + +.fa-circle-xmark::before { + content: "\f057"; } + +.fa-times-circle::before { + content: "\f057"; } + +.fa-xmark-circle::before { + content: "\f057"; } + +.fa-gifts::before { + content: "\f79c"; } + +.fa-hotel::before { + content: "\f594"; } + +.fa-earth-asia::before { + content: "\f57e"; } + +.fa-globe-asia::before { + content: "\f57e"; } + +.fa-id-card-clip::before { + content: "\f47f"; } + +.fa-id-card-alt::before { + content: "\f47f"; } + +.fa-magnifying-glass-plus::before { + content: "\f00e"; } + +.fa-search-plus::before { + content: "\f00e"; } + +.fa-thumbs-up::before { + content: "\f164"; } + +.fa-user-clock::before { + content: "\f4fd"; } + +.fa-hand-dots::before { + content: "\f461"; } + +.fa-allergies::before { + content: "\f461"; } + +.fa-file-invoice::before { + content: "\f570"; } + +.fa-window-minimize::before { + content: "\f2d1"; } + +.fa-mug-saucer::before { + content: "\f0f4"; } + +.fa-coffee::before { + content: "\f0f4"; } + +.fa-brush::before { + content: "\f55d"; } + +.fa-mask::before { + content: "\f6fa"; } + +.fa-magnifying-glass-minus::before { + content: "\f010"; } + +.fa-search-minus::before { + content: "\f010"; } + +.fa-ruler-vertical::before { + content: "\f548"; } + +.fa-user-large::before { + content: "\f406"; } + +.fa-user-alt::before { + content: "\f406"; } + +.fa-train-tram::before { + content: "\e5b4"; } + +.fa-user-nurse::before { + content: "\f82f"; } + +.fa-syringe::before { + content: "\f48e"; } + +.fa-cloud-sun::before { + content: "\f6c4"; } + +.fa-stopwatch-20::before { + content: "\e06f"; } + +.fa-square-full::before { + content: "\f45c"; } + +.fa-magnet::before { + content: "\f076"; } + +.fa-jar::before { + content: "\e516"; } + +.fa-note-sticky::before { + content: "\f249"; } + +.fa-sticky-note::before { + content: "\f249"; } + +.fa-bug-slash::before { + content: "\e490"; } + +.fa-arrow-up-from-water-pump::before { + content: "\e4b6"; } + +.fa-bone::before { + content: "\f5d7"; } + +.fa-user-injured::before { + content: "\f728"; } + +.fa-face-sad-tear::before { + content: "\f5b4"; } + +.fa-sad-tear::before { + content: "\f5b4"; } + +.fa-plane::before { + content: "\f072"; } + +.fa-tent-arrows-down::before { + content: "\e581"; } + +.fa-exclamation::before { + content: "\21"; } + +.fa-arrows-spin::before { + content: "\e4bb"; } + +.fa-print::before { + content: "\f02f"; } + +.fa-turkish-lira-sign::before { + content: "\e2bb"; } + +.fa-try::before { + content: "\e2bb"; } + +.fa-turkish-lira::before { + content: "\e2bb"; } + +.fa-dollar-sign::before { + content: "\24"; } + +.fa-dollar::before { + content: "\24"; } + +.fa-usd::before { + content: "\24"; } + +.fa-x::before { + content: "\58"; } + +.fa-magnifying-glass-dollar::before { + content: "\f688"; } + +.fa-search-dollar::before { + content: "\f688"; } + +.fa-users-gear::before { + content: "\f509"; } + +.fa-users-cog::before { + content: "\f509"; } + +.fa-person-military-pointing::before { + content: "\e54a"; } + +.fa-building-columns::before { + content: "\f19c"; } + +.fa-bank::before { + content: "\f19c"; } + +.fa-institution::before { + content: "\f19c"; } + +.fa-museum::before { + content: "\f19c"; } + +.fa-university::before { + content: "\f19c"; } + +.fa-umbrella::before { + content: "\f0e9"; } + +.fa-trowel::before { + content: "\e589"; } + +.fa-d::before { + content: "\44"; } + +.fa-stapler::before { + content: "\e5af"; } + +.fa-masks-theater::before { + content: "\f630"; } + +.fa-theater-masks::before { + content: "\f630"; } + +.fa-kip-sign::before { + content: "\e1c4"; } + +.fa-hand-point-left::before { + content: "\f0a5"; } + +.fa-handshake-simple::before { + content: "\f4c6"; } + +.fa-handshake-alt::before { + content: "\f4c6"; } + +.fa-jet-fighter::before { + content: "\f0fb"; } + +.fa-fighter-jet::before { + content: "\f0fb"; } + +.fa-square-share-nodes::before { + content: "\f1e1"; } + +.fa-share-alt-square::before { + content: "\f1e1"; } + +.fa-barcode::before { + content: "\f02a"; } + +.fa-plus-minus::before { + content: "\e43c"; } + +.fa-video::before { + content: "\f03d"; } + +.fa-video-camera::before { + content: "\f03d"; } + +.fa-graduation-cap::before { + content: "\f19d"; } + +.fa-mortar-board::before { + content: "\f19d"; } + +.fa-hand-holding-medical::before { + content: "\e05c"; } + +.fa-person-circle-check::before { + content: "\e53e"; } + +.fa-turn-up::before { + content: "\f3bf"; } + +.fa-level-up-alt::before { + content: "\f3bf"; } + +.sr-only, +.fa-sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; } + +.sr-only-focusable:not(:focus), +.fa-sr-only-focusable:not(:focus) { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; } +:root, :host { + --fa-style-family-brands: 'Font Awesome 6 Brands'; + --fa-font-brands: normal 400 1em/1 'Font Awesome 6 Brands'; } + +@font-face { + font-family: 'Font Awesome 6 Brands'; + font-style: normal; + font-weight: 400; + font-display: block; + src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); } + +.fab, +.fa-brands { + font-weight: 400; } + +.fa-monero:before { + content: "\f3d0"; } + +.fa-hooli:before { + content: "\f427"; } + +.fa-yelp:before { + content: "\f1e9"; } + +.fa-cc-visa:before { + content: "\f1f0"; } + +.fa-lastfm:before { + content: "\f202"; } + +.fa-shopware:before { + content: "\f5b5"; } + +.fa-creative-commons-nc:before { + content: "\f4e8"; } + +.fa-aws:before { + content: "\f375"; } + +.fa-redhat:before { + content: "\f7bc"; } + +.fa-yoast:before { + content: "\f2b1"; } + +.fa-cloudflare:before { + content: "\e07d"; } + +.fa-ups:before { + content: "\f7e0"; } + +.fa-pixiv:before { + content: "\e640"; } + +.fa-wpexplorer:before { + content: "\f2de"; } + +.fa-dyalog:before { + content: "\f399"; } + +.fa-bity:before { + content: "\f37a"; } + +.fa-stackpath:before { + content: "\f842"; } + +.fa-buysellads:before { + content: "\f20d"; } + +.fa-first-order:before { + content: "\f2b0"; } + +.fa-modx:before { + content: "\f285"; } + +.fa-guilded:before { + content: "\e07e"; } + +.fa-vnv:before { + content: "\f40b"; } + +.fa-square-js:before { + content: "\f3b9"; } + +.fa-js-square:before { + content: "\f3b9"; } + +.fa-microsoft:before { + content: "\f3ca"; } + +.fa-qq:before { + content: "\f1d6"; } + +.fa-orcid:before { + content: "\f8d2"; } + +.fa-java:before { + content: "\f4e4"; } + +.fa-invision:before { + content: "\f7b0"; } + +.fa-creative-commons-pd-alt:before { + content: "\f4ed"; } + +.fa-centercode:before { + content: "\f380"; } + +.fa-glide-g:before { + content: "\f2a6"; } + +.fa-drupal:before { + content: "\f1a9"; } + +.fa-hire-a-helper:before { + content: "\f3b0"; } + +.fa-creative-commons-by:before { + content: "\f4e7"; } + +.fa-unity:before { + content: "\e049"; } + +.fa-whmcs:before { + content: "\f40d"; } + +.fa-rocketchat:before { + content: "\f3e8"; } + +.fa-vk:before { + content: "\f189"; } + +.fa-untappd:before { + content: "\f405"; } + +.fa-mailchimp:before { + content: "\f59e"; } + +.fa-css3-alt:before { + content: "\f38b"; } + +.fa-square-reddit:before { + content: "\f1a2"; } + +.fa-reddit-square:before { + content: "\f1a2"; } + +.fa-vimeo-v:before { + content: "\f27d"; } + +.fa-contao:before { + content: "\f26d"; } + +.fa-square-font-awesome:before { + content: "\e5ad"; } + +.fa-deskpro:before { + content: "\f38f"; } + +.fa-brave:before { + content: "\e63c"; } + +.fa-sistrix:before { + content: "\f3ee"; } + +.fa-square-instagram:before { + content: "\e055"; } + +.fa-instagram-square:before { + content: "\e055"; } + +.fa-battle-net:before { + content: "\f835"; } + +.fa-the-red-yeti:before { + content: "\f69d"; } + +.fa-square-hacker-news:before { + content: "\f3af"; } + +.fa-hacker-news-square:before { + content: "\f3af"; } + +.fa-edge:before { + content: "\f282"; } + +.fa-threads:before { + content: "\e618"; } + +.fa-napster:before { + content: "\f3d2"; } + +.fa-square-snapchat:before { + content: "\f2ad"; } + +.fa-snapchat-square:before { + content: "\f2ad"; } + +.fa-google-plus-g:before { + content: "\f0d5"; } + +.fa-artstation:before { + content: "\f77a"; } + +.fa-markdown:before { + content: "\f60f"; } + +.fa-sourcetree:before { + content: "\f7d3"; } + +.fa-google-plus:before { + content: "\f2b3"; } + +.fa-diaspora:before { + content: "\f791"; } + +.fa-foursquare:before { + content: "\f180"; } + +.fa-stack-overflow:before { + content: "\f16c"; } + +.fa-github-alt:before { + content: "\f113"; } + +.fa-phoenix-squadron:before { + content: "\f511"; } + +.fa-pagelines:before { + content: "\f18c"; } + +.fa-algolia:before { + content: "\f36c"; } + +.fa-red-river:before { + content: "\f3e3"; } + +.fa-creative-commons-sa:before { + content: "\f4ef"; } + +.fa-safari:before { + content: "\f267"; } + +.fa-google:before { + content: "\f1a0"; } + +.fa-square-font-awesome-stroke:before { + content: "\f35c"; } + +.fa-font-awesome-alt:before { + content: "\f35c"; } + +.fa-atlassian:before { + content: "\f77b"; } + +.fa-linkedin-in:before { + content: "\f0e1"; } + +.fa-digital-ocean:before { + content: "\f391"; } + +.fa-nimblr:before { + content: "\f5a8"; } + +.fa-chromecast:before { + content: "\f838"; } + +.fa-evernote:before { + content: "\f839"; } + +.fa-hacker-news:before { + content: "\f1d4"; } + +.fa-creative-commons-sampling:before { + content: "\f4f0"; } + +.fa-adversal:before { + content: "\f36a"; } + +.fa-creative-commons:before { + content: "\f25e"; } + +.fa-watchman-monitoring:before { + content: "\e087"; } + +.fa-fonticons:before { + content: "\f280"; } + +.fa-weixin:before { + content: "\f1d7"; } + +.fa-shirtsinbulk:before { + content: "\f214"; } + +.fa-codepen:before { + content: "\f1cb"; } + +.fa-git-alt:before { + content: "\f841"; } + +.fa-lyft:before { + content: "\f3c3"; } + +.fa-rev:before { + content: "\f5b2"; } + +.fa-windows:before { + content: "\f17a"; } + +.fa-wizards-of-the-coast:before { + content: "\f730"; } + +.fa-square-viadeo:before { + content: "\f2aa"; } + +.fa-viadeo-square:before { + content: "\f2aa"; } + +.fa-meetup:before { + content: "\f2e0"; } + +.fa-centos:before { + content: "\f789"; } + +.fa-adn:before { + content: "\f170"; } + +.fa-cloudsmith:before { + content: "\f384"; } + +.fa-opensuse:before { + content: "\e62b"; } + +.fa-pied-piper-alt:before { + content: "\f1a8"; } + +.fa-square-dribbble:before { + content: "\f397"; } + +.fa-dribbble-square:before { + content: "\f397"; } + +.fa-codiepie:before { + content: "\f284"; } + +.fa-node:before { + content: "\f419"; } + +.fa-mix:before { + content: "\f3cb"; } + +.fa-steam:before { + content: "\f1b6"; } + +.fa-cc-apple-pay:before { + content: "\f416"; } + +.fa-scribd:before { + content: "\f28a"; } + +.fa-debian:before { + content: "\e60b"; } + +.fa-openid:before { + content: "\f19b"; } + +.fa-instalod:before { + content: "\e081"; } + +.fa-expeditedssl:before { + content: "\f23e"; } + +.fa-sellcast:before { + content: "\f2da"; } + +.fa-square-twitter:before { + content: "\f081"; } + +.fa-twitter-square:before { + content: "\f081"; } + +.fa-r-project:before { + content: "\f4f7"; } + +.fa-delicious:before { + content: "\f1a5"; } + +.fa-freebsd:before { + content: "\f3a4"; } + +.fa-vuejs:before { + content: "\f41f"; } + +.fa-accusoft:before { + content: "\f369"; } + +.fa-ioxhost:before { + content: "\f208"; } + +.fa-fonticons-fi:before { + content: "\f3a2"; } + +.fa-app-store:before { + content: "\f36f"; } + +.fa-cc-mastercard:before { + content: "\f1f1"; } + +.fa-itunes-note:before { + content: "\f3b5"; } + +.fa-golang:before { + content: "\e40f"; } + +.fa-kickstarter:before { + content: "\f3bb"; } + +.fa-grav:before { + content: "\f2d6"; } + +.fa-weibo:before { + content: "\f18a"; } + +.fa-uncharted:before { + content: "\e084"; } + +.fa-firstdraft:before { + content: "\f3a1"; } + +.fa-square-youtube:before { + content: "\f431"; } + +.fa-youtube-square:before { + content: "\f431"; } + +.fa-wikipedia-w:before { + content: "\f266"; } + +.fa-wpressr:before { + content: "\f3e4"; } + +.fa-rendact:before { + content: "\f3e4"; } + +.fa-angellist:before { + content: "\f209"; } + +.fa-galactic-republic:before { + content: "\f50c"; } + +.fa-nfc-directional:before { + content: "\e530"; } + +.fa-skype:before { + content: "\f17e"; } + +.fa-joget:before { + content: "\f3b7"; } + +.fa-fedora:before { + content: "\f798"; } + +.fa-stripe-s:before { + content: "\f42a"; } + +.fa-meta:before { + content: "\e49b"; } + +.fa-laravel:before { + content: "\f3bd"; } + +.fa-hotjar:before { + content: "\f3b1"; } + +.fa-bluetooth-b:before { + content: "\f294"; } + +.fa-square-letterboxd:before { + content: "\e62e"; } + +.fa-sticker-mule:before { + content: "\f3f7"; } + +.fa-creative-commons-zero:before { + content: "\f4f3"; } + +.fa-hips:before { + content: "\f452"; } + +.fa-behance:before { + content: "\f1b4"; } + +.fa-reddit:before { + content: "\f1a1"; } + +.fa-discord:before { + content: "\f392"; } + +.fa-chrome:before { + content: "\f268"; } + +.fa-app-store-ios:before { + content: "\f370"; } + +.fa-cc-discover:before { + content: "\f1f2"; } + +.fa-wpbeginner:before { + content: "\f297"; } + +.fa-confluence:before { + content: "\f78d"; } + +.fa-shoelace:before { + content: "\e60c"; } + +.fa-mdb:before { + content: "\f8ca"; } + +.fa-dochub:before { + content: "\f394"; } + +.fa-accessible-icon:before { + content: "\f368"; } + +.fa-ebay:before { + content: "\f4f4"; } + +.fa-amazon:before { + content: "\f270"; } + +.fa-unsplash:before { + content: "\e07c"; } + +.fa-yarn:before { + content: "\f7e3"; } + +.fa-square-steam:before { + content: "\f1b7"; } + +.fa-steam-square:before { + content: "\f1b7"; } + +.fa-500px:before { + content: "\f26e"; } + +.fa-square-vimeo:before { + content: "\f194"; } + +.fa-vimeo-square:before { + content: "\f194"; } + +.fa-asymmetrik:before { + content: "\f372"; } + +.fa-font-awesome:before { + content: "\f2b4"; } + +.fa-font-awesome-flag:before { + content: "\f2b4"; } + +.fa-font-awesome-logo-full:before { + content: "\f2b4"; } + +.fa-gratipay:before { + content: "\f184"; } + +.fa-apple:before { + content: "\f179"; } + +.fa-hive:before { + content: "\e07f"; } + +.fa-gitkraken:before { + content: "\f3a6"; } + +.fa-keybase:before { + content: "\f4f5"; } + +.fa-apple-pay:before { + content: "\f415"; } + +.fa-padlet:before { + content: "\e4a0"; } + +.fa-amazon-pay:before { + content: "\f42c"; } + +.fa-square-github:before { + content: "\f092"; } + +.fa-github-square:before { + content: "\f092"; } + +.fa-stumbleupon:before { + content: "\f1a4"; } + +.fa-fedex:before { + content: "\f797"; } + +.fa-phoenix-framework:before { + content: "\f3dc"; } + +.fa-shopify:before { + content: "\e057"; } + +.fa-neos:before { + content: "\f612"; } + +.fa-square-threads:before { + content: "\e619"; } + +.fa-hackerrank:before { + content: "\f5f7"; } + +.fa-researchgate:before { + content: "\f4f8"; } + +.fa-swift:before { + content: "\f8e1"; } + +.fa-angular:before { + content: "\f420"; } + +.fa-speakap:before { + content: "\f3f3"; } + +.fa-angrycreative:before { + content: "\f36e"; } + +.fa-y-combinator:before { + content: "\f23b"; } + +.fa-empire:before { + content: "\f1d1"; } + +.fa-envira:before { + content: "\f299"; } + +.fa-google-scholar:before { + content: "\e63b"; } + +.fa-square-gitlab:before { + content: "\e5ae"; } + +.fa-gitlab-square:before { + content: "\e5ae"; } + +.fa-studiovinari:before { + content: "\f3f8"; } + +.fa-pied-piper:before { + content: "\f2ae"; } + +.fa-wordpress:before { + content: "\f19a"; } + +.fa-product-hunt:before { + content: "\f288"; } + +.fa-firefox:before { + content: "\f269"; } + +.fa-linode:before { + content: "\f2b8"; } + +.fa-goodreads:before { + content: "\f3a8"; } + +.fa-square-odnoklassniki:before { + content: "\f264"; } + +.fa-odnoklassniki-square:before { + content: "\f264"; } + +.fa-jsfiddle:before { + content: "\f1cc"; } + +.fa-sith:before { + content: "\f512"; } + +.fa-themeisle:before { + content: "\f2b2"; } + +.fa-page4:before { + content: "\f3d7"; } + +.fa-hashnode:before { + content: "\e499"; } + +.fa-react:before { + content: "\f41b"; } + +.fa-cc-paypal:before { + content: "\f1f4"; } + +.fa-squarespace:before { + content: "\f5be"; } + +.fa-cc-stripe:before { + content: "\f1f5"; } + +.fa-creative-commons-share:before { + content: "\f4f2"; } + +.fa-bitcoin:before { + content: "\f379"; } + +.fa-keycdn:before { + content: "\f3ba"; } + +.fa-opera:before { + content: "\f26a"; } + +.fa-itch-io:before { + content: "\f83a"; } + +.fa-umbraco:before { + content: "\f8e8"; } + +.fa-galactic-senate:before { + content: "\f50d"; } + +.fa-ubuntu:before { + content: "\f7df"; } + +.fa-draft2digital:before { + content: "\f396"; } + +.fa-stripe:before { + content: "\f429"; } + +.fa-houzz:before { + content: "\f27c"; } + +.fa-gg:before { + content: "\f260"; } + +.fa-dhl:before { + content: "\f790"; } + +.fa-square-pinterest:before { + content: "\f0d3"; } + +.fa-pinterest-square:before { + content: "\f0d3"; } + +.fa-xing:before { + content: "\f168"; } + +.fa-blackberry:before { + content: "\f37b"; } + +.fa-creative-commons-pd:before { + content: "\f4ec"; } + +.fa-playstation:before { + content: "\f3df"; } + +.fa-quinscape:before { + content: "\f459"; } + +.fa-less:before { + content: "\f41d"; } + +.fa-blogger-b:before { + content: "\f37d"; } + +.fa-opencart:before { + content: "\f23d"; } + +.fa-vine:before { + content: "\f1ca"; } + +.fa-signal-messenger:before { + content: "\e663"; } + +.fa-paypal:before { + content: "\f1ed"; } + +.fa-gitlab:before { + content: "\f296"; } + +.fa-typo3:before { + content: "\f42b"; } + +.fa-reddit-alien:before { + content: "\f281"; } + +.fa-yahoo:before { + content: "\f19e"; } + +.fa-dailymotion:before { + content: "\e052"; } + +.fa-affiliatetheme:before { + content: "\f36b"; } + +.fa-pied-piper-pp:before { + content: "\f1a7"; } + +.fa-bootstrap:before { + content: "\f836"; } + +.fa-odnoklassniki:before { + content: "\f263"; } + +.fa-nfc-symbol:before { + content: "\e531"; } + +.fa-mintbit:before { + content: "\e62f"; } + +.fa-ethereum:before { + content: "\f42e"; } + +.fa-speaker-deck:before { + content: "\f83c"; } + +.fa-creative-commons-nc-eu:before { + content: "\f4e9"; } + +.fa-patreon:before { + content: "\f3d9"; } + +.fa-avianex:before { + content: "\f374"; } + +.fa-ello:before { + content: "\f5f1"; } + +.fa-gofore:before { + content: "\f3a7"; } + +.fa-bimobject:before { + content: "\f378"; } + +.fa-brave-reverse:before { + content: "\e63d"; } + +.fa-facebook-f:before { + content: "\f39e"; } + +.fa-square-google-plus:before { + content: "\f0d4"; } + +.fa-google-plus-square:before { + content: "\f0d4"; } + +.fa-mandalorian:before { + content: "\f50f"; } + +.fa-first-order-alt:before { + content: "\f50a"; } + +.fa-osi:before { + content: "\f41a"; } + +.fa-google-wallet:before { + content: "\f1ee"; } + +.fa-d-and-d-beyond:before { + content: "\f6ca"; } + +.fa-periscope:before { + content: "\f3da"; } + +.fa-fulcrum:before { + content: "\f50b"; } + +.fa-cloudscale:before { + content: "\f383"; } + +.fa-forumbee:before { + content: "\f211"; } + +.fa-mizuni:before { + content: "\f3cc"; } + +.fa-schlix:before { + content: "\f3ea"; } + +.fa-square-xing:before { + content: "\f169"; } + +.fa-xing-square:before { + content: "\f169"; } + +.fa-bandcamp:before { + content: "\f2d5"; } + +.fa-wpforms:before { + content: "\f298"; } + +.fa-cloudversify:before { + content: "\f385"; } + +.fa-usps:before { + content: "\f7e1"; } + +.fa-megaport:before { + content: "\f5a3"; } + +.fa-magento:before { + content: "\f3c4"; } + +.fa-spotify:before { + content: "\f1bc"; } + +.fa-optin-monster:before { + content: "\f23c"; } + +.fa-fly:before { + content: "\f417"; } + +.fa-aviato:before { + content: "\f421"; } + +.fa-itunes:before { + content: "\f3b4"; } + +.fa-cuttlefish:before { + content: "\f38c"; } + +.fa-blogger:before { + content: "\f37c"; } + +.fa-flickr:before { + content: "\f16e"; } + +.fa-viber:before { + content: "\f409"; } + +.fa-soundcloud:before { + content: "\f1be"; } + +.fa-digg:before { + content: "\f1a6"; } + +.fa-tencent-weibo:before { + content: "\f1d5"; } + +.fa-letterboxd:before { + content: "\e62d"; } + +.fa-symfony:before { + content: "\f83d"; } + +.fa-maxcdn:before { + content: "\f136"; } + +.fa-etsy:before { + content: "\f2d7"; } + +.fa-facebook-messenger:before { + content: "\f39f"; } + +.fa-audible:before { + content: "\f373"; } + +.fa-think-peaks:before { + content: "\f731"; } + +.fa-bilibili:before { + content: "\e3d9"; } + +.fa-erlang:before { + content: "\f39d"; } + +.fa-x-twitter:before { + content: "\e61b"; } + +.fa-cotton-bureau:before { + content: "\f89e"; } + +.fa-dashcube:before { + content: "\f210"; } + +.fa-42-group:before { + content: "\e080"; } + +.fa-innosoft:before { + content: "\e080"; } + +.fa-stack-exchange:before { + content: "\f18d"; } + +.fa-elementor:before { + content: "\f430"; } + +.fa-square-pied-piper:before { + content: "\e01e"; } + +.fa-pied-piper-square:before { + content: "\e01e"; } + +.fa-creative-commons-nd:before { + content: "\f4eb"; } + +.fa-palfed:before { + content: "\f3d8"; } + +.fa-superpowers:before { + content: "\f2dd"; } + +.fa-resolving:before { + content: "\f3e7"; } + +.fa-xbox:before { + content: "\f412"; } + +.fa-searchengin:before { + content: "\f3eb"; } + +.fa-tiktok:before { + content: "\e07b"; } + +.fa-square-facebook:before { + content: "\f082"; } + +.fa-facebook-square:before { + content: "\f082"; } + +.fa-renren:before { + content: "\f18b"; } + +.fa-linux:before { + content: "\f17c"; } + +.fa-glide:before { + content: "\f2a5"; } + +.fa-linkedin:before { + content: "\f08c"; } + +.fa-hubspot:before { + content: "\f3b2"; } + +.fa-deploydog:before { + content: "\f38e"; } + +.fa-twitch:before { + content: "\f1e8"; } + +.fa-ravelry:before { + content: "\f2d9"; } + +.fa-mixer:before { + content: "\e056"; } + +.fa-square-lastfm:before { + content: "\f203"; } + +.fa-lastfm-square:before { + content: "\f203"; } + +.fa-vimeo:before { + content: "\f40a"; } + +.fa-mendeley:before { + content: "\f7b3"; } + +.fa-uniregistry:before { + content: "\f404"; } + +.fa-figma:before { + content: "\f799"; } + +.fa-creative-commons-remix:before { + content: "\f4ee"; } + +.fa-cc-amazon-pay:before { + content: "\f42d"; } + +.fa-dropbox:before { + content: "\f16b"; } + +.fa-instagram:before { + content: "\f16d"; } + +.fa-cmplid:before { + content: "\e360"; } + +.fa-upwork:before { + content: "\e641"; } + +.fa-facebook:before { + content: "\f09a"; } + +.fa-gripfire:before { + content: "\f3ac"; } + +.fa-jedi-order:before { + content: "\f50e"; } + +.fa-uikit:before { + content: "\f403"; } + +.fa-fort-awesome-alt:before { + content: "\f3a3"; } + +.fa-phabricator:before { + content: "\f3db"; } + +.fa-ussunnah:before { + content: "\f407"; } + +.fa-earlybirds:before { + content: "\f39a"; } + +.fa-trade-federation:before { + content: "\f513"; } + +.fa-autoprefixer:before { + content: "\f41c"; } + +.fa-whatsapp:before { + content: "\f232"; } + +.fa-slideshare:before { + content: "\f1e7"; } + +.fa-google-play:before { + content: "\f3ab"; } + +.fa-viadeo:before { + content: "\f2a9"; } + +.fa-line:before { + content: "\f3c0"; } + +.fa-google-drive:before { + content: "\f3aa"; } + +.fa-servicestack:before { + content: "\f3ec"; } + +.fa-simplybuilt:before { + content: "\f215"; } + +.fa-bitbucket:before { + content: "\f171"; } + +.fa-imdb:before { + content: "\f2d8"; } + +.fa-deezer:before { + content: "\e077"; } + +.fa-raspberry-pi:before { + content: "\f7bb"; } + +.fa-jira:before { + content: "\f7b1"; } + +.fa-docker:before { + content: "\f395"; } + +.fa-screenpal:before { + content: "\e570"; } + +.fa-bluetooth:before { + content: "\f293"; } + +.fa-gitter:before { + content: "\f426"; } + +.fa-d-and-d:before { + content: "\f38d"; } + +.fa-microblog:before { + content: "\e01a"; } + +.fa-cc-diners-club:before { + content: "\f24c"; } + +.fa-gg-circle:before { + content: "\f261"; } + +.fa-pied-piper-hat:before { + content: "\f4e5"; } + +.fa-kickstarter-k:before { + content: "\f3bc"; } + +.fa-yandex:before { + content: "\f413"; } + +.fa-readme:before { + content: "\f4d5"; } + +.fa-html5:before { + content: "\f13b"; } + +.fa-sellsy:before { + content: "\f213"; } + +.fa-sass:before { + content: "\f41e"; } + +.fa-wirsindhandwerk:before { + content: "\e2d0"; } + +.fa-wsh:before { + content: "\e2d0"; } + +.fa-buromobelexperte:before { + content: "\f37f"; } + +.fa-salesforce:before { + content: "\f83b"; } + +.fa-octopus-deploy:before { + content: "\e082"; } + +.fa-medapps:before { + content: "\f3c6"; } + +.fa-ns8:before { + content: "\f3d5"; } + +.fa-pinterest-p:before { + content: "\f231"; } + +.fa-apper:before { + content: "\f371"; } + +.fa-fort-awesome:before { + content: "\f286"; } + +.fa-waze:before { + content: "\f83f"; } + +.fa-cc-jcb:before { + content: "\f24b"; } + +.fa-snapchat:before { + content: "\f2ab"; } + +.fa-snapchat-ghost:before { + content: "\f2ab"; } + +.fa-fantasy-flight-games:before { + content: "\f6dc"; } + +.fa-rust:before { + content: "\e07a"; } + +.fa-wix:before { + content: "\f5cf"; } + +.fa-square-behance:before { + content: "\f1b5"; } + +.fa-behance-square:before { + content: "\f1b5"; } + +.fa-supple:before { + content: "\f3f9"; } + +.fa-webflow:before { + content: "\e65c"; } + +.fa-rebel:before { + content: "\f1d0"; } + +.fa-css3:before { + content: "\f13c"; } + +.fa-staylinked:before { + content: "\f3f5"; } + +.fa-kaggle:before { + content: "\f5fa"; } + +.fa-space-awesome:before { + content: "\e5ac"; } + +.fa-deviantart:before { + content: "\f1bd"; } + +.fa-cpanel:before { + content: "\f388"; } + +.fa-goodreads-g:before { + content: "\f3a9"; } + +.fa-square-git:before { + content: "\f1d2"; } + +.fa-git-square:before { + content: "\f1d2"; } + +.fa-square-tumblr:before { + content: "\f174"; } + +.fa-tumblr-square:before { + content: "\f174"; } + +.fa-trello:before { + content: "\f181"; } + +.fa-creative-commons-nc-jp:before { + content: "\f4ea"; } + +.fa-get-pocket:before { + content: "\f265"; } + +.fa-perbyte:before { + content: "\e083"; } + +.fa-grunt:before { + content: "\f3ad"; } + +.fa-weebly:before { + content: "\f5cc"; } + +.fa-connectdevelop:before { + content: "\f20e"; } + +.fa-leanpub:before { + content: "\f212"; } + +.fa-black-tie:before { + content: "\f27e"; } + +.fa-themeco:before { + content: "\f5c6"; } + +.fa-python:before { + content: "\f3e2"; } + +.fa-android:before { + content: "\f17b"; } + +.fa-bots:before { + content: "\e340"; } + +.fa-free-code-camp:before { + content: "\f2c5"; } + +.fa-hornbill:before { + content: "\f592"; } + +.fa-js:before { + content: "\f3b8"; } + +.fa-ideal:before { + content: "\e013"; } + +.fa-git:before { + content: "\f1d3"; } + +.fa-dev:before { + content: "\f6cc"; } + +.fa-sketch:before { + content: "\f7c6"; } + +.fa-yandex-international:before { + content: "\f414"; } + +.fa-cc-amex:before { + content: "\f1f3"; } + +.fa-uber:before { + content: "\f402"; } + +.fa-github:before { + content: "\f09b"; } + +.fa-php:before { + content: "\f457"; } + +.fa-alipay:before { + content: "\f642"; } + +.fa-youtube:before { + content: "\f167"; } + +.fa-skyatlas:before { + content: "\f216"; } + +.fa-firefox-browser:before { + content: "\e007"; } + +.fa-replyd:before { + content: "\f3e6"; } + +.fa-suse:before { + content: "\f7d6"; } + +.fa-jenkins:before { + content: "\f3b6"; } + +.fa-twitter:before { + content: "\f099"; } + +.fa-rockrms:before { + content: "\f3e9"; } + +.fa-pinterest:before { + content: "\f0d2"; } + +.fa-buffer:before { + content: "\f837"; } + +.fa-npm:before { + content: "\f3d4"; } + +.fa-yammer:before { + content: "\f840"; } + +.fa-btc:before { + content: "\f15a"; } + +.fa-dribbble:before { + content: "\f17d"; } + +.fa-stumbleupon-circle:before { + content: "\f1a3"; } + +.fa-internet-explorer:before { + content: "\f26b"; } + +.fa-stubber:before { + content: "\e5c7"; } + +.fa-telegram:before { + content: "\f2c6"; } + +.fa-telegram-plane:before { + content: "\f2c6"; } + +.fa-old-republic:before { + content: "\f510"; } + +.fa-odysee:before { + content: "\e5c6"; } + +.fa-square-whatsapp:before { + content: "\f40c"; } + +.fa-whatsapp-square:before { + content: "\f40c"; } + +.fa-node-js:before { + content: "\f3d3"; } + +.fa-edge-legacy:before { + content: "\e078"; } + +.fa-slack:before { + content: "\f198"; } + +.fa-slack-hash:before { + content: "\f198"; } + +.fa-medrt:before { + content: "\f3c8"; } + +.fa-usb:before { + content: "\f287"; } + +.fa-tumblr:before { + content: "\f173"; } + +.fa-vaadin:before { + content: "\f408"; } + +.fa-quora:before { + content: "\f2c4"; } + +.fa-square-x-twitter:before { + content: "\e61a"; } + +.fa-reacteurope:before { + content: "\f75d"; } + +.fa-medium:before { + content: "\f23a"; } + +.fa-medium-m:before { + content: "\f23a"; } + +.fa-amilia:before { + content: "\f36d"; } + +.fa-mixcloud:before { + content: "\f289"; } + +.fa-flipboard:before { + content: "\f44d"; } + +.fa-viacoin:before { + content: "\f237"; } + +.fa-critical-role:before { + content: "\f6c9"; } + +.fa-sitrox:before { + content: "\e44a"; } + +.fa-discourse:before { + content: "\f393"; } + +.fa-joomla:before { + content: "\f1aa"; } + +.fa-mastodon:before { + content: "\f4f6"; } + +.fa-airbnb:before { + content: "\f834"; } + +.fa-wolf-pack-battalion:before { + content: "\f514"; } + +.fa-buy-n-large:before { + content: "\f8a6"; } + +.fa-gulp:before { + content: "\f3ae"; } + +.fa-creative-commons-sampling-plus:before { + content: "\f4f1"; } + +.fa-strava:before { + content: "\f428"; } + +.fa-ember:before { + content: "\f423"; } + +.fa-canadian-maple-leaf:before { + content: "\f785"; } + +.fa-teamspeak:before { + content: "\f4f9"; } + +.fa-pushed:before { + content: "\f3e1"; } + +.fa-wordpress-simple:before { + content: "\f411"; } + +.fa-nutritionix:before { + content: "\f3d6"; } + +.fa-wodu:before { + content: "\e088"; } + +.fa-google-pay:before { + content: "\e079"; } + +.fa-intercom:before { + content: "\f7af"; } + +.fa-zhihu:before { + content: "\f63f"; } + +.fa-korvue:before { + content: "\f42f"; } + +.fa-pix:before { + content: "\e43a"; } + +.fa-steam-symbol:before { + content: "\f3f6"; } +:root, :host { + --fa-style-family-classic: 'Font Awesome 6 Free'; + --fa-font-regular: normal 400 1em/1 'Font Awesome 6 Free'; } + +@font-face { + font-family: 'Font Awesome 6 Free'; + font-style: normal; + font-weight: 400; + font-display: block; + src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); } + +.far, +.fa-regular { + font-weight: 400; } +:root, :host { + --fa-style-family-classic: 'Font Awesome 6 Free'; + --fa-font-solid: normal 900 1em/1 'Font Awesome 6 Free'; } + +@font-face { + font-family: 'Font Awesome 6 Free'; + font-style: normal; + font-weight: 900; + font-display: block; + src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); } + +.fas, +.fa-solid { + font-weight: 900; } +@font-face { + font-family: 'Font Awesome 5 Brands'; + font-display: block; + font-weight: 400; + src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); } + +@font-face { + font-family: 'Font Awesome 5 Free'; + font-display: block; + font-weight: 900; + src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); } + +@font-face { + font-family: 'Font Awesome 5 Free'; + font-display: block; + font-weight: 400; + src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); } +@font-face { + font-family: 'FontAwesome'; + font-display: block; + src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); } + +@font-face { + font-family: 'FontAwesome'; + font-display: block; + src: url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.ttf") format("truetype"); } + +@font-face { + font-family: 'FontAwesome'; + font-display: block; + src: url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.ttf") format("truetype"); + unicode-range: U+F003,U+F006,U+F014,U+F016-F017,U+F01A-F01B,U+F01D,U+F022,U+F03E,U+F044,U+F046,U+F05C-F05D,U+F06E,U+F070,U+F087-F088,U+F08A,U+F094,U+F096-F097,U+F09D,U+F0A0,U+F0A2,U+F0A4-F0A7,U+F0C5,U+F0C7,U+F0E5-F0E6,U+F0EB,U+F0F6-F0F8,U+F10C,U+F114-F115,U+F118-F11A,U+F11C-F11D,U+F133,U+F147,U+F14E,U+F150-F152,U+F185-F186,U+F18E,U+F190-F192,U+F196,U+F1C1-F1C9,U+F1D9,U+F1DB,U+F1E3,U+F1EA,U+F1F7,U+F1F9,U+F20A,U+F247-F248,U+F24A,U+F24D,U+F255-F25B,U+F25D,U+F271-F274,U+F278,U+F27B,U+F28C,U+F28E,U+F29C,U+F2B5,U+F2B7,U+F2BA,U+F2BC,U+F2BE,U+F2C0-F2C1,U+F2C3,U+F2D0,U+F2D2,U+F2D4,U+F2DC; } + +@font-face { + font-family: 'FontAwesome'; + font-display: block; + src: url("../webfonts/fa-v4compatibility.woff2") format("woff2"), url("../webfonts/fa-v4compatibility.ttf") format("truetype"); + unicode-range: U+F041,U+F047,U+F065-F066,U+F07D-F07E,U+F080,U+F08B,U+F08E,U+F090,U+F09A,U+F0AC,U+F0AE,U+F0B2,U+F0D0,U+F0D6,U+F0E4,U+F0EC,U+F10A-F10B,U+F123,U+F13E,U+F148-F149,U+F14C,U+F156,U+F15E,U+F160-F161,U+F163,U+F175-F178,U+F195,U+F1F8,U+F219,U+F27A; } diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-brands-400.ttf b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-brands-400.ttf new file mode 100644 index 0000000000..5efb1d4f96 Binary files /dev/null and b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-brands-400.ttf differ diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-brands-400.woff2 b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-brands-400.woff2 new file mode 100644 index 0000000000..36fbda7d33 Binary files /dev/null and b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-brands-400.woff2 differ diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-regular-400.ttf b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-regular-400.ttf new file mode 100644 index 0000000000..838b4e2cfe Binary files /dev/null and b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-regular-400.ttf differ diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-regular-400.woff2 b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-regular-400.woff2 new file mode 100644 index 0000000000..b6cabbacb6 Binary files /dev/null and b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-regular-400.woff2 differ diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-solid-900.ttf b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-solid-900.ttf new file mode 100644 index 0000000000..ec24749db9 Binary files /dev/null and b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-solid-900.ttf differ diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-solid-900.woff2 b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-solid-900.woff2 new file mode 100644 index 0000000000..824d518eb4 Binary files /dev/null and b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-solid-900.woff2 differ diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-v4compatibility.ttf b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-v4compatibility.ttf new file mode 100644 index 0000000000..b175aa8ece Binary files /dev/null and b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-v4compatibility.ttf differ diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-v4compatibility.woff2 b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-v4compatibility.woff2 new file mode 100644 index 0000000000..e09b5a5500 Binary files /dev/null and b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor/wwwroot/libs/fontawesome/webfonts/fa-v4compatibility.woff2 differ diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Extensions/TagHelperOutputExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Extensions/TagHelperOutputExtensions.cs index 40ca23a8f1..7b1b4372b5 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Extensions/TagHelperOutputExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Extensions/TagHelperOutputExtensions.cs @@ -1,11 +1,17 @@ using Microsoft.AspNetCore.Razor.TagHelpers; +using System; using System.IO; +using System.Linq; using System.Text.Encodings.Web; namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Extensions; public static class TagHelperOutputExtensions { + // ASCII whitespace per the HTML5 spec, used to tokenize space-separated + // attribute values such as aria-describedby. + private static readonly char[] HtmlAsciiWhitespace = { ' ', '\t', '\n', '\r', '\f' }; + public static string Render(this TagHelperOutput output, HtmlEncoder htmlEncoder) { using (var writer = new StringWriter()) @@ -14,4 +20,35 @@ public static class TagHelperOutputExtensions return writer.ToString(); } } + + /// + /// Appends an id token to the space-separated aria-describedby attribute, + /// preserving any tokens that were already present (e.g. provided by the consumer) + /// and skipping the token when it is already in the list. + /// + public static void AppendAriaDescribedby(this TagHelperOutput output, string token) + { + if (string.IsNullOrEmpty(token)) + { + return; + } + + var existing = output.Attributes + .FirstOrDefault(a => string.Equals(a.Name, "aria-describedby", StringComparison.OrdinalIgnoreCase)) + ?.Value?.ToString(); + + if (string.IsNullOrWhiteSpace(existing)) + { + output.Attributes.SetAttribute("aria-describedby", token); + return; + } + + var tokens = existing.Split(HtmlAsciiWhitespace, StringSplitOptions.RemoveEmptyEntries); + if (tokens.Any(t => t == token)) + { + return; + } + + output.Attributes.SetAttribute("aria-describedby", existing + " " + token); + } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs index f40ff1030b..f8994a462c 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpInputTagHelperService.cs @@ -88,10 +88,10 @@ public class AbpInputTagHelperService : AbpTagHelperService var (inputTag, isCheckBox) = await GetInputTagHelperOutputAsync(context, output); context.Items[nameof(IsOutputHidden)] = IsOutputHidden(inputTag); - var inputHtml = inputTag.Render(_encoder); var label = await GetLabelAsHtmlAsync(context, output, inputTag, isCheckBox); var info = GetInfoAsHtml(context, output, inputTag, isCheckBox); var validation = isCheckBox ? "" : await GetValidationAsHtmlAsync(context, output, inputTag); + var inputHtml = inputTag.Render(_encoder); return (GetContent(context, output, label, inputHtml, validation, info, isCheckBox), isCheckBox); } @@ -259,15 +259,14 @@ public class AbpInputTagHelperService : AbpTagHelperService } var idAttr = inputTagHelperOutput.Attributes.FirstOrDefault(a => a.Name == "id"); + var idValue = idAttr?.Value?.ToString(); - if (idAttr == null) + if (string.IsNullOrEmpty(idValue)) { return; } - var infoText = _tagHelperLocalizer.GetLocalizedText(idAttr.Value + "InfoText", TagHelper.AspFor.ModelExplorer); - - inputTagHelperOutput.Attributes.Add("aria-describedby", infoText); + inputTagHelperOutput.AppendAriaDescribedby(idValue + "InfoText"); } protected virtual bool IsInputCheckbox(TagHelperContext context, TagHelperOutput output, TagHelperAttributeList attributes) @@ -356,14 +355,18 @@ public class AbpInputTagHelperService : AbpTagHelperService } var idAttr = inputTag.Attributes.FirstOrDefault(a => a.Name == "id"); + var idValue = idAttr?.Value?.ToString(); var localizedText = _tagHelperLocalizer.GetLocalizedText(text, TagHelper.AspFor.ModelExplorer); var div = new TagBuilder("div"); - div.Attributes.Add("id", idAttr?.Value + "InfoText"); div.AddCssClass("form-text"); div.InnerHtml.Append(localizedText); - inputTag.Attributes.Add("aria-describedby", idAttr?.Value + "InfoText"); + if (!string.IsNullOrEmpty(idValue)) + { + div.Attributes.Add("id", idValue + "InfoText"); + inputTag.AppendAriaDescribedby(idValue + "InfoText"); + } return div.ToHtmlString(); } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs index c411d571f4..42d85cfb74 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/Form/AbpSelectTagHelperService.cs @@ -73,10 +73,10 @@ public class AbpSelectTagHelperService : AbpTagHelperService protected virtual async Task GetFormInputGroupAsHtmlAsync(TagHelperContext context, TagHelperOutput output, TagHelperContent childContent) { var selectTag = await GetSelectTagAsync(context, output, childContent); - var selectAsHtml = selectTag.Render(_encoder); var label = await GetLabelAsHtmlAsync(context, output, selectTag); var validation = await GetValidationAsHtmlAsync(context, output, selectTag); var infoText = GetInfoAsHtml(context, output, selectTag); + var selectAsHtml = selectTag.Render(_encoder); return TagHelper.FloatingLabel ? selectAsHtml + Environment.NewLine + label + Environment.NewLine + infoText + Environment.NewLine + validation : label + Environment.NewLine + selectAsHtml + Environment.NewLine + infoText + Environment.NewLine + validation; @@ -216,15 +216,14 @@ public class AbpSelectTagHelperService : AbpTagHelperService } var idAttr = inputTagHelperOutput.Attributes.FirstOrDefault(a => a.Name == "id"); + var idValue = idAttr?.Value?.ToString(); - if (idAttr == null) + if (string.IsNullOrEmpty(idValue)) { return; } - var infoText = _tagHelperLocalizer.GetLocalizedText(idAttr.Value + "InfoText", TagHelper.AspFor.ModelExplorer); - - inputTagHelperOutput.Attributes.Add("aria-describedby", infoText); + inputTagHelperOutput.AppendAriaDescribedby(idValue + "InfoText"); } protected virtual string GetInfoAsHtml(TagHelperContext context, TagHelperOutput output, TagHelperOutput inputTag) @@ -249,14 +248,20 @@ public class AbpSelectTagHelperService : AbpTagHelperService } var idAttr = inputTag.Attributes.FirstOrDefault(a => a.Name == "id"); + var idValue = idAttr?.Value?.ToString(); var localizedText = _tagHelperLocalizer.GetLocalizedText(text, TagHelper.AspFor.ModelExplorer); - var small = new TagBuilder("small"); - small.Attributes.Add("id", idAttr?.Value?.ToString() + "InfoText"); - small.AddCssClass("form-text"); - small.InnerHtml.Append(localizedText); + var div = new TagBuilder("div"); + div.AddCssClass("form-text"); + div.InnerHtml.Append(localizedText); + + if (!string.IsNullOrEmpty(idValue)) + { + div.Attributes.Add("id", idValue + "InfoText"); + inputTag.AppendAriaDescribedby(idValue + "InfoText"); + } - return small.ToHtmlString(); + return div.ToHtmlString(); } protected virtual List GetSelectItemsFromEnum(TagHelperContext context, TagHelperOutput output, ModelExplorer explorer) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/AbpAspNetCoreMvcUiThemeSharedModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/AbpAspNetCoreMvcUiThemeSharedModule.cs index b62d3f27ba..da418c8dc2 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/AbpAspNetCoreMvcUiThemeSharedModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/AbpAspNetCoreMvcUiThemeSharedModule.cs @@ -4,6 +4,7 @@ using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Packages; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; +using Volo.Abp.Features; using Volo.Abp.Modularity; using Volo.Abp.VirtualFileSystem; @@ -12,7 +13,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; [DependsOn( typeof(AbpAspNetCoreMvcUiBootstrapModule), typeof(AbpAspNetCoreMvcUiPackagesModule), - typeof(AbpAspNetCoreMvcUiWidgetsModule) + typeof(AbpAspNetCoreMvcUiWidgetsModule), + typeof(AbpFeaturesModule) )] public class AbpAspNetCoreMvcUiThemeSharedModule : AbpModule { diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Toolbars/ToolbarManager.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Toolbars/ToolbarManager.cs index 7822277371..c4e25c98a9 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Toolbars/ToolbarManager.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Toolbars/ToolbarManager.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Options; using Volo.Abp.AspNetCore.Mvc.UI.Theming; using Volo.Abp.Authorization.Permissions; using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; using Volo.Abp.SimpleStateChecking; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars; @@ -37,6 +38,7 @@ public class ToolbarManager : IToolbarManager, ITransientDependency using (var scope = ServiceProvider.CreateScope()) { using (RequirePermissionsSimpleBatchStateChecker.Use(new RequirePermissionsSimpleBatchStateChecker())) + using (RequireFeaturesSimpleBatchStateChecker.Use(new RequireFeaturesSimpleBatchStateChecker())) { var context = new ToolbarConfigurationContext(ThemeManager.CurrentTheme, toolbar, scope.ServiceProvider); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj index f80e51eb05..c2bc439691 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.csproj @@ -30,6 +30,7 @@ + diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/AuthenticatedSimpleStateCheckerSerializerContributor.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/AuthenticatedSimpleStateCheckerSerializerContributor.cs index d23273e7a2..13c6f92d94 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/AuthenticatedSimpleStateCheckerSerializerContributor.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/AuthenticatedSimpleStateCheckerSerializerContributor.cs @@ -10,7 +10,7 @@ public class AuthenticatedSimpleStateCheckerSerializerContributor : { public const string CheckerShortName = "A"; - public string? SerializeToJson(ISimpleStateChecker checker) + public string? SerializeToJson(ISimpleStateChecker checker) where TState : IHasSimpleStateCheckers { if (checker is not RequireAuthenticatedSimpleStateChecker) @@ -25,6 +25,10 @@ public class AuthenticatedSimpleStateCheckerSerializerContributor : return jsonObject.ToJsonString(); } + public string? SerializeToJson(ISimpleStateChecker checker, TState state) + where TState : IHasSimpleStateCheckers + => SerializeToJson(checker); + public ISimpleStateChecker? Deserialize(JsonObject jsonObject, TState state) where TState : IHasSimpleStateCheckers { diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionsSimpleStateCheckerSerializerContributor.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionsSimpleStateCheckerSerializerContributor.cs index 1a06b83ed7..61978be6c4 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionsSimpleStateCheckerSerializerContributor.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/PermissionsSimpleStateCheckerSerializerContributor.cs @@ -20,13 +20,31 @@ public class PermissionsSimpleStateCheckerSerializerContributor : return null; } - var jsonObject = new JsonObject { + return BuildJson(permissionsSimpleStateChecker.RequiresAll, permissionsSimpleStateChecker.PermissionNames); + } + + public string? SerializeToJson(ISimpleStateChecker checker, TState state) + where TState : IHasSimpleStateCheckers + { + if (checker is RequirePermissionsSimpleBatchStateChecker batch) + { + var model = batch.GetModelOrNull(state); + return model == null ? null : BuildJson(model.RequiresAll, model.Permissions); + } + + return SerializeToJson(checker); + } + + private static string BuildJson(bool requiresAll, string[] permissionNames) + { + var jsonObject = new JsonObject + { ["T"] = CheckerShortName, - ["A"] = permissionsSimpleStateChecker.RequiresAll + ["A"] = requiresAll }; var nameArray = new JsonArray(); - foreach (var permissionName in permissionsSimpleStateChecker.PermissionNames) + foreach (var permissionName in permissionNames) { nameArray.Add(permissionName); } diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RequirePermissionsSimpleBatchStateChecker.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RequirePermissionsSimpleBatchStateChecker.cs index 70e74a6bca..d78e0ceb6f 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RequirePermissionsSimpleBatchStateChecker.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/Permissions/RequirePermissionsSimpleBatchStateChecker.cs @@ -11,19 +11,28 @@ namespace Volo.Abp.Authorization.Permissions; public class RequirePermissionsSimpleBatchStateChecker : SimpleBatchStateCheckerBase where TState : IHasSimpleStateCheckers { - public static RequirePermissionsSimpleBatchStateChecker Current => _current.Value!; - private static readonly AsyncLocal> _current = new AsyncLocal>(); + public static RequirePermissionsSimpleBatchStateChecker Current + { + get + { + if (_current.Value == null) + { + _current.Value = new RequirePermissionsSimpleBatchStateChecker(); + } + + return _current.Value; + } + } + private static readonly AsyncLocal?> _current = new AsyncLocal?>(); private readonly List> _models; - static RequirePermissionsSimpleBatchStateChecker() - { - _current.Value = new RequirePermissionsSimpleBatchStateChecker(); - } + private readonly Dictionary> _modelsByState; public RequirePermissionsSimpleBatchStateChecker() { _models = new List>(); + _modelsByState = new Dictionary>(); } public RequirePermissionsSimpleBatchStateChecker AddCheckModels(params RequirePermissionsSimpleBatchStateCheckerModel[] models) @@ -31,6 +40,13 @@ public class RequirePermissionsSimpleBatchStateChecker : SimpleBatchStat Check.NotNullOrEmpty(models, nameof(models)); _models.AddRange(models); + foreach (var model in models) + { + if (!_modelsByState.ContainsKey(model.State)) + { + _modelsByState[model.State] = model; + } + } return this; } @@ -41,19 +57,44 @@ public class RequirePermissionsSimpleBatchStateChecker : SimpleBatchStat return new DisposeAction(() => _current.Value = previousValue); } + public virtual RequirePermissionsSimpleBatchStateCheckerModel? GetModelOrNull(TState state) + { + return _modelsByState.TryGetValue(state, out var model) ? model : null; + } + public override async Task> IsEnabledAsync(SimpleBatchStateCheckerContext context) { var permissionChecker = context.ServiceProvider.GetRequiredService(); var result = new SimpleStateCheckerResult(context.States); - var permissions = _models.Where(x => context.States.Any(s => s.Equals(x.State))).SelectMany(x => x.Permissions).Distinct().ToArray(); - var grantResult = await permissionChecker.IsGrantedAsync(permissions); + var stateSet = new HashSet(context.States); + var modelLookup = new Dictionary>(); + var allPermissions = new HashSet(); + + foreach (var model in _models) + { + if (!stateSet.Contains(model.State)) + { + continue; + } + + if (!modelLookup.ContainsKey(model.State)) + { + modelLookup[model.State] = model; + } + + foreach (var permission in model.Permissions) + { + allPermissions.Add(permission); + } + } + + var grantResult = await permissionChecker.IsGrantedAsync(allPermissions.ToArray()); foreach (var state in context.States) { - var model = _models.FirstOrDefault(x => x.State.Equals(state)); - if (model != null) + if (modelLookup.TryGetValue(state, out var model)) { if (model.RequiresAll) { diff --git a/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireDynamicBackgroundWorkerManager.cs b/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireDynamicBackgroundWorkerManager.cs index a9b9026606..d54e37c534 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireDynamicBackgroundWorkerManager.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireDynamicBackgroundWorkerManager.cs @@ -13,7 +13,11 @@ using Volo.Abp.Hangfire; namespace Volo.Abp.BackgroundWorkers.Hangfire; [Dependency(ReplaceServices = true)] -public class HangfireDynamicBackgroundWorkerManager : IDynamicBackgroundWorkerManager, ISingletonDependency +public class HangfireDynamicBackgroundWorkerManager : + IDynamicBackgroundWorkerManager, + ISupportsRuntimeRegistration, + ISupportsCronScheduling, + ISingletonDependency { protected IServiceProvider ServiceProvider { get; } protected IDynamicBackgroundWorkerHandlerRegistry HandlerRegistry { get; } diff --git a/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzDynamicBackgroundWorkerManager.cs b/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzDynamicBackgroundWorkerManager.cs index 5a729ad974..2f098d6ab4 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzDynamicBackgroundWorkerManager.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzDynamicBackgroundWorkerManager.cs @@ -10,7 +10,11 @@ using Volo.Abp.DependencyInjection; namespace Volo.Abp.BackgroundWorkers.Quartz; [Dependency(ReplaceServices = true)] -public class QuartzDynamicBackgroundWorkerManager : IDynamicBackgroundWorkerManager, ISingletonDependency +public class QuartzDynamicBackgroundWorkerManager : + IDynamicBackgroundWorkerManager, + ISupportsRuntimeRegistration, + ISupportsCronScheduling, + ISingletonDependency { public const string DynamicWorkerNameKey = "AbpDynamicWorkerName"; diff --git a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/DefaultDynamicBackgroundWorkerManager.cs b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/DefaultDynamicBackgroundWorkerManager.cs index 439e61de21..62dce443a8 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/DefaultDynamicBackgroundWorkerManager.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/DefaultDynamicBackgroundWorkerManager.cs @@ -10,7 +10,10 @@ using Volo.Abp.Threading; namespace Volo.Abp.BackgroundWorkers; -public class DefaultDynamicBackgroundWorkerManager : IDynamicBackgroundWorkerManager, ISingletonDependency +public class DefaultDynamicBackgroundWorkerManager : + IDynamicBackgroundWorkerManager, + ISupportsRuntimeRegistration, + ISingletonDependency { protected IServiceProvider ServiceProvider { get; } public ILogger Logger { get; set; } @@ -39,11 +42,11 @@ public class DefaultDynamicBackgroundWorkerManager : IDynamicBackgroundWorkerMan schedule.Validate(); - if (schedule.Period == null) + if (!schedule.CronExpression.IsNullOrWhiteSpace()) { throw new AbpException( - $"The default in-memory background worker manager does not support CronExpression without Period for dynamic worker '{workerName}'. " + - "Please set Period, or use a scheduler-backed provider (Hangfire, Quartz, TickerQ)."); + $"The default in-memory background worker manager does not support CronExpression for dynamic worker '{workerName}'. " + + "Please clear CronExpression and use Period-based scheduling, or use a scheduler-backed provider (Hangfire or Quartz)."); } await _semaphore.WaitAsync(cancellationToken); @@ -102,11 +105,11 @@ public class DefaultDynamicBackgroundWorkerManager : IDynamicBackgroundWorkerMan schedule.Validate(); - if (schedule.Period == null) + if (!schedule.CronExpression.IsNullOrWhiteSpace()) { throw new AbpException( - $"The default in-memory background worker manager does not support CronExpression without Period for dynamic worker '{workerName}'. " + - "Please set Period, or use a scheduler-backed provider (Hangfire, Quartz, TickerQ)."); + $"The default in-memory background worker manager does not support CronExpression for dynamic worker '{workerName}'. " + + "Please clear CronExpression and use Period-based scheduling, or use a scheduler-backed provider (Hangfire or Quartz)."); } await _semaphore.WaitAsync(cancellationToken); diff --git a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/IDynamicBackgroundWorkerManager.cs b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/IDynamicBackgroundWorkerManager.cs index 7e625e7c9c..a897549249 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/IDynamicBackgroundWorkerManager.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/IDynamicBackgroundWorkerManager.cs @@ -6,6 +6,12 @@ namespace Volo.Abp.BackgroundWorkers; /// /// Manages dynamic background workers that are registered at runtime /// without requiring a strongly-typed worker class. +/// +/// Implementations may differ in capabilities. Check +/// before calling / / , +/// and before passing +/// . +/// /// public interface IDynamicBackgroundWorkerManager { diff --git a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/ISupportsCronScheduling.cs b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/ISupportsCronScheduling.cs new file mode 100644 index 0000000000..9160ef368a --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/ISupportsCronScheduling.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.BackgroundWorkers; + +/// +/// Marks a dynamic background worker manager that supports cron-based scheduling. +/// +public interface ISupportsCronScheduling +{ +} diff --git a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/ISupportsRuntimeRegistration.cs b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/ISupportsRuntimeRegistration.cs new file mode 100644 index 0000000000..4f343113bf --- /dev/null +++ b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/ISupportsRuntimeRegistration.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.BackgroundWorkers; + +/// +/// Marks a dynamic background worker manager that supports registering workers at runtime. +/// +public interface ISupportsRuntimeRegistration +{ +} diff --git a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/InMemoryDynamicBackgroundWorker.cs b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/InMemoryDynamicBackgroundWorker.cs index bab468a655..7bffb3895a 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/InMemoryDynamicBackgroundWorker.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/InMemoryDynamicBackgroundWorker.cs @@ -1,10 +1,12 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; using Volo.Abp.Threading; namespace Volo.Abp.BackgroundWorkers; +[DisableConventionalRegistration] public class InMemoryDynamicBackgroundWorker : AsyncPeriodicBackgroundWorkerBase { public string WorkerName { get; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs index a594613b44..257e12d0dc 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net; using System.Net.Http; using System.Text; using System.Text.RegularExpressions; @@ -240,14 +241,14 @@ public class AbpIoSourceCodeStore : ISourceCodeStore, ITransientDependency using (var response = await client.PostAsync(url, stringContent, _cliHttpClientFactory.GetCancellationToken(TimeSpan.FromMinutes(10)))) { - await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(response); + await EnsureAbpIoSuccessfulResponseAsync(response); var result = await response.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize(result).Version; } } - catch (Exception ex) + catch (Exception ex) when (ex is not CliUsageException) { - Console.WriteLine("Error occured while getting the latest version from {0} : {1}", url, ex.Message); + Console.WriteLine("Error occurred while getting the latest version from {0} : {1}", url, ex.Message); return null; } } @@ -273,17 +274,39 @@ public class AbpIoSourceCodeStore : ISourceCodeStore, ITransientDependency using (var response = await client.PostAsync(url, stringContent, _cliHttpClientFactory.GetCancellationToken(TimeSpan.FromMinutes(10)))) { - await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(response); + await EnsureAbpIoSuccessfulResponseAsync(response); var result = await response.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize(result).Version; } } - catch (Exception) + catch (Exception ex) when (ex is not CliUsageException) { return null; } } + private async Task EnsureAbpIoSuccessfulResponseAsync(HttpResponseMessage responseMessage) + { + if (responseMessage is { StatusCode: HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden }) + { + var message = $"Remote server returns '{(int)responseMessage.StatusCode}-{responseMessage.ReasonPhrase}'. "; + + var serverError = await RemoteServiceExceptionHandler.GetAbpRemoteServiceErrorAsync(responseMessage); + if (!string.IsNullOrWhiteSpace(serverError)) + { + message += serverError + " "; + } + + message += $"Authentication or license check failed while accessing {CliUrls.WwwAbpIo}. " + + "Please make sure you are logged in with `abp login ` and your ABP commercial license is active and covers the requested version. " + + $"You can check your license at {CliUrls.WwwAbpIo}my-organizations"; + + throw new CliUsageException(message); + } + + await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(responseMessage); + } + private async Task IsVersionExists(string templateName, string version) { var url = $"{CliUrls.WwwAbpIo}api/download/all-versions?includePreReleases=true"; @@ -313,6 +336,8 @@ public class AbpIoSourceCodeStore : ISourceCodeStore, ITransientDependency private async Task DownloadSourceCodeContentAsync(SourceCodeDownloadInputDto input) { var url = $"{CliUrls.WwwAbpIo}api/download/{input.Type}/"; + var isAbpIoDownload = input.TemplateSource.IsNullOrWhiteSpace(); + var downloadUrl = isAbpIoDownload ? url : input.TemplateSource; HttpResponseMessage responseMessage = null; @@ -320,7 +345,7 @@ public class AbpIoSourceCodeStore : ISourceCodeStore, ITransientDependency { var client = _cliHttpClientFactory.CreateClient(timeout: TimeSpan.FromMinutes(5)); - if (input.TemplateSource.IsNullOrWhiteSpace()) + if (isAbpIoDownload) { responseMessage = await client.PostAsync( url, @@ -334,24 +359,38 @@ public class AbpIoSourceCodeStore : ISourceCodeStore, ITransientDependency _cliHttpClientFactory.GetCancellationToken()); } - await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(responseMessage); - var resultAsBytes = await responseMessage.Content.ReadAsByteArrayAsync(); - responseMessage.Dispose(); + if (isAbpIoDownload) + { + await EnsureAbpIoSuccessfulResponseAsync(responseMessage); + } + else + { + await RemoteServiceExceptionHandler.EnsureSuccessfulHttpResponseAsync(responseMessage); + } - return resultAsBytes; + return await responseMessage.Content.ReadAsByteArrayAsync(); } catch (Exception ex) { - if(ex is UserFriendlyException) + if (ex is CliUsageException) + { + throw; + } + + if (ex is UserFriendlyException) { Logger.LogWarning(ex.Message); throw; } - Console.WriteLine("Error occured while downloading source-code from {0} : {1}{2}{3}", url, + Console.WriteLine("Error occurred while downloading source-code from {0} : {1}{2}{3}", downloadUrl, responseMessage?.ToString(), Environment.NewLine, ex.Message); throw; } + finally + { + responseMessage?.Dispose(); + } } private static bool IsNetworkSource(string source) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/RemoteServiceExceptionHandler.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/RemoteServiceExceptionHandler.cs index fffdb6291f..2cf1e5f6ab 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/RemoteServiceExceptionHandler.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/RemoteServiceExceptionHandler.cs @@ -4,10 +4,11 @@ using System.Linq; using System.Net.Http; using System.Text; using System.Threading.Tasks; -using Newtonsoft.Json; using Volo.Abp.DependencyInjection; using Volo.Abp.Http; using Volo.Abp.Json; +using NewtonsoftJsonException = Newtonsoft.Json.JsonException; +using SystemJsonException = System.Text.Json.JsonException; namespace Volo.Abp.Cli.ProjectBuilding; @@ -49,12 +50,11 @@ public class RemoteServiceExceptionHandler : IRemoteServiceExceptionHandler, ITr RemoteServiceErrorResponse errorResult; try { - errorResult = _jsonSerializer.Deserialize - ( + errorResult = _jsonSerializer.Deserialize( await responseMessage.Content.ReadAsStringAsync() ); } - catch (JsonReaderException) + catch (Exception ex) when (IsJsonException(ex)) { return null; } @@ -98,4 +98,9 @@ public class RemoteServiceExceptionHandler : IRemoteServiceExceptionHandler, ITr return sbError.ToString(); } + + private static bool IsJsonException(Exception ex) + { + return ex is SystemJsonException or NewtonsoftJsonException; + } } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/ISimpleStateCheckerSerializer.cs b/framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/ISimpleStateCheckerSerializer.cs index 96c0785d6e..0be80ff319 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/ISimpleStateCheckerSerializer.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/ISimpleStateCheckerSerializer.cs @@ -7,6 +7,10 @@ public interface ISimpleStateCheckerSerializer public string? Serialize(ISimpleStateChecker checker) where TState : IHasSimpleStateCheckers; + + public string? Serialize(ISimpleStateChecker checker, TState state) + where TState : IHasSimpleStateCheckers; + public ISimpleStateChecker? Deserialize(JsonObject jsonObject, TState state) where TState : IHasSimpleStateCheckers; } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/ISimpleStateCheckerSerializerContributor.cs b/framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/ISimpleStateCheckerSerializerContributor.cs index 79510024b7..b8c3e4943f 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/ISimpleStateCheckerSerializerContributor.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/ISimpleStateCheckerSerializerContributor.cs @@ -7,6 +7,9 @@ public interface ISimpleStateCheckerSerializerContributor public string? SerializeToJson(ISimpleStateChecker checker) where TState : IHasSimpleStateCheckers; + public string? SerializeToJson(ISimpleStateChecker checker, TState state) + where TState : IHasSimpleStateCheckers; + public ISimpleStateChecker? Deserialize(JsonObject jsonObject, TState state) where TState : IHasSimpleStateCheckers; } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/SimpleStateCheckerManager.cs b/framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/SimpleStateCheckerManager.cs index 1726a89f84..ffc02d3207 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/SimpleStateCheckerManager.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/SimpleStateCheckerManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -46,7 +46,7 @@ public class SimpleStateCheckerManager : ISimpleStateCheckerManager !x)) @@ -57,7 +57,7 @@ public class SimpleStateCheckerManager : ISimpleStateCheckerManager globalStateChecker in Options.GlobalStateCheckers .Where(x => typeof(ISimpleBatchStateChecker).IsAssignableFrom(x)) - .Select(x => ServiceProvider.GetRequiredService(x))) + .Select(x => scope.ServiceProvider.GetRequiredService(x))) { var context = new SimpleBatchStateCheckerContext( scope.ServiceProvider.GetRequiredService(), @@ -69,11 +69,15 @@ public class SimpleStateCheckerManager : ISimpleStateCheckerManager !typeof(ISimpleBatchStateChecker).IsAssignableFrom(x)); + foreach (var state in states) { - if (result[state]) + if (result[state] && + (hasNonBatchGlobalCheckers || state.StateCheckers.Any(x => x is not ISimpleBatchStateChecker))) { - result[state] = await InternalIsEnabledAsync(state, false); + result[state] = await EvaluateCheckersAsync(state, false, scope); } } @@ -96,27 +100,36 @@ public class SimpleStateCheckerManager : ISimpleStateCheckerManager(scope.ServiceProvider.GetRequiredService(), state); + return await EvaluateCheckersAsync(state, useBatchChecker, scope); + } + } - foreach (var provider in state.StateCheckers.WhereIf(!useBatchChecker, x => x is not ISimpleBatchStateChecker)) + protected virtual async Task EvaluateCheckersAsync(TState state, bool useBatchChecker, IServiceScope scope) + { + var context = new SimpleStateCheckerContext( + !useBatchChecker + ? scope.ServiceProvider.GetRequiredService() + : scope.ServiceProvider.GetRequiredService(), + state); + + foreach (var provider in state.StateCheckers.WhereIf(!useBatchChecker, x => x is not ISimpleBatchStateChecker)) + { + if (!await provider.IsEnabledAsync(context)) { - if (!await provider.IsEnabledAsync(context)) - { - return false; - } + return false; } + } - foreach (ISimpleStateChecker provider in Options.GlobalStateCheckers - .WhereIf(!useBatchChecker, x => !typeof(ISimpleBatchStateChecker).IsAssignableFrom(x)) - .Select(x => ServiceProvider.GetRequiredService(x))) + foreach (ISimpleStateChecker provider in Options.GlobalStateCheckers + .WhereIf(!useBatchChecker, x => !typeof(ISimpleBatchStateChecker).IsAssignableFrom(x)) + .Select(x => scope.ServiceProvider.GetRequiredService(x))) + { + if (!await provider.IsEnabledAsync(context)) { - if (!await provider.IsEnabledAsync(context)) - { - return false; - } + return false; } - - return true; } + + return true; } } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/SimpleStateCheckerSerializer.cs b/framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/SimpleStateCheckerSerializer.cs index 8ae58d16be..bc4004ec89 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/SimpleStateCheckerSerializer.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/SimpleStateCheckerSerializer.cs @@ -15,7 +15,7 @@ public class SimpleStateCheckerSerializer : _contributors = contributors; } - public string? Serialize(ISimpleStateChecker checker) + public string? Serialize(ISimpleStateChecker checker) where TState : IHasSimpleStateCheckers { foreach (var contributor in _contributors) @@ -30,6 +30,21 @@ public class SimpleStateCheckerSerializer : return null; } + public string? Serialize(ISimpleStateChecker checker, TState state) + where TState : IHasSimpleStateCheckers + { + foreach (var contributor in _contributors) + { + var result = contributor.SerializeToJson(checker, state); + if (result != null) + { + return result; + } + } + + return null; + } + public ISimpleStateChecker? Deserialize(JsonObject jsonObject, TState state) where TState : IHasSimpleStateCheckers { diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/SimpleStateCheckerSerializerExtensions.cs b/framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/SimpleStateCheckerSerializerExtensions.cs index 8d06c7cfc3..43e8ca0d6c 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/SimpleStateCheckerSerializerExtensions.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/SimpleStateChecking/SimpleStateCheckerSerializerExtensions.cs @@ -8,25 +8,42 @@ namespace Volo.Abp.SimpleStateChecking; public static class SimpleStateCheckerSerializerExtensions { public static string? Serialize( - this ISimpleStateCheckerSerializer serializer, + this ISimpleStateCheckerSerializer serializer, IList> stateCheckers) where TState : IHasSimpleStateCheckers + { + return SerializeCore(stateCheckers, serializer.Serialize); + } + + public static string? Serialize( + this ISimpleStateCheckerSerializer serializer, + IList> stateCheckers, + TState state) + where TState : IHasSimpleStateCheckers + { + return SerializeCore(stateCheckers, c => serializer.Serialize(c, state)); + } + + private static string? SerializeCore( + IList> stateCheckers, + Func, string?> serializeChecker) + where TState : IHasSimpleStateCheckers { switch (stateCheckers.Count) { case 0: return null; case 1: - var serializedChecker = serializer.Serialize(stateCheckers.Single()); + var serializedChecker = serializeChecker(stateCheckers.Single()); return serializedChecker != null ? $"[{serializedChecker}]" : null; default: var serializedCheckers = new List(stateCheckers.Count); - + foreach (var stateChecker in stateCheckers) { - var serialized = serializer.Serialize(stateChecker); + var serialized = serializeChecker(stateChecker); if (serialized != null) { serializedCheckers.Add(serialized); diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index dd2a9cfde7..553f90f859 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -197,29 +197,7 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, protected virtual EfCoreDatabaseProvider? GetDatabaseProviderOrNull(ModelBuilder modelBuilder) { - switch (Database.ProviderName) - { - case "Microsoft.EntityFrameworkCore.SqlServer": - return EfCoreDatabaseProvider.SqlServer; - case "Npgsql.EntityFrameworkCore.PostgreSQL": - return EfCoreDatabaseProvider.PostgreSql; - case "Pomelo.EntityFrameworkCore.MySql": - case "MySql.Data.MySqlClient": - return EfCoreDatabaseProvider.MySql; - case "Oracle.EntityFrameworkCore": - case "Devart.Data.Oracle.Entity.EFCore": - return EfCoreDatabaseProvider.Oracle; - case "Microsoft.EntityFrameworkCore.Sqlite": - return EfCoreDatabaseProvider.Sqlite; - case "Microsoft.EntityFrameworkCore.InMemory": - return EfCoreDatabaseProvider.InMemory; - case "FirebirdSql.EntityFrameworkCore.Firebird": - return EfCoreDatabaseProvider.Firebird; - case "Microsoft.EntityFrameworkCore.Cosmos": - return EfCoreDatabaseProvider.Cosmos; - default: - return null; - } + return EfCoreDatabaseProviderHelper.GetDatabaseProviderOrNull(Database.ProviderName); } public async override Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EfCoreDatabaseProviderHelper.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EfCoreDatabaseProviderHelper.cs new file mode 100644 index 0000000000..6b3c228c62 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/EfCoreDatabaseProviderHelper.cs @@ -0,0 +1,52 @@ +using System; + +namespace Volo.Abp.EntityFrameworkCore; + +public static class EfCoreDatabaseProviderHelper +{ + public static EfCoreDatabaseProvider? GetDatabaseProviderOrNull(string? providerName) + { + if (providerName.IsNullOrWhiteSpace()) + { + return null; + } + + if (providerName.Contains("SqlServer", StringComparison.OrdinalIgnoreCase)) + { + return EfCoreDatabaseProvider.SqlServer; + } + if (providerName.Contains("Npgsql", StringComparison.OrdinalIgnoreCase) || + providerName.Contains("PostgreSQL", StringComparison.OrdinalIgnoreCase)) + { + return EfCoreDatabaseProvider.PostgreSql; + } + if (providerName.Contains("MySql", StringComparison.OrdinalIgnoreCase) || + providerName.Contains("Pomelo", StringComparison.OrdinalIgnoreCase)) + { + return EfCoreDatabaseProvider.MySql; + } + if (providerName.Contains("Oracle", StringComparison.OrdinalIgnoreCase) || + providerName.Contains("Devart", StringComparison.OrdinalIgnoreCase)) + { + return EfCoreDatabaseProvider.Oracle; + } + if (providerName.Contains("Sqlite", StringComparison.OrdinalIgnoreCase)) + { + return EfCoreDatabaseProvider.Sqlite; + } + if (providerName.Contains("InMemory", StringComparison.OrdinalIgnoreCase)) + { + return EfCoreDatabaseProvider.InMemory; + } + if (providerName.Contains("Firebird", StringComparison.OrdinalIgnoreCase)) + { + return EfCoreDatabaseProvider.Firebird; + } + if (providerName.Contains("Cosmos", StringComparison.OrdinalIgnoreCase)) + { + return EfCoreDatabaseProvider.Cosmos; + } + + return null; + } +} diff --git a/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/ar.json b/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/ar.json index d261f4c15c..0dbe3950c9 100644 --- a/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/ar.json +++ b/framework/src/Volo.Abp.ExceptionHandling/Volo/Abp/ExceptionHandling/Localization/ar.json @@ -1,31 +1,31 @@ { "culture": "ar", "texts": { - "InternalServerErrorMessage": "حدث خطأ داخلي أثناء طلبك!", - "ValidationErrorMessage": "طلبك غير صحيح!", - "ValidationNarrativeErrorMessageTitle": "تم الكشف عن الأخطاء التالية أثناء التحقق .", - "DefaultErrorMessage": "حدث خطأ!", - "DefaultErrorMessageDetail": "لم يتم إرسال تفاصيل الخطأ بواسطة الخادم.", - "DefaultErrorMessage401": "أنت غير مصدق!", - "DefaultErrorMessage401Detail": "يجب عليك تسجيل الدخول لأداء هذه العملية.", - "DefaultErrorMessage403": "أنك غير مخول!", - "DefaultErrorMessage403Detail": "لا يسمح لك بإجراء هذه العملية!", - "DefaultErrorMessage404": "المورد غير موجود!", - "DefaultErrorMessage404Detail": "لم يتم العثور على المورد المطلوب على الخادم!", - "EntityNotFoundErrorMessage": "لا يوجد كيان {0} بالمعرف = {1}!", - "EntityNotFoundErrorMessageWithoutId": "لا يوجد كيان {0}!", - "AbpDbConcurrencyErrorMessage": "تم تغيير البيانات التي قدمتها بالفعل من قبل مستخدم/عميل آخر. يرجى تجاهل التغييرات التي قمت بها والمحاولة من البداية.", + "InternalServerErrorMessage": "حدث خطأ داخلي أثناء تنفيذ طلبك.", + "ValidationErrorMessage": "البيانات المُدخلة غير صحيحة.", + "ValidationNarrativeErrorMessageTitle": "تم العثور على الأخطاء التالية أثناء التحقق:", + "DefaultErrorMessage": "حدث خطأ.", + "DefaultErrorMessageDetail": "لم يتم إرسال تفاصيل الخطأ من الخادم.", + "DefaultErrorMessage401": "لم يتم تسجيل الدخول.", + "DefaultErrorMessage401Detail": "يجب تسجيل الدخول لإتمام هذه العملية.", + "DefaultErrorMessage403": "ليس لديك صلاحية.", + "DefaultErrorMessage403Detail": "غير مسموح لك بتنفيذ هذه العملية.", + "DefaultErrorMessage404": "المورد غير موجود.", + "DefaultErrorMessage404Detail": "تعذر العثور على المورد المطلوب.", + "EntityNotFoundErrorMessage": "لا يوجد {0} بالمعرف = {1}.", + "EntityNotFoundErrorMessageWithoutId": "العنصر {0} غير موجود.", + "AbpDbConcurrencyErrorMessage": "تم تغيير البيانات التي أرسلتها بواسطة مستخدم آخر. يرجى تجاهل التغييرات التي أجريتها والمحاولة مرة أخرى.", "Error": "خطأ", - "UnhandledException": "استثناء غير معالج!", - "Authorizing": "التحقق من الصلاحية…", + "UnhandledException": "حدث استثناء غير متوقع.", + "Authorizing": "جارٍ التحقق من الصلاحيات…", "401Message": "غير مصرح", "403Message": "ممنوع", "404Message": "الصفحة غير موجودة", - "500Message": "خطأ في الخادم الداخلي", - "403MessageDetail": "أنت غير مصرح لك لإجراء هذه العملية!", - "404MessageDetail": "عذرا ، لا يوجد شيء في هذا العنوان.", + "500Message": "خطأ داخلي في الخادم", + "403MessageDetail": "ليس لديك صلاحية لتنفيذ هذه العملية.", + "404MessageDetail": "عذرًا، الصفحة المطلوبة غير موجودة.", "Unauthorized": "غير مصرح", - "invalid_token": "الرمز غير صالح", - "SessionExpired": "انتهت جلستك. يرجى تسجيل الدخول مرة أخرى لمتابعة التطبيق." + "invalid_token": "الرمز المميز غير صالح", + "SessionExpired": "انتهت الجلسة. يرجى تسجيل الدخول مرة أخرى للمتابعة." } } diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerBase.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerBase.cs index f17213af2a..34c01820b3 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerBase.cs +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureCheckerBase.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; @@ -28,4 +29,16 @@ public abstract class FeatureCheckerBase : IFeatureChecker, ITransientDependency ); } } + + public virtual async Task> IsEnabledAsync(string[] names) + { + var result = new Dictionary(); + + foreach (var name in names) + { + result[name] = await IsEnabledAsync(name); + } + + return result; + } } diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureSimpleStateCheckerExtensions.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureSimpleStateCheckerExtensions.cs index ff1bca8229..2c62c29870 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureSimpleStateCheckerExtensions.cs +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureSimpleStateCheckerExtensions.cs @@ -1,4 +1,4 @@ -using JetBrains.Annotations; +using JetBrains.Annotations; using Volo.Abp.SimpleStateChecking; namespace Volo.Abp.Features; @@ -10,7 +10,7 @@ public static class FeatureSimpleStateCheckerExtensions params string[] features) where TState : IHasSimpleStateCheckers { - state.RequireFeatures(true, features); + state.RequireFeatures(requiresAll: true, batchCheck: true, features); return state; } @@ -19,11 +19,32 @@ public static class FeatureSimpleStateCheckerExtensions bool requiresAll, params string[] features) where TState : IHasSimpleStateCheckers + { + state.RequireFeatures(requiresAll: requiresAll, batchCheck: true, features); + return state; + } + + public static TState RequireFeatures( + [NotNull] this TState state, + bool requiresAll, + bool batchCheck, + params string[] features) + where TState : IHasSimpleStateCheckers { Check.NotNull(state, nameof(state)); Check.NotNullOrEmpty(features, nameof(features)); - state.StateCheckers.Add(new RequireFeaturesSimpleStateChecker(requiresAll, features)); + if (batchCheck) + { + RequireFeaturesSimpleBatchStateChecker.Current.AddCheckModels( + new RequireFeaturesSimpleBatchStateCheckerModel(state, features, requiresAll)); + state.StateCheckers.Add(RequireFeaturesSimpleBatchStateChecker.Current); + } + else + { + state.StateCheckers.Add(new RequireFeaturesSimpleStateChecker(requiresAll, features)); + } + return state; } } diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeaturesSimpleStateCheckerSerializerContributor.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeaturesSimpleStateCheckerSerializerContributor.cs index c604cb75b4..555a9d32a5 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeaturesSimpleStateCheckerSerializerContributor.cs +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeaturesSimpleStateCheckerSerializerContributor.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Text.Json.Nodes; using Volo.Abp.DependencyInjection; using Volo.Abp.SimpleStateChecking; @@ -10,7 +10,7 @@ public class FeaturesSimpleStateCheckerSerializerContributor : ISingletonDependency { public const string CheckerShortName = "F"; - + public string? SerializeToJson(ISimpleStateChecker checker) where TState : IHasSimpleStateCheckers { @@ -19,13 +19,31 @@ public class FeaturesSimpleStateCheckerSerializerContributor : return null; } - var jsonObject = new JsonObject { + return BuildJson(featuresSimpleStateChecker.RequiresAll, featuresSimpleStateChecker.FeatureNames); + } + + public string? SerializeToJson(ISimpleStateChecker checker, TState state) + where TState : IHasSimpleStateCheckers + { + if (checker is RequireFeaturesSimpleBatchStateChecker batch) + { + var model = batch.GetModelOrNull(state); + return model == null ? null : BuildJson(model.RequiresAll, model.FeatureNames); + } + + return SerializeToJson(checker); + } + + private static string BuildJson(bool requiresAll, string[] featureNames) + { + var jsonObject = new JsonObject + { ["T"] = CheckerShortName, - ["A"] = featuresSimpleStateChecker.RequiresAll + ["A"] = requiresAll }; var nameArray = new JsonArray(); - foreach (var featureName in featuresSimpleStateChecker.FeatureNames) + foreach (var featureName in featureNames) { nameArray.Add(featureName); } @@ -53,4 +71,4 @@ public class FeaturesSimpleStateCheckerSerializerContributor : nameArray.Select(x => x!.ToString()).ToArray() ); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureChecker.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureChecker.cs index 501c5ff8f8..f97b4504a0 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureChecker.cs +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureChecker.cs @@ -1,4 +1,5 @@ -using JetBrains.Annotations; +using System.Collections.Generic; +using JetBrains.Annotations; using System.Threading.Tasks; namespace Volo.Abp.Features; @@ -8,4 +9,6 @@ public interface IFeatureChecker Task GetOrNullAsync([NotNull] string name); Task IsEnabledAsync(string name); + + Task> IsEnabledAsync([NotNull] string[] names); } diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker.cs new file mode 100644 index 0000000000..5faf1b891a --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.SimpleStateChecking; + +namespace Volo.Abp.Features; + +public class RequireFeaturesSimpleBatchStateChecker : SimpleBatchStateCheckerBase + where TState : IHasSimpleStateCheckers +{ + public static RequireFeaturesSimpleBatchStateChecker Current + { + get + { + if (_current.Value == null) + { + _current.Value = new RequireFeaturesSimpleBatchStateChecker(); + } + + return _current.Value; + } + } + private static readonly AsyncLocal?> _current = new(); + + private readonly List> _models; + + private readonly Dictionary> _modelsByState; + + public RequireFeaturesSimpleBatchStateChecker() + { + _models = new List>(); + _modelsByState = new Dictionary>(); + } + + public RequireFeaturesSimpleBatchStateChecker AddCheckModels( + params RequireFeaturesSimpleBatchStateCheckerModel[] models) + { + Check.NotNullOrEmpty(models, nameof(models)); + + _models.AddRange(models); + foreach (var model in models) + { + if (!_modelsByState.ContainsKey(model.State)) + { + _modelsByState[model.State] = model; + } + } + return this; + } + + public static IDisposable Use(RequireFeaturesSimpleBatchStateChecker checker) + { + var previousValue = Current; + _current.Value = checker; + return new DisposeAction(() => _current.Value = previousValue); + } + + public virtual RequireFeaturesSimpleBatchStateCheckerModel? GetModelOrNull(TState state) + { + return _modelsByState.TryGetValue(state, out var model) ? model : null; + } + + public override async Task> IsEnabledAsync( + SimpleBatchStateCheckerContext context) + { + var featureChecker = context.ServiceProvider.GetRequiredService(); + + var result = new SimpleStateCheckerResult(context.States); + + var stateSet = new HashSet(context.States); + var modelLookup = new Dictionary>(); + var allFeatures = new HashSet(); + + foreach (var model in _models) + { + if (!stateSet.Contains(model.State)) + { + continue; + } + + if (!modelLookup.ContainsKey(model.State)) + { + modelLookup[model.State] = model; + } + + foreach (var featureName in model.FeatureNames) + { + allFeatures.Add(featureName); + } + } + + var featureValues = await featureChecker.IsEnabledAsync(allFeatures.ToArray()); + + foreach (var state in context.States) + { + if (modelLookup.TryGetValue(state, out var model)) + { + if (model.RequiresAll) + { + result[state] = model.FeatureNames.All(x => featureValues.TryGetValue(x, out var v) && v); + } + else + { + result[state] = model.FeatureNames.Any(x => featureValues.TryGetValue(x, out var v) && v); + } + } + } + + return result; + } +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/RequireFeaturesSimpleBatchStateCheckerModel.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/RequireFeaturesSimpleBatchStateCheckerModel.cs new file mode 100644 index 0000000000..389ebac3af --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/RequireFeaturesSimpleBatchStateCheckerModel.cs @@ -0,0 +1,23 @@ +using Volo.Abp.SimpleStateChecking; + +namespace Volo.Abp.Features; + +public class RequireFeaturesSimpleBatchStateCheckerModel + where TState : IHasSimpleStateCheckers +{ + public TState State { get; } + + public string[] FeatureNames { get; } + + public bool RequiresAll { get; } + + public RequireFeaturesSimpleBatchStateCheckerModel(TState state, string[] featureNames, bool requiresAll = true) + { + Check.NotNull(state, nameof(state)); + Check.NotNullOrEmpty(featureNames, nameof(featureNames)); + + State = state; + FeatureNames = featureNames; + RequiresAll = requiresAll; + } +} diff --git a/framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalFeaturesSimpleStateCheckerSerializerContributor.cs b/framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalFeaturesSimpleStateCheckerSerializerContributor.cs index fe8530435b..4063197cf1 100644 --- a/framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalFeaturesSimpleStateCheckerSerializerContributor.cs +++ b/framework/src/Volo.Abp.GlobalFeatures/Volo/Abp/GlobalFeatures/GlobalFeaturesSimpleStateCheckerSerializerContributor.cs @@ -34,6 +34,10 @@ public class GlobalFeaturesSimpleStateCheckerSerializerContributor : return jsonObject.ToJsonString(); } + public string? SerializeToJson(ISimpleStateChecker checker, TState state) + where TState : IHasSimpleStateCheckers + => SerializeToJson(checker); + public ISimpleStateChecker? Deserialize(JsonObject jsonObject, TState state) where TState : IHasSimpleStateCheckers { diff --git a/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/SkiaSharpCompressOptions.cs b/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/SkiaSharpCompressOptions.cs new file mode 100644 index 0000000000..1c7e029807 --- /dev/null +++ b/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/SkiaSharpCompressOptions.cs @@ -0,0 +1,11 @@ +namespace Volo.Abp.Imaging; + +public class SkiaSharpCompressOptions +{ + public int Quality { get; set; } + + public SkiaSharpCompressOptions() + { + Quality = 75; + } +} diff --git a/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/SkiaSharpImageCompressorContributor.cs b/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/SkiaSharpImageCompressorContributor.cs new file mode 100644 index 0000000000..c3117d0461 --- /dev/null +++ b/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/SkiaSharpImageCompressorContributor.cs @@ -0,0 +1,152 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using SkiaSharp; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http; + +namespace Volo.Abp.Imaging; + +public class SkiaSharpImageCompressorContributor : IImageCompressorContributor, ITransientDependency +{ + protected SkiaSharpCompressOptions Options { get; } + + public SkiaSharpImageCompressorContributor(IOptions options) + { + Options = options.Value; + } + + public virtual async Task> TryCompressAsync( + Stream stream, + string? mimeType = null, + CancellationToken cancellationToken = default) + { + if (!string.IsNullOrWhiteSpace(mimeType) && !CanCompress(mimeType)) + { + return new ImageCompressResult(stream, ImageProcessState.Unsupported); + } + + var (memoryBitmapStream, memorySkCodecStream) = await CreateMemoryStream(stream, cancellationToken); + var originalLength = memoryBitmapStream.Length; + + try + { + using var codec = SKCodec.Create(memorySkCodecStream); + if (codec == null || !CanEncodeFormat(codec.EncodedFormat)) + { + return new ImageCompressResult(stream, ImageProcessState.Unsupported); + } + + using var bitmap = SKBitmap.Decode(memoryBitmapStream); + if (bitmap == null) + { + return new ImageCompressResult(stream, ImageProcessState.Unsupported); + } + + using var image = SKImage.FromBitmap(bitmap); + using var encoded = image.Encode(codec.EncodedFormat, Options.Quality); + + var output = new MemoryStream(); + try + { + encoded.SaveTo(output); + output.Position = 0; + + if (output.Length < originalLength) + { + return new ImageCompressResult(output, ImageProcessState.Done); + } + + output.Dispose(); + return new ImageCompressResult(stream, ImageProcessState.Canceled); + } + catch + { + output.Dispose(); + throw; + } + } + finally + { + memoryBitmapStream.Dispose(); + memorySkCodecStream.Dispose(); + } + } + + public virtual async Task> TryCompressAsync( + byte[] bytes, + string? mimeType = null, + CancellationToken cancellationToken = default) + { + if (!string.IsNullOrWhiteSpace(mimeType) && !CanCompress(mimeType)) + { + return new ImageCompressResult(bytes, ImageProcessState.Unsupported); + } + + using var ms = new MemoryStream(bytes); + var result = await TryCompressAsync(ms, mimeType, cancellationToken); + + if (result.State != ImageProcessState.Done) + { + return new ImageCompressResult(bytes, result.State); + } + + var newBytes = await result.Result.GetAllBytesAsync(cancellationToken); + result.Result.Dispose(); + return new ImageCompressResult(newBytes, result.State); + } + + protected virtual bool CanCompress(string? mimeType) + { + return mimeType switch { + MimeTypes.Image.Jpeg => true, + MimeTypes.Image.Png => true, + MimeTypes.Image.Webp => true, + _ => false + }; + } + + protected virtual bool CanEncodeFormat(SKEncodedImageFormat format) + { + return format switch + { + SKEncodedImageFormat.Jpeg => true, + SKEncodedImageFormat.Png => true, + SKEncodedImageFormat.Webp => true, + _ => false + }; + } + + protected virtual async Task<(MemoryStream, MemoryStream)> CreateMemoryStream(Stream stream, CancellationToken cancellationToken) + { + var streamPosition = stream.CanSeek ? stream.Position : 0; + + var memoryBitmapStream = new MemoryStream(); + var memorySkCodecStream = new MemoryStream(); + + try + { + await stream.CopyToAsync(memoryBitmapStream, cancellationToken); + + if (stream.CanSeek) + { + stream.Position = streamPosition; + } + + memoryBitmapStream.Position = 0; + await memoryBitmapStream.CopyToAsync(memorySkCodecStream, cancellationToken); + + memoryBitmapStream.Position = 0; + memorySkCodecStream.Position = 0; + + return (memoryBitmapStream, memorySkCodecStream); + } + catch + { + memoryBitmapStream.Dispose(); + memorySkCodecStream.Dispose(); + throw; + } + } +} diff --git a/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/SkiaSharpImageResizerContributor.cs b/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/SkiaSharpImageResizerContributor.cs index 7a5db23620..dd7da3251a 100644 --- a/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/SkiaSharpImageResizerContributor.cs +++ b/framework/src/Volo.Abp.Imaging.SkiaSharp/Volo/Abp/Imaging/SkiaSharpImageResizerContributor.cs @@ -47,34 +47,76 @@ public class SkiaSharpImageResizerContributor : IImageResizerContributor, ITrans return new ImageResizeResult(stream, ImageProcessState.Unsupported); } - var (memoryBitmapStream, memorySkCodecStream) = await CreateMemoryStream(stream); - - using var original = SKBitmap.Decode(memoryBitmapStream); - using var resized = original.Resize(new SKImageInfo((int)resizeArgs.Width, (int)resizeArgs.Height), Options.SKSamplingOptions); - using var image = SKImage.FromBitmap(resized); - using var codec = SKCodec.Create(memorySkCodecStream); - var memoryStream = new MemoryStream(); - using var skData = image.Encode(codec.EncodedFormat, Options.Quality); - skData.SaveTo(memoryStream); - return new ImageResizeResult(memoryStream, ImageProcessState.Done); + var (memoryBitmapStream, memorySkCodecStream) = await CreateMemoryStream(stream, cancellationToken); + + try + { + using var codec = SKCodec.Create(memorySkCodecStream); + if (codec == null || !CanEncodeFormat(codec.EncodedFormat)) + { + return new ImageResizeResult(stream, ImageProcessState.Unsupported); + } + + using var original = SKBitmap.Decode(memoryBitmapStream); + if (original == null) + { + return new ImageResizeResult(stream, ImageProcessState.Unsupported); + } + + using var resized = ApplyResize(original, resizeArgs); + using var image = SKImage.FromBitmap(resized); + + var memoryStream = new MemoryStream(); + try + { + using var skData = image.Encode(codec.EncodedFormat, Options.Quality); + skData.SaveTo(memoryStream); + memoryStream.Position = 0; + return new ImageResizeResult(memoryStream, ImageProcessState.Done); + } + catch + { + memoryStream.Dispose(); + throw; + } + } + finally + { + memoryBitmapStream.Dispose(); + memorySkCodecStream.Dispose(); + } } - protected virtual async Task<(MemoryStream, MemoryStream)> CreateMemoryStream(Stream stream) + protected virtual async Task<(MemoryStream, MemoryStream)> CreateMemoryStream(Stream stream, CancellationToken cancellationToken = default) { - var streamPosition = stream.Position; + var streamPosition = stream.CanSeek ? stream.Position : 0; var memoryBitmapStream = new MemoryStream(); var memorySkCodecStream = new MemoryStream(); - await stream.CopyToAsync(memoryBitmapStream); - stream.Position = streamPosition; - await stream.CopyToAsync(memorySkCodecStream); - stream.Position = streamPosition; + try + { + await stream.CopyToAsync(memoryBitmapStream, cancellationToken); + + if (stream.CanSeek) + { + stream.Position = streamPosition; + } + + memoryBitmapStream.Position = 0; + await memoryBitmapStream.CopyToAsync(memorySkCodecStream, cancellationToken); - memoryBitmapStream.Position = 0; - memorySkCodecStream.Position = 0; + memoryBitmapStream.Position = 0; + memorySkCodecStream.Position = 0; - return (memoryBitmapStream, memorySkCodecStream); + return (memoryBitmapStream, memorySkCodecStream); + } + catch + { + memoryBitmapStream.Dispose(); + memorySkCodecStream.Dispose(); + throw; + } } protected virtual bool CanResize(string? mimeType) @@ -86,4 +128,150 @@ public class SkiaSharpImageResizerContributor : IImageResizerContributor, ITrans _ => false }; } + + protected virtual bool CanEncodeFormat(SKEncodedImageFormat format) + { + return format switch + { + SKEncodedImageFormat.Jpeg => true, + SKEncodedImageFormat.Png => true, + SKEncodedImageFormat.Webp => true, + _ => false + }; + } + + protected virtual SKBitmap ApplyResize(SKBitmap source, ImageResizeArgs resizeArgs) + { + var targetWidth = (int)resizeArgs.Width; + var targetHeight = (int)resizeArgs.Height; + + if (targetWidth <= 0 && targetHeight <= 0) + { + return source.Copy(); + } + + if (targetWidth <= 0) + { + targetWidth = Math.Max(1, (int)Math.Round((double)source.Width * targetHeight / source.Height)); + } + else if (targetHeight <= 0) + { + targetHeight = Math.Max(1, (int)Math.Round((double)source.Height * targetWidth / source.Width)); + } + + var mode = resizeArgs.Mode == ImageResizeMode.Default ? ImageResizeMode.Stretch : resizeArgs.Mode; + + switch (mode) + { + case ImageResizeMode.None: + case ImageResizeMode.Stretch: + return source.Resize(new SKImageInfo(targetWidth, targetHeight), Options.SKSamplingOptions); + + case ImageResizeMode.Max: + { + var scale = Math.Min((double)targetWidth / source.Width, (double)targetHeight / source.Height); + var newW = Math.Max(1, (int)Math.Round(source.Width * scale)); + var newH = Math.Max(1, (int)Math.Round(source.Height * scale)); + return source.Resize(new SKImageInfo(newW, newH), Options.SKSamplingOptions); + } + + case ImageResizeMode.Min: + { + var scale = Math.Max((double)targetWidth / source.Width, (double)targetHeight / source.Height); + var newW = Math.Max(1, (int)Math.Round(source.Width * scale)); + var newH = Math.Max(1, (int)Math.Round(source.Height * scale)); + return source.Resize(new SKImageInfo(newW, newH), Options.SKSamplingOptions); + } + + case ImageResizeMode.Crop: + { + var scale = Math.Max((double)targetWidth / source.Width, (double)targetHeight / source.Height); + var intermediateW = Math.Max(1, (int)Math.Round(source.Width * scale)); + var intermediateH = Math.Max(1, (int)Math.Round(source.Height * scale)); + using var intermediate = source.Resize(new SKImageInfo(intermediateW, intermediateH), Options.SKSamplingOptions); + + var bitmap = new SKBitmap(targetWidth, targetHeight); + try + { + using var canvas = new SKCanvas(bitmap); + var srcX = (intermediateW - targetWidth) / 2; + var srcY = (intermediateH - targetHeight) / 2; + canvas.DrawBitmap( + intermediate, + new SKRect(srcX, srcY, srcX + targetWidth, srcY + targetHeight), + new SKRect(0, 0, targetWidth, targetHeight)); + return bitmap; + } + catch + { + bitmap.Dispose(); + throw; + } + } + + case ImageResizeMode.Pad: + { + var scale = Math.Min((double)targetWidth / source.Width, (double)targetHeight / source.Height); + var intermediateW = Math.Max(1, (int)Math.Round(source.Width * scale)); + var intermediateH = Math.Max(1, (int)Math.Round(source.Height * scale)); + using var intermediate = source.Resize(new SKImageInfo(intermediateW, intermediateH), Options.SKSamplingOptions); + + var bitmap = new SKBitmap(targetWidth, targetHeight); + try + { + using var canvas = new SKCanvas(bitmap); + canvas.Clear(SKColors.Transparent); + var dstX = (targetWidth - intermediateW) / 2; + var dstY = (targetHeight - intermediateH) / 2; + canvas.DrawBitmap(intermediate, new SKPoint(dstX, dstY)); + return bitmap; + } + catch + { + bitmap.Dispose(); + throw; + } + } + + case ImageResizeMode.BoxPad: + { + SKBitmap? scaled = null; + try + { + var working = source; + if (source.Width > targetWidth || source.Height > targetHeight) + { + var scale = Math.Min((double)targetWidth / source.Width, (double)targetHeight / source.Height); + var newW = Math.Max(1, (int)Math.Round(source.Width * scale)); + var newH = Math.Max(1, (int)Math.Round(source.Height * scale)); + scaled = source.Resize(new SKImageInfo(newW, newH), Options.SKSamplingOptions); + working = scaled; + } + + var bitmap = new SKBitmap(targetWidth, targetHeight); + try + { + using var canvas = new SKCanvas(bitmap); + canvas.Clear(SKColors.Transparent); + var dstX = (targetWidth - working.Width) / 2; + var dstY = (targetHeight - working.Height) / 2; + canvas.DrawBitmap(working, new SKPoint(dstX, dstY)); + return bitmap; + } + catch + { + bitmap.Dispose(); + throw; + } + } + finally + { + scaled?.Dispose(); + } + } + + default: + throw new NotSupportedException("Resize mode " + resizeArgs.Mode + " is not supported!"); + } + } } diff --git a/framework/src/Volo.Abp.MudBlazorUI/AbpMudBlazorMessageLocalizerHelper.cs b/framework/src/Volo.Abp.MudBlazorUI/AbpMudBlazorMessageLocalizerHelper.cs new file mode 100644 index 0000000000..007db40ca6 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/AbpMudBlazorMessageLocalizerHelper.cs @@ -0,0 +1,29 @@ +using System; +using Microsoft.Extensions.Localization; + +namespace Volo.Abp.MudBlazorUI; + +public class AbpMudBlazorMessageLocalizerHelper +{ + private readonly IStringLocalizer _localizer; + + public AbpMudBlazorMessageLocalizerHelper(IStringLocalizer localizer) + { + _localizer = localizer; + } + + public string Localize(string message, params object[] arguments) + { + return _localizer[message, arguments]; + } + + public string? LocalizeOrNull(string? message, params object[] arguments) + { + if (message.IsNullOrEmpty()) + { + return null; + } + + return _localizer[message!, arguments]; + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/AbpMudBlazorUIModule.cs b/framework/src/Volo.Abp.MudBlazorUI/AbpMudBlazorUIModule.cs new file mode 100644 index 0000000000..74c425c214 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/AbpMudBlazorUIModule.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.DependencyInjection; +using MudBlazor; +using MudBlazor.Services; +using Volo.Abp.Application; +using Volo.Abp.AspNetCore.Components.Web; +using Volo.Abp.Authorization; +using Volo.Abp.Features; +using Volo.Abp.GlobalFeatures; +using Volo.Abp.Modularity; + +namespace Volo.Abp.MudBlazorUI; + +[DependsOn( + typeof(AbpAspNetCoreComponentsWebModule), + typeof(AbpDddApplicationContractsModule), + typeof(AbpAuthorizationModule), + typeof(AbpGlobalFeaturesModule), + typeof(AbpFeaturesModule) +)] +public class AbpMudBlazorUIModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + ConfigureMudBlazor(context); + } + + private void ConfigureMudBlazor(ServiceConfigurationContext context) + { + context.Services.AddMudServices(config => + { + config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomEnd; + config.SnackbarConfiguration.PreventDuplicates = false; + config.SnackbarConfiguration.NewestOnTop = true; + config.SnackbarConfiguration.ShowCloseIcon = true; + config.SnackbarConfiguration.VisibleStateDuration = 5000; + config.SnackbarConfiguration.HideTransitionDuration = 500; + config.SnackbarConfiguration.ShowTransitionDuration = 500; + config.SnackbarConfiguration.SnackbarVariant = Variant.Filled; + }); + + context.Services.AddSingleton(typeof(AbpMudBlazorMessageLocalizerHelper<>)); + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/AbpMudCrudPageBase.cs b/framework/src/Volo.Abp.MudBlazorUI/AbpMudCrudPageBase.cs new file mode 100644 index 0000000000..7943003eeb --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/AbpMudCrudPageBase.cs @@ -0,0 +1,720 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Localization.Resources.AbpUi; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; +using MudBlazor; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; +using Volo.Abp.AspNetCore.Components; +using Volo.Abp.AspNetCore.Components.Web.Extensibility.EntityActions; +using Volo.Abp.AspNetCore.Components.Web.Extensibility.TableColumns; +using Volo.Abp.Authorization; +using Volo.Abp.Localization; +using Volo.Abp.MudBlazorUI.Components; +using Volo.Abp.ObjectExtending; +using Volo.Abp.ObjectExtending.Modularity; + +namespace Volo.Abp.MudBlazorUI; + +public abstract class AbpMudCrudPageBase< + TAppService, + TEntityDto, + TKey> + : AbpMudCrudPageBase< + TAppService, + TEntityDto, + TKey, + PagedAndSortedResultRequestDto> + where TAppService : ICrudAppService< + TEntityDto, + TKey> + where TEntityDto : class, IEntityDto, new() +{ +} + +public abstract class AbpMudCrudPageBase< + TAppService, + TEntityDto, + TKey, + TGetListInput> + : AbpMudCrudPageBase< + TAppService, + TEntityDto, + TKey, + TGetListInput, + TEntityDto> + where TAppService : ICrudAppService< + TEntityDto, + TKey, + TGetListInput> + where TEntityDto : class, IEntityDto, new() + where TGetListInput : new() +{ +} + +public abstract class AbpMudCrudPageBase< + TAppService, + TEntityDto, + TKey, + TGetListInput, + TCreateInput> + : AbpMudCrudPageBase< + TAppService, + TEntityDto, + TKey, + TGetListInput, + TCreateInput, + TCreateInput> + where TAppService : ICrudAppService< + TEntityDto, + TKey, + TGetListInput, + TCreateInput> + where TEntityDto : IEntityDto + where TCreateInput : class, new() + where TGetListInput : new() +{ +} + +public abstract class AbpMudCrudPageBase< + TAppService, + TEntityDto, + TKey, + TGetListInput, + TCreateInput, + TUpdateInput> + : AbpMudCrudPageBase< + TAppService, + TEntityDto, + TEntityDto, + TKey, + TGetListInput, + TCreateInput, + TUpdateInput> + where TAppService : ICrudAppService< + TEntityDto, + TKey, + TGetListInput, + TCreateInput, + TUpdateInput> + where TEntityDto : IEntityDto + where TCreateInput : class, new() + where TUpdateInput : class, new() + where TGetListInput : new() +{ +} + +public abstract class AbpMudCrudPageBase< + TAppService, + TGetOutputDto, + TGetListOutputDto, + TKey, + TGetListInput, + TCreateInput, + TUpdateInput> + : AbpMudCrudPageBase< + TAppService, + TGetOutputDto, + TGetListOutputDto, + TKey, + TGetListInput, + TCreateInput, + TUpdateInput, + TGetListOutputDto, + TCreateInput, + TUpdateInput> + where TAppService : ICrudAppService< + TGetOutputDto, + TGetListOutputDto, + TKey, + TGetListInput, + TCreateInput, + TUpdateInput> + where TGetOutputDto : IEntityDto + where TGetListOutputDto : IEntityDto + where TCreateInput : class, new() + where TUpdateInput : class, new() + where TGetListInput : new() +{ +} + +public abstract class AbpMudCrudPageBase< + TAppService, + TGetOutputDto, + TGetListOutputDto, + TKey, + TGetListInput, + TCreateInput, + TUpdateInput, + TListViewModel, + TCreateViewModel, + TUpdateViewModel> + : AbpComponentBase + where TAppService : ICrudAppService< + TGetOutputDto, + TGetListOutputDto, + TKey, + TGetListInput, + TCreateInput, + TUpdateInput> + where TGetOutputDto : IEntityDto + where TGetListOutputDto : IEntityDto + where TCreateInput : class + where TUpdateInput : class + where TGetListInput : new() + where TListViewModel : IEntityDto + where TCreateViewModel : class, new() + where TUpdateViewModel : class, new() +{ + [Inject] protected TAppService AppService { get; set; } = default!; + [Inject] protected IStringLocalizer UiLocalizer { get; set; } = default!; + [Inject] public IAbpEnumLocalizer AbpEnumLocalizer { get; set; } = default!; + [Inject] protected IDialogService DialogService { get; set; } = default!; + [Inject] protected ISnackbar Snackbar { get; set; } = default!; + [Inject] protected ExtensionPropertyPolicyChecker ExtensionPropertyPolicyChecker { get; set; } = default!; + + protected virtual int PageSize { get; set; } = LimitedResultRequestDto.DefaultMaxResultCount; + + protected int CurrentPage = 1; + protected string CurrentSorting = default!; + protected TGetListInput GetListInput = new TGetListInput(); + protected TCreateViewModel NewEntity; + protected TKey EditingEntityId = default!; + protected TUpdateViewModel EditingEntity; + protected MudForm? CreateFormRef; + protected MudForm? EditFormRef; + protected List BreadcrumbItems = new List(2); + protected MudDataGridEntityActionsColumn EntityActionsColumn = default!; + protected EntityActionDictionary EntityActions { get; set; } + protected TableColumnDictionary TableColumns { get; set; } + + protected string? CreatePolicyName { get; set; } + protected string? UpdatePolicyName { get; set; } + protected string? DeletePolicyName { get; set; } + + public bool HasCreatePermission { get; set; } + public bool HasUpdatePermission { get; set; } + public bool HasDeletePermission { get; set; } + + protected MudDialog? _createDialog; + protected MudDialog? _editDialog; + + protected virtual DialogOptions CreateDialogOptions => new DialogOptions + { + MaxWidth = MaxWidth.Medium, + FullWidth = true, + CloseOnEscapeKey = true, + BackdropClick = false + }; + + protected virtual DialogOptions EditDialogOptions => new DialogOptions + { + MaxWidth = MaxWidth.Medium, + FullWidth = true, + CloseOnEscapeKey = true, + BackdropClick = false + }; + + protected AbpMudExtensibleDataGrid _dataGrid = default!; + + protected AbpMudCrudPageBase() + { + NewEntity = new TCreateViewModel(); + EditingEntity = new TUpdateViewModel(); + TableColumns = new TableColumnDictionary(); + EntityActions = new EntityActionDictionary(); + } + + protected override async Task OnInitializedAsync() + { + await TrySetPermissionsAsync(); + await TrySetEntityActionsAsync(); + await TrySetTableColumnsAsync(); + await InvokeAsync(StateHasChanged); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + await SetToolbarItemsAsync(); + await SetBreadcrumbItemsAsync(); + } + await base.OnAfterRenderAsync(firstRender); + } + + private async Task TrySetPermissionsAsync() + { + if (IsDisposed) + { + return; + } + + await SetPermissionsAsync(); + } + + protected virtual async Task SetPermissionsAsync() + { + if (CreatePolicyName != null) + { + HasCreatePermission = await AuthorizationService.IsGrantedAsync(CreatePolicyName); + } + + if (UpdatePolicyName != null) + { + HasUpdatePermission = await AuthorizationService.IsGrantedAsync(UpdatePolicyName); + } + + if (DeletePolicyName != null) + { + HasDeletePermission = await AuthorizationService.IsGrantedAsync(DeletePolicyName); + } + } + + private IReadOnlyList MapToListViewModel(IReadOnlyList dtos) + { + if (typeof(TGetListOutputDto) == typeof(TListViewModel)) + { + return dtos.As>(); + } + + return ObjectMapper.Map, List>(dtos); + } + + protected virtual string MapSortFieldToPropertyName(string sortBy, List columns) + { + if (string.IsNullOrEmpty(sortBy)) + { + return sortBy; + } + + return sortBy; + } + + protected virtual Task UpdateGetListInputAsync() + { + if (GetListInput is ISortedResultRequest sortedResultRequestInput) + { + sortedResultRequestInput.Sorting = CurrentSorting; + } + + if (GetListInput is IPagedResultRequest pagedResultRequestInput) + { + pagedResultRequestInput.SkipCount = (CurrentPage - 1) * PageSize; + } + + if (GetListInput is ILimitedResultRequest limitedResultRequestInput) + { + limitedResultRequestInput.MaxResultCount = PageSize; + } + + return Task.CompletedTask; + } + + protected virtual async Task SearchEntitiesAsync() + { + var currentPage = CurrentPage; + CurrentPage = 1; + if (currentPage == 1) + { + await _dataGrid.ReloadServerDataAsync(); + } + else + { + await InvokeAsync(StateHasChanged); + } + } + + protected virtual async Task> OnDataGridReadAsync(GridState state) + { + CurrentSorting = state.SortDefinitions + .Select(s => _dataGrid.ResolveSortPropertyName(s) + (s.Descending ? " DESC" : "")) + .JoinAsString(","); + CurrentPage = state.Page + 1; + if (state.PageSize > 0) + { + PageSize = state.PageSize; + } + + try + { + await UpdateGetListInputAsync(); + var result = await AppService.GetListAsync(GetListInput); + var items = MapToListViewModel(result.Items); + + await InvokeAsync(StateHasChanged); + + return new GridData + { + Items = items, + TotalItems = (int)result.TotalCount + }; + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + return new GridData + { + Items = Array.Empty(), + TotalItems = 0 + }; + } + } + + protected virtual async Task OpenCreateDialogAsync() + { + try + { + await CheckCreatePolicyAsync(); + + NewEntity = new TCreateViewModel(); + + if (_createDialog != null) + { + await InvokeAsync(() => _createDialog.ShowAsync()); + } + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + protected virtual Task CloseCreateDialogAsync() + { + NewEntity = new TCreateViewModel(); + if (_createDialog != null) + { + return InvokeAsync(() => _createDialog.CloseAsync()); + } + return Task.CompletedTask; + } + + protected virtual async Task OpenEditDialogAsync(TListViewModel entity) + { + try + { + await CheckUpdatePolicyAsync(); + + var entityDto = await AppService.GetAsync(entity.Id); + + EditingEntityId = entity.Id; + EditingEntity = MapToEditingEntity(entityDto); + + if (_editDialog != null) + { + await InvokeAsync(() => _editDialog.ShowAsync()); + } + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + protected virtual TUpdateViewModel MapToEditingEntity(TGetOutputDto entityDto) + { + return ObjectMapper.Map(entityDto); + } + + protected virtual TCreateInput MapToCreateInput(TCreateViewModel createViewModel) + { + if (typeof(TCreateInput) == typeof(TCreateViewModel)) + { + return createViewModel.As(); + } + + return ObjectMapper.Map(createViewModel); + } + + protected virtual TUpdateInput MapToUpdateInput(TUpdateViewModel updateViewModel) + { + if (typeof(TUpdateInput) == typeof(TUpdateViewModel)) + { + return updateViewModel.As(); + } + + return ObjectMapper.Map(updateViewModel); + } + + protected virtual Task CloseEditDialogAsync() + { + if (_editDialog != null) + { + return InvokeAsync(() => _editDialog.CloseAsync()); + } + return Task.CompletedTask; + } + + protected virtual async Task CreateEntityAsync() + { + try + { + var isValid = true; + if (CreateFormRef != null) + { + await CreateFormRef.Validate(); + isValid = CreateFormRef.IsValid; + } + + if (isValid) + { + await OnCreatingEntityAsync(); + + await CheckCreatePolicyAsync(); + var createInput = MapToCreateInput(NewEntity); + await AppService.CreateAsync(createInput); + + await OnCreatedEntityAsync(); + } + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + protected virtual Task OnCreatingEntityAsync() + { + return Task.CompletedTask; + } + + protected virtual async Task OnCreatedEntityAsync() + { + await _dataGrid.ReloadServerDataAsync(); + await CloseCreateDialogAsync(); + Snackbar.Add(GetCreateMessage(), Severity.Success); + } + + protected virtual string GetCreateMessage() + { + return UiLocalizer["CreatedSuccessfully"]; + } + + protected virtual async Task UpdateEntityAsync() + { + try + { + var isValid = true; + if (EditFormRef != null) + { + await EditFormRef.Validate(); + isValid = EditFormRef.IsValid; + } + + if (isValid) + { + await OnUpdatingEntityAsync(); + + await CheckUpdatePolicyAsync(); + var updateInput = MapToUpdateInput(EditingEntity); + await AppService.UpdateAsync(EditingEntityId, updateInput); + + await OnUpdatedEntityAsync(); + } + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + protected virtual Task OnUpdatingEntityAsync() + { + return Task.CompletedTask; + } + + protected virtual async Task OnUpdatedEntityAsync() + { + await _dataGrid.ReloadServerDataAsync(); + await CloseEditDialogAsync(); + Snackbar.Add(GetUpdateMessage(), Severity.Success); + } + + protected virtual string GetUpdateMessage() + { + return UiLocalizer["SavedSuccessfully"]; + } + + protected virtual async Task DeleteEntityAsync(TListViewModel entity) + { + try + { + await CheckDeletePolicyAsync(); + + var confirmed = await ConfirmDeleteAsync(entity); + if (!confirmed) + { + return; + } + + await OnDeletingEntityAsync(); + await AppService.DeleteAsync(entity.Id); + await OnDeletedEntityAsync(); + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + protected virtual Task OnDeletingEntityAsync() + { + return Task.CompletedTask; + } + + protected virtual async Task OnDeletedEntityAsync() + { + await _dataGrid.ReloadServerDataAsync(); + Snackbar.Add(GetDeleteMessage(), Severity.Success); + } + + protected virtual string GetDeleteMessage() + { + return UiLocalizer["DeletedSuccessfully"]; + } + + protected virtual string GetDeleteConfirmationMessage(TListViewModel entity) + { + return UiLocalizer["ItemWillBeDeletedMessage"]; + } + + protected virtual async Task ConfirmDeleteAsync(TListViewModel entity) + { + var result = await DialogService.ShowMessageBox( + UiLocalizer["AreYouSure"], + GetDeleteConfirmationMessage(entity), + yesText: UiLocalizer["Yes"], + cancelText: UiLocalizer["Cancel"]); + + return result == true; + } + + protected virtual async Task CheckCreatePolicyAsync() + { + await CheckPolicyAsync(CreatePolicyName); + } + + protected virtual async Task CheckUpdatePolicyAsync() + { + await CheckPolicyAsync(UpdatePolicyName); + } + + protected virtual async Task CheckDeletePolicyAsync() + { + await CheckPolicyAsync(DeletePolicyName); + } + + /// + /// Calls IAuthorizationService.CheckAsync for the given . + /// Throws if given policy was not granted for the current user. + /// + /// Does nothing if is null or empty. + /// + /// A policy name to check + protected virtual async Task CheckPolicyAsync(string? policyName) + { + if (string.IsNullOrEmpty(policyName)) + { + return; + } + + await AuthorizationService.CheckAsync(policyName); + } + + protected virtual ValueTask SetBreadcrumbItemsAsync() + { + return ValueTask.CompletedTask; + } + + private async ValueTask TrySetEntityActionsAsync() + { + if (IsDisposed) + { + return; + } + + await SetEntityActionsAsync(); + } + + protected virtual ValueTask SetEntityActionsAsync() + { + return ValueTask.CompletedTask; + } + + private async ValueTask TrySetTableColumnsAsync() + { + if (IsDisposed) + { + return; + } + + await SetTableColumnsAsync(); + } + + protected virtual ValueTask SetTableColumnsAsync() + { + return ValueTask.CompletedTask; + } + + protected virtual ValueTask SetToolbarItemsAsync() + { + return ValueTask.CompletedTask; + } + + protected virtual async Task> GetExtensionTableColumnsAsync(string moduleName, string entityType) + { + var tableColumns = new List(); + var properties = ModuleExtensionConfigurationHelper.GetPropertyConfigurations(moduleName, entityType).ToList(); + foreach (var propertyInfo in properties) + { + if (!await ExtensionPropertyPolicyChecker.CheckPolicyAsync(propertyInfo.Policy)) + { + continue; + } + + if (propertyInfo.IsAvailableToClients && propertyInfo.UI.OnTable.IsVisible) + { + if (propertyInfo.Name.EndsWith("_Text")) + { + var lookupPropertyName = propertyInfo.Name.RemovePostFix("_Text"); + var lookupPropertyDefinition = properties.SingleOrDefault(t => t.Name == lookupPropertyName)!; + tableColumns.Add(new TableColumn + { + Title = lookupPropertyDefinition.GetLocalizedDisplayName(StringLocalizerFactory), + Data = $"ExtraProperties[{propertyInfo.Name}]", + PropertyName = propertyInfo.Name + }); + } + else + { + var column = new TableColumn + { + Title = propertyInfo.GetLocalizedDisplayName(StringLocalizerFactory), + Data = $"ExtraProperties[{propertyInfo.Name}]", + PropertyName = propertyInfo.Name + }; + + if (propertyInfo.IsDate() || propertyInfo.IsDateTime()) + { + column.DisplayFormat = propertyInfo.GetDateEditInputFormatOrNull(); + } + + if (propertyInfo.Type.IsEnum) + { + column.ValueConverter = (val) => + AbpEnumLocalizer.GetString(propertyInfo.Type, val.As().ExtraProperties[propertyInfo.Name]!, new IStringLocalizer?[] { StringLocalizerFactory.CreateDefaultOrNull() }); + } + + tableColumns.Add(column); + } + } + } + + return tableColumns; + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/AbpMudExtensibleDataGrid.razor b/framework/src/Volo.Abp.MudBlazorUI/Components/AbpMudExtensibleDataGrid.razor new file mode 100644 index 0000000000..2bf9b47935 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/AbpMudExtensibleDataGrid.razor @@ -0,0 +1,133 @@ +@typeparam TItem +@using MudBlazor +@using Volo.Abp.Data + + + + + + + + @UiLocalizer["NoDataAvailableInDatatable"] + + + + @if (Columns != null) + { + @foreach (var column in Columns) + { + if (column.Actions.Any()) + { + + + @{ + var effectiveActions = column.Actions + .Where(a => a.Visible?.Invoke(context.Item!) != false) + .ToList(); + var entityActionType = effectiveActions.Count == 1 ? MudActionType.Button : MudActionType.Dropdown; + } + + @foreach (var action in column.Actions) + { + if (action.ConfirmationMessage != null) + { + + + } + else + { + + + } + } + + + + } + else + { + @if (column.Component != null) + { + + } + else + { + var hasExtensionPropertyData = !string.IsNullOrWhiteSpace(column.Data) && ExtensionPropertiesRegex.IsMatch(column.Data!); + if (!hasExtensionPropertyData) + { + @if (column.ValueConverter == null) + { + + + @if (ShowPager) + { + + } + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/AbpMudExtensibleDataGrid.razor.cs b/framework/src/Volo.Abp.MudBlazorUI/Components/AbpMudExtensibleDataGrid.razor.cs new file mode 100644 index 0000000000..0efd289d64 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/AbpMudExtensibleDataGrid.razor.cs @@ -0,0 +1,307 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Localization.Resources.AbpUi; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.Extensions.Localization; +using MudBlazor; +using Volo.Abp.AspNetCore.Components.Web.Extensibility.TableColumns; +using Volo.Abp.Data; +using Volo.Abp.Timing; + +namespace Volo.Abp.MudBlazorUI.Components; + +public partial class AbpMudExtensibleDataGrid : ComponentBase +{ + protected const string DataFieldAttributeName = "Data"; + + private MudDataGrid? _dataGrid; + + protected Regex ExtensionPropertiesRegex = new Regex(@"ExtraProperties\[(.*?)\]"); + + /// + /// Maps SortBy func references to their corresponding property names. + /// Used to resolve property names from MudBlazor SortDefinition.SortFunc. + /// + private readonly Dictionary, string> _sortFuncToPropertyMap = new(); + + [Parameter] public Func, Task>>? ServerData { get; set; } + + [Parameter] public bool Loading { get; set; } + + [Parameter] public bool ShowPager { get; set; } = true; + + [Parameter] public int PageSize { get; set; } = 10; + + [Parameter] public IEnumerable Columns { get; set; } = default!; + + [Parameter] public int CurrentPage { get; set; } = 1; + + [Parameter] public string? Class { get; set; } + + [Parameter] public string ActionColumnWidth { get; set; } = "150px"; + + protected int ZeroBasedCurrentPage => CurrentPage > 0 ? CurrentPage - 1 : 0; + + [Inject] + public IStringLocalizerFactory StringLocalizerFactory { get; set; } = default!; + + [Inject] + public IStringLocalizer UiLocalizer { get; set; } = default!; + + [Inject] + public IClock Clock { get; set; } = default!; + + public virtual async Task ReloadServerDataAsync() + { + if (_dataGrid != null && ServerData != null) + { + await InvokeAsync(() => _dataGrid.ReloadServerData()); + } + } + + /// + /// Resolves the property name for a given SortDefinition by matching its SortFunc reference. + /// When MudBlazor uses Func for SortBy on TemplateColumn, it returns an internal GUID identifier + /// instead of the property name. This method looks up the original property name from the + /// registered sort function mapping. + /// + /// The property name if found in the mapping, otherwise falls back to SortDefinition.SortBy. + public virtual string ResolveSortPropertyName(SortDefinition sortDefinition) + { + if (sortDefinition.SortFunc != null && _sortFuncToPropertyMap.TryGetValue(sortDefinition.SortFunc, out var propertyName)) + { + return propertyName; + } + + return sortDefinition.SortBy; + } + + protected virtual RenderFragment RenderCustomTableColumnComponent(Type type, object data) + { + return (builder) => + { + builder.OpenComponent(0, type); + builder.AddAttribute(1, DataFieldAttributeName, data); + builder.CloseComponent(); + }; + } + + protected virtual string GetConvertedFieldValue(TItem? item, TableColumn columnDefinition) + { + if (item == null) + { + return string.Empty; + } + + var convertedValue = columnDefinition.ValueConverter!.Invoke(item!); + if (!columnDefinition.DisplayFormat.IsNullOrEmpty()) + { + return string.Format(columnDefinition.DisplayFormatProvider, columnDefinition.DisplayFormat!, + convertedValue); + } + + return convertedValue; + } + + protected virtual string GetConvertedFieldValue(object? value, TableColumn columnDefinition) + { + if (value is DateTime dateTime) + { + var converted = Clock.ConvertToUserTime(dateTime); + if (!columnDefinition.DisplayFormat.IsNullOrEmpty()) + { + return string.Format(columnDefinition.DisplayFormatProvider, columnDefinition.DisplayFormat!, converted); + } + + return converted.ToString(columnDefinition.DisplayFormatProvider as CultureInfo ?? CultureInfo.CurrentCulture); + } + + if (value is DateTimeOffset dateTimeOffset) + { + var converted = Clock.ConvertToUserTime(dateTimeOffset); + if (!columnDefinition.DisplayFormat.IsNullOrEmpty()) + { + return string.Format(columnDefinition.DisplayFormatProvider, columnDefinition.DisplayFormat!, converted); + } + + return converted.ToString(columnDefinition.DisplayFormatProvider as CultureInfo ?? CultureInfo.CurrentCulture); + } + + if (value == null) + { + return string.Empty; + } + + if (!columnDefinition.DisplayFormat.IsNullOrEmpty()) + { + return string.Format(columnDefinition.DisplayFormatProvider, columnDefinition.DisplayFormat!, value); + } + + return value.ToString() ?? string.Empty; + } + + protected virtual object GetPropertyValue(TItem item, string propertyPath) + { + ArgumentNullException.ThrowIfNull(item); + + var properties = propertyPath.Split('.'); + object value = item; + + foreach (var prop in properties) + { + var propertyInfo = value.GetType().GetProperty(prop, BindingFlags.Public | BindingFlags.Instance) + ?? throw new ArgumentException($"Property '{prop}' not found on type '{value.GetType().Name}'", nameof(propertyPath)); + + var propertyValue = propertyInfo.GetValue(value); + + // Allow null property values and treat them as empty for display/sorting purposes + if (propertyValue == null) + { + return string.Empty; + } + + value = propertyValue; + } + + if (value is DateTime dateTime) + { + return Clock.ConvertToUserTime(dateTime); + } + + if (value is DateTimeOffset dateTimeOffset) + { + return Clock.ConvertToUserTime(dateTimeOffset); + } + + return value; + } + + protected virtual string GetColumnStyle(TableColumn column) + { + if (!string.IsNullOrEmpty(column.Width)) + { + return $"width: {column.Width}"; + } + + return string.Empty; + } + + protected virtual Func? GetSortByFunc(TableColumn column) + { + if (!column.Sortable) + { + return null; + } + + // Determine the property path for sorting + string? propertyPath = null; + if (!string.IsNullOrEmpty(column.PropertyName)) + { + propertyPath = column.PropertyName; + } + else if (!string.IsNullOrEmpty(column.Data) && !ExtensionPropertiesRegex.IsMatch(column.Data)) + { + propertyPath = column.Data; + } + + if (string.IsNullOrEmpty(propertyPath)) + { + return null; + } + + var properties = propertyPath.Split('.'); + + // Build a lambda expression that directly accesses the property + var parameter = System.Linq.Expressions.Expression.Parameter(typeof(TItem), "x"); + System.Linq.Expressions.Expression expression = parameter; + + Func? sortFunc = null; + foreach (var prop in properties) + { + var propertyInfo = expression.Type.GetProperty(prop, BindingFlags.Public | BindingFlags.Instance); + if (propertyInfo == null) + { + // Fallback to GetPropertyValue if property not found + sortFunc = item => GetPropertyValue(item, propertyPath); + break; + } + expression = System.Linq.Expressions.Expression.Property(expression, propertyInfo); + } + + if (sortFunc == null) + { + var convertExpression = System.Linq.Expressions.Expression.Convert(expression, typeof(object)); + var lambda = System.Linq.Expressions.Expression.Lambda>(convertExpression, parameter); + sortFunc = lambda.Compile(); + } + + // Register the mapping so we can resolve property name from SortDefinition.SortFunc later + _sortFuncToPropertyMap[sortFunc] = propertyPath; + + return sortFunc; + } + + protected virtual Func? GetExtensionPropertySortFunc(TableColumn column) + { + if (!column.Sortable || string.IsNullOrWhiteSpace(column.Data)) + { + return null; + } + + var match = ExtensionPropertiesRegex.Match(column.Data); + if (!match.Success) + { + return null; + } + + var propertyName = match.Groups[1].Value; + Func sortFunc = item => + { + var entity = item as IHasExtraProperties; + return entity?.GetProperty(propertyName) ?? string.Empty; + }; + + // Register the mapping so we can resolve property name from SortDefinition.SortFunc later + _sortFuncToPropertyMap[sortFunc] = propertyName; + + return sortFunc; + } + + protected virtual Color GetColor(object? color) + { + if (color == null) + { + return Color.Primary; + } + + // Handle if it's already a MudBlazor Color + if (color is Color mudColor) + { + return mudColor; + } + + // Handle string color names + if (color is string colorString) + { + return colorString.ToLowerInvariant() switch + { + "primary" => Color.Primary, + "secondary" => Color.Secondary, + "success" => Color.Success, + "warning" => Color.Warning, + "error" or "danger" => Color.Error, + "info" => Color.Info, + "dark" => Color.Dark, + _ => Color.Primary + }; + } + + return Color.Primary; + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/AlertWrapper.cs b/framework/src/Volo.Abp.MudBlazorUI/Components/AlertWrapper.cs new file mode 100644 index 0000000000..9466ced609 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/AlertWrapper.cs @@ -0,0 +1,9 @@ +using Volo.Abp.AspNetCore.Components.Alerts; + +namespace Volo.Abp.MudBlazorUI.Components; + +internal class AlertWrapper +{ + public AlertMessage AlertMessage { get; set; } = default!; + public bool IsVisible { get; set; } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/MudDataGridEntityActionsColumn.razor b/framework/src/Volo.Abp.MudBlazorUI/Components/MudDataGridEntityActionsColumn.razor new file mode 100644 index 0000000000..4b0eb0efd9 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/MudDataGridEntityActionsColumn.razor @@ -0,0 +1,6 @@ +@typeparam TItem +@using MudBlazor + + + @ChildContent + diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/MudDataGridEntityActionsColumn.razor.cs b/framework/src/Volo.Abp.MudBlazorUI/Components/MudDataGridEntityActionsColumn.razor.cs new file mode 100644 index 0000000000..cedf0a74bf --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/MudDataGridEntityActionsColumn.razor.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using Localization.Resources.AbpUi; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; + +namespace Volo.Abp.MudBlazorUI.Components; + +public partial class MudDataGridEntityActionsColumn : ComponentBase +{ + [Parameter] + public RenderFragment? ChildContent { get; set; } + + [Parameter] + public string? Title { get; set; } + + [Parameter] + public string Width { get; set; } = "150px"; + + [Parameter] + public bool Displayable { get; set; } = true; + + [Inject] + public IStringLocalizer UiLocalizer { get; set; } = default!; + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + await SetDefaultValuesAsync(); + } + + protected virtual ValueTask SetDefaultValuesAsync() + { + Title ??= UiLocalizer["Actions"]; + return ValueTask.CompletedTask; + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/MudEntityAction.razor b/framework/src/Volo.Abp.MudBlazorUI/Components/MudEntityAction.razor new file mode 100644 index 0000000000..c1ae598b89 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/MudEntityAction.razor @@ -0,0 +1,29 @@ +@typeparam TItem +@using MudBlazor + +@if (ParentActions != null && Visible && HasPermission) +{ + if (ParentActions.Type == MudActionType.Dropdown) + { + if (Primary == false) + { + + @if (!string.IsNullOrEmpty(Icon)) + { + + } + @Text + + } + } + else + { + + @if (!string.IsNullOrEmpty(Icon)) + { + + } + @Text + + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/MudEntityAction.razor.cs b/framework/src/Volo.Abp.MudBlazorUI/Components/MudEntityAction.razor.cs new file mode 100644 index 0000000000..07ba3a3bed --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/MudEntityAction.razor.cs @@ -0,0 +1,86 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Components; +using MudBlazor; +using Volo.Abp.AspNetCore.Components.Messages; + +namespace Volo.Abp.MudBlazorUI.Components; + +public partial class MudEntityAction : ComponentBase +{ + [Parameter] + public bool Visible { get; set; } = true; + + [Parameter] + public bool Disabled { get; set; } = false; + + internal bool HasPermission { get; set; } = true; + + [Parameter] + public string Text { get; set; } = default!; + + [Parameter] + public bool Primary { get; set; } + + [Parameter] + public EventCallback Clicked { get; set; } + + [Parameter] + [Obsolete("Use Visible to hide actions based on permissions. Check the permission yourself. It is more performant. This option might be removed in future versions.")] + public string? RequiredPolicy { get; set; } + + [Parameter] + public Color Color { get; set; } = Color.Primary; + + [Parameter] + public Func? ConfirmationMessage { get; set; } + + [Parameter] + public string? Icon { get; set; } + + [CascadingParameter(Name = "ParentActions")] + public IMudEntityActions? ParentActions { get; set; } = default!; + + [Inject] + protected IAuthorizationService AuthorizationService { get; set; } = default!; + + [Inject] + protected IUiMessageService UiMessageService { get; set; } = default!; + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + await SetDefaultValuesAsync(); + +#pragma warning disable CS0618 + if (!RequiredPolicy.IsNullOrEmpty()) + { + HasPermission = await AuthorizationService.IsGrantedAsync(RequiredPolicy!); + } +#pragma warning restore CS0618 + + ParentActions?.AddAction(this); + } + + protected internal virtual async Task ActionClickedAsync() + { + if (ConfirmationMessage != null) + { + if (await UiMessageService.Confirm(ConfirmationMessage())) + { + await InvokeAsync(() => Clicked.InvokeAsync()); + } + } + else + { + await Clicked.InvokeAsync(); + } + } + + protected virtual ValueTask SetDefaultValuesAsync() + { + Color = Color.Primary; + return ValueTask.CompletedTask; + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/MudEntityActions.razor b/framework/src/Volo.Abp.MudBlazorUI/Components/MudEntityActions.razor new file mode 100644 index 0000000000..e60654fdf1 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/MudEntityActions.razor @@ -0,0 +1,24 @@ +@typeparam TItem +@using MudBlazor +@using Volo.Abp.MudBlazorUI.Components + +@if (Type == MudActionType.Dropdown || DisabledOrNoActions()) +{ + + + @ChildContent + + +} +else +{ + + + @ChildContent + + +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/MudEntityActions.razor.cs b/framework/src/Volo.Abp.MudBlazorUI/Components/MudEntityActions.razor.cs new file mode 100644 index 0000000000..35b09f83f3 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/MudEntityActions.razor.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Localization.Resources.AbpUi; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; +using MudBlazor; + +namespace Volo.Abp.MudBlazorUI.Components; + +/// +/// Non-generic interface for MudEntityActions to allow cascading parameter matching +/// regardless of the generic type argument. +/// +public interface IMudEntityActions +{ + MudActionType Type { get; } + void AddAction(object action); +} + +public partial class MudEntityActions : ComponentBase, IMudEntityActions +{ + protected readonly List> Actions = new List>(); + protected bool HasPrimaryAction => Actions.Any(t => t.Primary); + protected MudEntityAction? PrimaryAction => Actions.FirstOrDefault(t => t.Primary); + + [Parameter] + public Color ToggleColor { get; set; } = Color.Primary; + + [Parameter] + public string? ToggleText { get; set; } + + [Parameter] + public RenderFragment ChildContent { get; set; } = default!; + + [Parameter] + public MudDataGridEntityActionsColumn EntityActionsColumn { get; set; } = default!; + + [Parameter] + public MudActionType Type { get; set; } = MudActionType.Dropdown; + + [Parameter] + public bool Disabled { get; set; } = false; + + [CascadingParameter] + public MudDataGridEntityActionsColumn? ParentEntityActionsColumn { get; set; } + + [Inject] + public IStringLocalizer UiLocalizer { get; set; } = default!; + + internal void AddAction(MudEntityAction action) + { + Actions.Add(action); + } + + void IMudEntityActions.AddAction(object action) + { + if (action is MudEntityAction typedAction) + { + AddAction(typedAction); + } + } + + protected virtual bool DisabledOrNoActions() + { + return Disabled || (Actions.Any() && Actions.All(t => !t.Visible || !t.HasPermission)); + } + + protected override void OnInitialized() + { + base.OnInitialized(); + ToggleText = UiLocalizer["Actions"]; + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + if (ParentEntityActionsColumn != null) + { + ParentEntityActionsColumn.Displayable = Actions.Any(t => t.Visible && t.HasPermission); + } + + await InvokeAsync(StateHasChanged); + } + + await base.OnAfterRenderAsync(firstRender); + } +} + +public enum MudActionType +{ + Dropdown, + Button +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/ExtensionPropertyModalType.cs b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/ExtensionPropertyModalType.cs new file mode 100644 index 0000000000..0a9c881010 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/ExtensionPropertyModalType.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.MudBlazorUI.Components.ObjectExtending; + +public enum ExtensionPropertyModalType : byte +{ + CreateModal = 0, + EditModal = 1 +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudCheckExtensionProperty.razor b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudCheckExtensionProperty.razor new file mode 100644 index 0000000000..1fe1d6352a --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudCheckExtensionProperty.razor @@ -0,0 +1,14 @@ +@typeparam TEntity +@typeparam TResourceType +@using Volo.Abp.Localization +@inherits MudExtensionPropertyComponentBase + +@if (PropertyInfo != null && Entity != null) +{ + +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudCheckExtensionProperty.razor.cs b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudCheckExtensionProperty.razor.cs new file mode 100644 index 0000000000..7e098005b7 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudCheckExtensionProperty.razor.cs @@ -0,0 +1,13 @@ +using Volo.Abp.Data; + +namespace Volo.Abp.MudBlazorUI.Components.ObjectExtending; + +public partial class MudCheckExtensionProperty + where TEntity : IHasExtraProperties +{ + protected bool Value + { + get => Entity.GetProperty(PropertyInfo.Name); + set => Entity.SetProperty(PropertyInfo.Name, value, validate: false); + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudDateTimeExtensionProperty.razor b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudDateTimeExtensionProperty.razor new file mode 100644 index 0000000000..142ebcc181 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudDateTimeExtensionProperty.razor @@ -0,0 +1,32 @@ +@typeparam TEntity +@typeparam TResourceType +@using Volo.Abp.Localization +@using Volo.Abp.ObjectExtending +@inherits MudExtensionPropertyComponentBase + +@if (PropertyInfo != null && Entity != null) +{ + @if (PropertyInfo.IsDate()) + { + + } + else + { + + + + + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudDateTimeExtensionProperty.razor.cs b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudDateTimeExtensionProperty.razor.cs new file mode 100644 index 0000000000..00de58baa5 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudDateTimeExtensionProperty.razor.cs @@ -0,0 +1,60 @@ +using System; +using Volo.Abp.Data; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.MudBlazorUI.Components.ObjectExtending; + +public partial class MudDateTimeExtensionProperty + where TEntity : IHasExtraProperties +{ + protected DateTime? Value + { + get => PropertyInfo.GetInputValueOrDefault(Entity.GetProperty(PropertyInfo.Name)); + set => Entity.SetProperty(PropertyInfo.Name, value, false); + } + + protected DateTime? DateValue + { + get => Value?.Date; + set + { + if (value.HasValue) + { + var time = Value?.TimeOfDay ?? TimeSpan.Zero; + Value = value.Value.Date + time; + } + else + { + Value = null; + } + } + } + + protected TimeSpan? TimeValue + { + get => Value?.TimeOfDay; + set + { + if (Value.HasValue && value.HasValue) + { + Value = Value.Value.Date + value.Value; + } + } + } + + protected string GetDateFormat() + { + var dataFormatString = PropertyInfo.GetDataFormatStringOrNull(); + if (!string.IsNullOrEmpty(dataFormatString)) + { + return dataFormatString.Replace("{0:", "").Replace("}", ""); + } + + return PropertyInfo.IsDate() ? "yyyy-MM-dd" : "yyyy-MM-dd HH:mm"; + } + + protected string GetTimeLabel() + { + return "Time"; + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudDateTimeOffsetExtensionProperty.razor b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudDateTimeOffsetExtensionProperty.razor new file mode 100644 index 0000000000..ef1025f914 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudDateTimeOffsetExtensionProperty.razor @@ -0,0 +1,32 @@ +@typeparam TEntity +@typeparam TResourceType +@using Volo.Abp.Localization +@using Volo.Abp.ObjectExtending +@inherits MudExtensionPropertyComponentBase + +@if (PropertyInfo != null && Entity != null) +{ + @if (PropertyInfo.IsDate()) + { + + } + else + { + + + + + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudDateTimeOffsetExtensionProperty.razor.cs b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudDateTimeOffsetExtensionProperty.razor.cs new file mode 100644 index 0000000000..ee2a965004 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudDateTimeOffsetExtensionProperty.razor.cs @@ -0,0 +1,93 @@ +using System; +using Volo.Abp.Data; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.MudBlazorUI.Components.ObjectExtending; + +public partial class MudDateTimeOffsetExtensionProperty + where TEntity : IHasExtraProperties +{ + protected DateTimeOffset? Value + { + get + { + var raw = Entity.GetProperty(PropertyInfo.Name); + return raw switch + { + null => null, + DateTimeOffset dto => dto, + DateTime dt => dt.Kind switch + { + DateTimeKind.Utc => new DateTimeOffset(dt, TimeSpan.Zero), + DateTimeKind.Local => new DateTimeOffset(dt), + _ => new DateTimeOffset(DateTime.SpecifyKind(dt, DateTimeKind.Utc), TimeSpan.Zero) + }, + _ => null + }; + } + set => Entity.SetProperty(PropertyInfo.Name, value, false); + } + + protected DateTime? DateOnlyValue + { + get => Value?.DateTime.Date; + set + { + if (value.HasValue) + { + Value = new DateTimeOffset(value.Value); + } + else + { + Value = null; + } + } + } + + protected DateTime? DateValue + { + get => Value?.DateTime.Date; + set + { + if (value.HasValue) + { + var time = Value?.DateTime.TimeOfDay ?? TimeSpan.Zero; + var offset = Value?.Offset ?? TimeSpan.Zero; + Value = new DateTimeOffset(value.Value.Date + time, offset); + } + else + { + Value = null; + } + } + } + + protected TimeSpan? TimeValue + { + get => Value?.DateTime.TimeOfDay; + set + { + if (Value.HasValue && value.HasValue) + { + var offset = Value.Value.Offset; + Value = new DateTimeOffset(Value.Value.DateTime.Date + value.Value, offset); + } + } + } + + protected string GetDateFormat() + { + var dataFormatString = PropertyInfo.GetDataFormatStringOrNull(); + if (!string.IsNullOrEmpty(dataFormatString)) + { + return dataFormatString.Replace("{0:", "").Replace("}", ""); + } + + return PropertyInfo.IsDate() ? "yyyy-MM-dd" : "yyyy-MM-dd HH:mm"; + } + + protected string GetTimeLabel() + { + return "Time"; + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudExtensionProperties.razor b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudExtensionProperties.razor new file mode 100644 index 0000000000..8208f28e8c --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudExtensionProperties.razor @@ -0,0 +1,32 @@ +@typeparam TEntityType +@typeparam TResourceType +@using Volo.Abp.Data + +@{ + Entity.SetDefaultsForExtraProperties(); + + foreach (var propertyInfo in Properties) + { + if (!propertyInfo.Name.EndsWith("_Text")) + { + if (propertyInfo.IsEnum()) + { + + } + else if (!propertyInfo.Lookup.Url.IsNullOrEmpty()) + { + + } + else + { + var inputType = propertyInfo.GetInputType(); + __builder.OpenComponent(0, inputType.MakeGenericType(new[] { typeof(TEntityType), typeof(TResourceType) })); + __builder.AddAttribute(1, "PropertyInfo", propertyInfo); + __builder.AddAttribute(2, "Entity", Entity); + __builder.AddAttribute(3, "LH", LH); + __builder.AddAttribute(4, "ModalType", ModalType); + __builder.CloseComponent(); + } + } + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudExtensionProperties.razor.cs b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudExtensionProperties.razor.cs new file mode 100644 index 0000000000..6ff16ea051 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudExtensionProperties.razor.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; +using Volo.Abp.Data; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.MudBlazorUI.Components.ObjectExtending; + +public partial class MudExtensionProperties : ComponentBase + where TEntityType : IHasExtraProperties +{ + [Inject] + public IStringLocalizerFactory StringLocalizerFactory { get; set; } = default!; + + [Parameter] + public AbpMudBlazorMessageLocalizerHelper LH { get; set; } = default!; + + [Parameter] + public TEntityType Entity { get; set; } = default!; + + [Inject] + public IServiceProvider ServiceProvider { get; set; } = default!; + + [Parameter] + public ExtensionPropertyModalType? ModalType { get; set; } + + public ImmutableList Properties { get; set; } = ImmutableList.Empty; + + protected override async Task OnInitializedAsync() + { + Properties = + (await ObjectExtensionManager.Instance.GetPropertiesAndCheckPolicyAsync(ServiceProvider)) + .Where(p => ModalType == ExtensionPropertyModalType.CreateModal + ? p.UI.CreateModal.IsVisible + : p.UI.EditModal.IsVisible) + .ToImmutableList(); + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudExtensionPropertyComponentBase.cs b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudExtensionPropertyComponentBase.cs new file mode 100644 index 0000000000..36aa652b7d --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudExtensionPropertyComponentBase.cs @@ -0,0 +1,64 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; +using Volo.Abp.Data; +using Volo.Abp.Localization; +using Volo.Abp.MudBlazorUI.Validation; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.MudBlazorUI.Components.ObjectExtending; + +public abstract class MudExtensionPropertyComponentBase : OwningComponentBase + where TEntity : IHasExtraProperties +{ + [Inject] + public IStringLocalizerFactory StringLocalizerFactory { get; set; } = default!; + + [Inject] + public IAbpEnumLocalizer AbpEnumLocalizer { get; set; } = default!; + + [Parameter] + public TEntity Entity { get; set; } = default!; + + [Parameter] + public ObjectExtensionPropertyInfo PropertyInfo { get; set; } = default!; + + [Parameter] + public AbpMudBlazorMessageLocalizerHelper LH { get; set; } = default!; + + [Parameter] + public ExtensionPropertyModalType? ModalType { get; set; } + + protected virtual string? Validate(object? value) + { + var validationAttributes = PropertyInfo.GetValidationAttributes(); + var validationContext = new ValidationContext(Entity) + { + DisplayName = PropertyInfo.Name, + MemberName = PropertyInfo.Name + }; + + foreach (var validationAttribute in validationAttributes) + { + var result = validationAttribute.GetValidationResult(value, validationContext); + if (result == ValidationResult.Success || result == null) + { + continue; + } + + var errorMessage = result.ErrorMessage; + if (LH != null && errorMessage != null) + { + var formattedErrorMessage = MudValidationAttributeHelper.GetDefaultErrorMessage(validationAttribute); + var errorMessageString = MudValidationAttributeHelper.RevertErrorMessagePlaceholders(formattedErrorMessage); + errorMessage = LH.Localize(errorMessageString); + } + + return errorMessage; + } + + return null; + } + + protected bool IsReadonlyField => ModalType is ExtensionPropertyModalType.EditModal && PropertyInfo.UI.EditModal.IsReadOnly; +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudLookupExtensionProperty.razor b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudLookupExtensionProperty.razor new file mode 100644 index 0000000000..812049a795 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudLookupExtensionProperty.razor @@ -0,0 +1,20 @@ +#nullable enable +@typeparam TEntity +@typeparam TResourceType +@using Volo.Abp.Localization +@inherits MudExtensionPropertyComponentBase + +@if (PropertyInfo != null && Entity != null) +{ + +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudLookupExtensionProperty.razor.cs b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudLookupExtensionProperty.razor.cs new file mode 100644 index 0000000000..7fbb7d08c5 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudLookupExtensionProperty.razor.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Volo.Abp.AspNetCore.Components.Web.Extensibility; +using Volo.Abp.Data; + +namespace Volo.Abp.MudBlazorUI.Components.ObjectExtending; + +public partial class MudLookupExtensionProperty + where TEntity : IHasExtraProperties +{ + protected List> LookupItems = new(); + + [Inject] + public ILookupApiRequestService LookupApiService { get; set; } = default!; + + public string TextPropertyName => PropertyInfo.Name + "_Text"; + + private MudSelectItem? _selectedItem; + + public MudSelectItem SelectedItem { + get => _selectedItem!; + set => _selectedItem = value; + } + + public object? SelectedValue + { + get => Entity.GetProperty(PropertyInfo.Name); + set + { + Entity.SetProperty(PropertyInfo.Name, value, false); + UpdateLookupTextProperty(value); + } + } + + protected override void OnParametersSet() + { + var value = Entity.GetProperty(PropertyInfo.Name); + var text = Entity.GetProperty(TextPropertyName); + if (value != null && text != null) + { + SelectedItem = new MudSelectItem + { + Text = text.ToString()!, + Value = value + }; + LookupItems = new List> { SelectedItem }; + } + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + + if (firstRender) + { + LookupItems = await GetLookupItemsAsync(string.Empty); + await InvokeAsync(StateHasChanged); + } + } + + protected virtual void UpdateLookupTextProperty(object? value) + { + var selectedItemText = LookupItems.SingleOrDefault(t => t.Value?.Equals(value) == true)?.Text; + Entity.SetProperty(TextPropertyName, selectedItemText); + } + + protected virtual async Task>> GetLookupItemsAsync(string filter) + { + var selectItems = new List>(); + + var url = PropertyInfo.Lookup.Url; + if (!string.IsNullOrEmpty(filter)) + { + url += $"?{PropertyInfo.Lookup.FilterParamName}={filter.Trim()}"; + } + + var response = await LookupApiService.SendAsync(url); + + var document = JsonDocument.Parse(response); + var itemsArrayProp = document.RootElement.GetProperty(PropertyInfo.Lookup.ResultListPropertyName); + foreach (var item in itemsArrayProp.EnumerateArray()) + { + selectItems.Add(new MudSelectItem + { + Text = item.GetProperty(PropertyInfo.Lookup.DisplayPropertyName).GetString()!, + Value = JsonSerializer.Deserialize(item.GetProperty(PropertyInfo.Lookup.ValuePropertyName).GetRawText(), PropertyInfo.Type)! + }); + } + + return selectItems; + } + + protected virtual Task SelectedItemChanged(MudSelectItem item) + { + SelectedItem = item; + SelectedValue = item?.Value; + return Task.CompletedTask; + } + + protected virtual async Task?>> SearchAsync(string? value, CancellationToken token) + { + LookupItems = await GetLookupItemsAsync(value ?? string.Empty); + return LookupItems!; + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudSelectExtensionProperty.razor b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudSelectExtensionProperty.razor new file mode 100644 index 0000000000..902691d5f2 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudSelectExtensionProperty.razor @@ -0,0 +1,18 @@ +@typeparam TEntity +@typeparam TResourceType +@using Volo.Abp.Localization +@inherits MudExtensionPropertyComponentBase + +@if (PropertyInfo != null && Entity != null) +{ + + @foreach (var item in SelectItems) + { + @item.Text + } + +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudSelectExtensionProperty.razor.cs b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudSelectExtensionProperty.razor.cs new file mode 100644 index 0000000000..ee9f5b2bb7 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudSelectExtensionProperty.razor.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Localization; +using Volo.Abp.Data; + +namespace Volo.Abp.MudBlazorUI.Components.ObjectExtending; + +public partial class MudSelectExtensionProperty + where TEntity : IHasExtraProperties +{ + protected List> SelectItems = new(); + + public int? SelectedValue + { + get => Entity.GetProperty(PropertyInfo.Name, Nullable.GetUnderlyingType(PropertyInfo.Type!) != null ? null : 0); + set => Entity.SetProperty(PropertyInfo.Name, value, false); + } + + protected virtual List> GetSelectItemsFromEnum() + { + var selectItems = new List>(); + + var isNullableType = Nullable.GetUnderlyingType(PropertyInfo.Type!) != null; + var enumType = isNullableType + ? Nullable.GetUnderlyingType(PropertyInfo.Type)! + : PropertyInfo.Type; + + if (isNullableType) + { + selectItems.Add(new MudSelectItem()); + } + + foreach (var enumValue in enumType.GetEnumValues()) + { + selectItems.Add(new MudSelectItem + { + Value = (int)enumValue, + Text = AbpEnumLocalizer.GetString(enumType, enumValue, new[] { StringLocalizerFactory.CreateDefaultOrNull() }) + }); + } + + return selectItems; + } + + protected override void OnParametersSet() + { + SelectItems = GetSelectItemsFromEnum(); + StateHasChanged(); + + if (!Entity.HasProperty(PropertyInfo.Name)) + { + var isNullableType = Nullable.GetUnderlyingType(PropertyInfo.Type!) != null; + if (!isNullableType) + { + SelectedValue = (int)PropertyInfo.Type.GetEnumValues().GetValue(0)!; + } + } + } +} + +public class MudSelectItem +{ + public string Text { get; set; } = default!; + public TValue Value { get; set; } = default!; +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudTextAreaExtensionProperty.razor b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudTextAreaExtensionProperty.razor new file mode 100644 index 0000000000..2aa3e55f75 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudTextAreaExtensionProperty.razor @@ -0,0 +1,16 @@ +@typeparam TEntity +@typeparam TResourceType +@using Volo.Abp.Localization +@inherits MudExtensionPropertyComponentBase + +@if (PropertyInfo != null && Entity != null) +{ + +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudTextAreaExtensionProperty.razor.cs b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudTextAreaExtensionProperty.razor.cs new file mode 100644 index 0000000000..84c8017c74 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudTextAreaExtensionProperty.razor.cs @@ -0,0 +1,13 @@ +using Volo.Abp.Data; + +namespace Volo.Abp.MudBlazorUI.Components.ObjectExtending; + +public partial class MudTextAreaExtensionProperty + where TEntity : IHasExtraProperties +{ + protected string? Value + { + get => PropertyInfo.GetTextInputValueOrNull(Entity.GetProperty(PropertyInfo.Name)); + set => Entity.SetProperty(PropertyInfo.Name, value, validate: false); + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudTextExtensionProperty.razor b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudTextExtensionProperty.razor new file mode 100644 index 0000000000..aaa7326a85 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudTextExtensionProperty.razor @@ -0,0 +1,16 @@ +@typeparam TEntity +@typeparam TResourceType +@using Volo.Abp.Localization +@inherits MudExtensionPropertyComponentBase + +@if (PropertyInfo != null && Entity != null) +{ + +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudTextExtensionProperty.razor.cs b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudTextExtensionProperty.razor.cs new file mode 100644 index 0000000000..f100372b37 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudTextExtensionProperty.razor.cs @@ -0,0 +1,101 @@ +using System.ComponentModel.DataAnnotations; +using System.Linq; +using MudBlazor; +using Volo.Abp.Data; +using Volo.Abp.Reflection; + +namespace Volo.Abp.MudBlazorUI.Components.ObjectExtending; + +public partial class MudTextExtensionProperty + where TEntity : IHasExtraProperties +{ + protected string? Value + { + get => PropertyInfo.GetTextInputValueOrNull(Entity.GetProperty(PropertyInfo.Name)); + set => Entity.SetProperty(PropertyInfo.Name, value, validate: false); + } + + protected InputType GetInputType() + { + foreach (var attribute in PropertyInfo.Attributes) + { + if (attribute is DataTypeAttribute dataTypeAttribute) + { + return dataTypeAttribute.DataType switch + { + DataType.Password => InputType.Password, + DataType.EmailAddress => InputType.Email, + DataType.Url => InputType.Url, + DataType.PhoneNumber => InputType.Telephone, + _ => InputType.Text + }; + } + + if (attribute is EmailAddressAttribute) + { + return InputType.Email; + } + + if (attribute is UrlAttribute) + { + return InputType.Url; + } + + if (attribute is PhoneAttribute) + { + return InputType.Telephone; + } + } + + return InputType.Text; + } + + protected InputMode GetInputMode() + { + foreach (var attribute in PropertyInfo.Attributes) + { + if (attribute is DataTypeAttribute dataTypeAttribute) + { + return dataTypeAttribute.DataType switch + { + DataType.EmailAddress => InputMode.email, + DataType.Url => InputMode.url, + DataType.PhoneNumber => InputMode.tel, + _ => GetInputModeFromType() + }; + } + + if (attribute is EmailAddressAttribute) + { + return InputMode.email; + } + + if (attribute is UrlAttribute) + { + return InputMode.url; + } + + if (attribute is PhoneAttribute) + { + return InputMode.tel; + } + } + + return GetInputModeFromType(); + } + + private InputMode GetInputModeFromType() + { + if (TypeHelper.IsFloatingType(PropertyInfo.Type)) + { + return InputMode.@decimal; + } + + if (MudBlazorUiObjectExtensionPropertyInfoExtensions.NumberTypes.Contains(PropertyInfo.Type)) + { + return InputMode.numeric; + } + + return InputMode.text; + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudTimeExtensionProperty.razor b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudTimeExtensionProperty.razor new file mode 100644 index 0000000000..2c4c4d79b4 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudTimeExtensionProperty.razor @@ -0,0 +1,13 @@ +@typeparam TEntity +@typeparam TResourceType +@using Volo.Abp.Localization +@inherits MudExtensionPropertyComponentBase + +@if (PropertyInfo != null && Entity != null) +{ + +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudTimeExtensionProperty.razor.cs b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudTimeExtensionProperty.razor.cs new file mode 100644 index 0000000000..be83d980a9 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/ObjectExtending/MudTimeExtensionProperty.razor.cs @@ -0,0 +1,14 @@ +using System; +using Volo.Abp.Data; + +namespace Volo.Abp.MudBlazorUI.Components.ObjectExtending; + +public partial class MudTimeExtensionProperty + where TEntity : IHasExtraProperties +{ + protected TimeSpan? Value + { + get => PropertyInfo.GetInputValueOrDefault(Entity.GetProperty(PropertyInfo.Name)); + set => Entity.SetProperty(PropertyInfo.Name, value, false); + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/PageAlert.razor b/framework/src/Volo.Abp.MudBlazorUI/Components/PageAlert.razor new file mode 100644 index 0000000000..81798d124f --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/PageAlert.razor @@ -0,0 +1,18 @@ +@using MudBlazor + +@foreach (var alert in Alerts) +{ + if (alert.IsVisible) + { + + @if (!string.IsNullOrEmpty(alert.AlertMessage.Title)) + { + @alert.AlertMessage.Title + } + @alert.AlertMessage.Text + + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/PageAlert.razor.cs b/framework/src/Volo.Abp.MudBlazorUI/Components/PageAlert.razor.cs new file mode 100644 index 0000000000..bec9130877 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/PageAlert.razor.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Routing; +using MudBlazor; +using Volo.Abp.AspNetCore.Components.Alerts; + +namespace Volo.Abp.MudBlazorUI.Components; + +public partial class PageAlert : ComponentBase, IDisposable +{ + private List Alerts = new List(); + + [Inject] + protected IAlertManager AlertManager { get; set; } = default!; + + [Inject] + protected NavigationManager NavigationManager { get; set; } = default!; + + protected override void OnInitialized() + { + base.OnInitialized(); + NavigationManager.LocationChanged += NavigationManager_LocationChanged; + AlertManager.Alerts.CollectionChanged += Alerts_CollectionChanged; + + Alerts.AddRange(AlertManager.Alerts.Select(t => new AlertWrapper + { + AlertMessage = t, + IsVisible = true + })); + } + + //Since Blazor WASM doesn't support scoped dependency, we need to clear alerts on each location changed event. + private void NavigationManager_LocationChanged(object? sender, LocationChangedEventArgs e) + { + AlertManager.Alerts.Clear(); + Alerts.Clear(); + } + + private void Alerts_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + if (e.Action == NotifyCollectionChangedAction.Add) + { + foreach (var item in e.NewItems!) + { + Alerts.Add(new AlertWrapper + { + AlertMessage = (AlertMessage)item, + IsVisible = true + }); + } + } + InvokeAsync(StateHasChanged); + } + + protected virtual Severity GetAlertSeverity(AlertType alertType) + { + return alertType switch + { + AlertType.Info => Severity.Info, + AlertType.Success => Severity.Success, + AlertType.Warning => Severity.Warning, + AlertType.Danger => Severity.Error, + _ => Severity.Normal, + }; + } + + public void Dispose() + { + NavigationManager.LocationChanged -= NavigationManager_LocationChanged; + AlertManager.Alerts.CollectionChanged -= Alerts_CollectionChanged; + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/UiMessageAlert.razor b/framework/src/Volo.Abp.MudBlazorUI/Components/UiMessageAlert.razor new file mode 100644 index 0000000000..15233cd198 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/UiMessageAlert.razor @@ -0,0 +1,68 @@ +@using MudBlazor + + + + @if (!string.IsNullOrEmpty(Title)) + { + @Title + } + + +
+ @if (ShowMessageIcon) + { + + } + + @if (!IsMessageHtmlMarkup) + { + @Message + } + else + { + @((MarkupString)Message) + } + +
+
+ +
+ @if (IsConfirmation) + { + + @if (Options?.CancelButtonIcon != null) + { + + } + @CancelButtonText + + + @if (Options?.ConfirmButtonIcon != null) + { + + } + @ConfirmButtonText + + } + else + { + + @if (Options?.OkButtonIcon != null) + { + + } + @OkButtonText + + } +
+
+
diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/UiMessageAlert.razor.cs b/framework/src/Volo.Abp.MudBlazorUI/Components/UiMessageAlert.razor.cs new file mode 100644 index 0000000000..58ed2fae1b --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/UiMessageAlert.razor.cs @@ -0,0 +1,155 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using MudBlazor; +using Volo.Abp.AspNetCore.Components.Messages; + +namespace Volo.Abp.MudBlazorUI.Components; + +public partial class UiMessageAlert : ComponentBase, IDisposable +{ + private bool _visible; + private readonly DialogOptions _dialogOptions = new() + { + CloseOnEscapeKey = false, + BackdropClick = false, + MaxWidth = MaxWidth.Small, + FullWidth = true + }; + + protected virtual bool IsConfirmation + => MessageType == UiMessageType.Confirmation; + + protected virtual bool CenterMessage + => Options?.CenterMessage ?? true; + + protected virtual bool ShowMessageIcon + => Options?.ShowMessageIcon ?? true; + + protected virtual bool IsMessageHtmlMarkup + => Options?.IsMessageHtmlMarkup ?? false; + + protected virtual string MessageIcon => MessageType switch + { + UiMessageType.Info => Icons.Material.Filled.Info, + UiMessageType.Success => Icons.Material.Filled.CheckCircle, + UiMessageType.Warning => Icons.Material.Filled.Warning, + UiMessageType.Error => Icons.Material.Filled.Error, + UiMessageType.Confirmation => Icons.Material.Filled.Help, + _ => Icons.Material.Filled.Info, + }; + + protected virtual Color MessageIconColor => MessageType switch + { + UiMessageType.Info => Color.Info, + UiMessageType.Success => Color.Success, + UiMessageType.Warning => Color.Warning, + UiMessageType.Error => Color.Error, + UiMessageType.Confirmation => Color.Default, + _ => Color.Default, + }; + + protected virtual string OkButtonText + => Options?.OkButtonText ?? "OK"; + + protected virtual string ConfirmButtonText + => Options?.ConfirmButtonText ?? "Confirm"; + + protected virtual string CancelButtonText + => Options?.CancelButtonText ?? "Cancel"; + + [Parameter] public UiMessageType MessageType { get; set; } + + [Parameter] public string? Title { get; set; } + + [Parameter] public string Message { get; set; } = default!; + + [Parameter] public TaskCompletionSource? Callback { get; set; } + + [Parameter] public UiMessageOptions? Options { get; set; } + + [Parameter] public EventCallback Okayed { get; set; } + + [Parameter] public EventCallback Confirmed { get; set; } + + [Parameter] public EventCallback Canceled { get; set; } + + [Inject] protected MudBlazorUiMessageService? UiMessageService { get; set; } + + protected override void OnInitialized() + { + base.OnInitialized(); + + if (UiMessageService != null) + { + UiMessageService.MessageReceived += OnMessageReceived; + } + } + + private async void OnMessageReceived(object? sender, UiMessageEventArgs e) + { + MessageType = e.MessageType; + Message = e.Message; + Title = e.Title; + Options = e.Options; + Callback = e.Callback; + + await ShowMessageAlert(); + } + + protected virtual async Task ShowMessageAlert() + { + await InvokeAsync(() => + { + _visible = true; + StateHasChanged(); + }); + } + + protected virtual async Task HideMessageAlert() + { + await InvokeAsync(() => + { + _visible = false; + StateHasChanged(); + }); + } + + public void Dispose() + { + if (UiMessageService != null) + { + UiMessageService.MessageReceived -= OnMessageReceived; + } + } + + protected async Task OnOkClicked() + { + await HideMessageAlert(); + await Okayed.InvokeAsync(null); + } + + protected async Task OnConfirmClicked() + { + await HideMessageAlert(); + + if (IsConfirmation && Callback != null) + { + Callback.SetResult(true); + } + + await Confirmed.InvokeAsync(null); + } + + protected async Task OnCancelClicked() + { + await HideMessageAlert(); + + if (IsConfirmation && Callback != null) + { + Callback.SetResult(false); + } + + await Canceled.InvokeAsync(null); + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/UiNotificationAlert.razor b/framework/src/Volo.Abp.MudBlazorUI/Components/UiNotificationAlert.razor new file mode 100644 index 0000000000..fdf1b7cbe4 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/UiNotificationAlert.razor @@ -0,0 +1 @@ +@using MudBlazor diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/UiNotificationAlert.razor.cs b/framework/src/Volo.Abp.MudBlazorUI/Components/UiNotificationAlert.razor.cs new file mode 100644 index 0000000000..ac5c3a8c0d --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/UiNotificationAlert.razor.cs @@ -0,0 +1,82 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; +using MudBlazor; +using Volo.Abp.AspNetCore.Components.Notifications; +using Volo.Abp.Localization; + +namespace Volo.Abp.MudBlazorUI.Components; + +public partial class UiNotificationAlert : ComponentBase, IDisposable +{ + [Parameter] public UiNotificationType NotificationType { get; set; } + + [Parameter] public string Message { get; set; } = default!; + + [Parameter] public string? Title { get; set; } + + [Parameter] public UiNotificationOptions? Options { get; set; } + + [Parameter] public EventCallback Okayed { get; set; } + + [Parameter] public EventCallback Closed { get; set; } + + [Inject] protected MudBlazorUiNotificationService? UiNotificationService { get; set; } + + [Inject] protected ISnackbar Snackbar { get; set; } = default!; + + [Inject] protected IStringLocalizerFactory StringLocalizerFactory { get; set; } = default!; + + protected virtual Severity GetSeverity(UiNotificationType notificationType) + { + return notificationType switch + { + UiNotificationType.Info => Severity.Info, + UiNotificationType.Success => Severity.Success, + UiNotificationType.Warning => Severity.Warning, + UiNotificationType.Error => Severity.Error, + _ => Severity.Normal, + }; + } + + protected override void OnInitialized() + { + base.OnInitialized(); + + if (UiNotificationService != null) + { + UiNotificationService.NotificationReceived += OnNotificationReceived; + } + } + + protected virtual async void OnNotificationReceived(object? sender, UiNotificationEventArgs e) + { + NotificationType = e.NotificationType; + Message = e.Message; + Title = e.Title; + Options = e.Options; + + var message = !string.IsNullOrEmpty(Title) + ? $"{Title}
{Message}" + : Message; + + await InvokeAsync(() => + { + Snackbar.Add(message, GetSeverity(e.NotificationType), config => + { + config.RequireInteraction = false; + config.ShowCloseIcon = true; + config.CloseAfterNavigation = true; + }); + }); + } + + public virtual void Dispose() + { + if (UiNotificationService != null) + { + UiNotificationService.NotificationReceived -= OnNotificationReceived; + } + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/UiPageProgress.razor b/framework/src/Volo.Abp.MudBlazorUI/Components/UiPageProgress.razor new file mode 100644 index 0000000000..d60e4734be --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/UiPageProgress.razor @@ -0,0 +1,10 @@ +@using MudBlazor + +@if (Visible) +{ + +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Components/UiPageProgress.razor.cs b/framework/src/Volo.Abp.MudBlazorUI/Components/UiPageProgress.razor.cs new file mode 100644 index 0000000000..e85349b25a --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Components/UiPageProgress.razor.cs @@ -0,0 +1,56 @@ +using System; +using Microsoft.AspNetCore.Components; +using MudBlazor; +using Volo.Abp.AspNetCore.Components.Progression; + +namespace Volo.Abp.MudBlazorUI.Components; + +public partial class UiPageProgress : ComponentBase, IDisposable +{ + protected int? Percentage { get; set; } + + protected bool Visible { get; set; } + + protected Color Color { get; set; } = Color.Primary; + + [Inject] protected IUiPageProgressService? UiPageProgressService { get; set; } + + protected override void OnInitialized() + { + base.OnInitialized(); + + if (UiPageProgressService != null) + { + UiPageProgressService.ProgressChanged += OnProgressChanged; + } + } + + private async void OnProgressChanged(object? sender, UiPageProgressEventArgs e) + { + Percentage = e.Percentage; + Visible = e.Percentage == null || (e.Percentage >= 0 && e.Percentage <= 100); + Color = GetColor(e.Options.Type); + + await InvokeAsync(StateHasChanged); + } + + public virtual void Dispose() + { + if (UiPageProgressService != null) + { + UiPageProgressService.ProgressChanged -= OnProgressChanged; + } + } + + protected virtual Color GetColor(UiPageProgressType pageProgressType) + { + return pageProgressType switch + { + UiPageProgressType.Info => Color.Info, + UiPageProgressType.Success => Color.Success, + UiPageProgressType.Warning => Color.Warning, + UiPageProgressType.Error => Color.Error, + _ => Color.Primary, + }; + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/FodyWeavers.xml b/framework/src/Volo.Abp.MudBlazorUI/FodyWeavers.xml new file mode 100644 index 0000000000..7e9f94ead6 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + diff --git a/framework/src/Volo.Abp.MudBlazorUI/FodyWeavers.xsd b/framework/src/Volo.Abp.MudBlazorUI/FodyWeavers.xsd new file mode 100644 index 0000000000..ffa6fc4b78 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.MudBlazorUI/MudBlazorExtensionPropertyPolicyChecker.cs b/framework/src/Volo.Abp.MudBlazorUI/MudBlazorExtensionPropertyPolicyChecker.cs new file mode 100644 index 0000000000..bd021b593d --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/MudBlazorExtensionPropertyPolicyChecker.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; +using Volo.Abp.GlobalFeatures; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.MudBlazorUI; + +[Dependency(ReplaceServices = true)] +[ExposeServices(typeof(ExtensionPropertyPolicyChecker))] +public class MudBlazorExtensionPropertyPolicyChecker : ExtensionPropertyPolicyChecker +{ + protected IFeatureChecker FeatureChecker { get; } + protected IPermissionChecker PermissionChecker { get; } + + public MudBlazorExtensionPropertyPolicyChecker(IFeatureChecker featureChecker, IPermissionChecker permissionChecker) + { + FeatureChecker = featureChecker; + PermissionChecker = permissionChecker; + } + + protected override Task CheckGlobalFeaturesAsync(string featureName) + { + return Task.FromResult(GlobalFeatureManager.Instance.IsEnabled(featureName)); + } + + protected async override Task CheckFeaturesAsync(string featureName) + { + return await FeatureChecker.IsEnabledAsync(featureName); + } + + protected async override Task CheckPermissionsAsync(string permissionName) + { + return await PermissionChecker.IsGrantedAsync(permissionName); + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/MudBlazorUiMessageService.cs b/framework/src/Volo.Abp.MudBlazorUI/MudBlazorUiMessageService.cs new file mode 100644 index 0000000000..4d329feaec --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/MudBlazorUiMessageService.cs @@ -0,0 +1,93 @@ +using System; +using System.Threading.Tasks; +using Localization.Resources.AbpUi; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.AspNetCore.Components.Messages; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.MudBlazorUI; + +[Dependency(ReplaceServices = true)] +public class MudBlazorUiMessageService : IUiMessageService, IScopedDependency +{ + /// + /// An event raised after the message is received. Used to notify the message dialog. + /// + public event EventHandler? MessageReceived; + + private readonly IStringLocalizer _localizer; + + public ILogger Logger { get; set; } + + public MudBlazorUiMessageService(IStringLocalizer localizer) + { + _localizer = localizer; + Logger = NullLogger.Instance; + } + + public Task Info(string message, string? title = null, Action? options = null) + { + var uiMessageOptions = CreateDefaultOptions(); + options?.Invoke(uiMessageOptions); + + MessageReceived?.Invoke(this, new UiMessageEventArgs(UiMessageType.Info, message, title, uiMessageOptions)); + + return Task.CompletedTask; + } + + public Task Success(string message, string? title = null, Action? options = null) + { + var uiMessageOptions = CreateDefaultOptions(); + options?.Invoke(uiMessageOptions); + + MessageReceived?.Invoke(this, new UiMessageEventArgs(UiMessageType.Success, message, title, uiMessageOptions)); + + return Task.CompletedTask; + } + + public Task Warn(string message, string? title = null, Action? options = null) + { + var uiMessageOptions = CreateDefaultOptions(); + options?.Invoke(uiMessageOptions); + + MessageReceived?.Invoke(this, new UiMessageEventArgs(UiMessageType.Warning, message, title, uiMessageOptions)); + + return Task.CompletedTask; + } + + public Task Error(string message, string? title = null, Action? options = null) + { + var uiMessageOptions = CreateDefaultOptions(); + options?.Invoke(uiMessageOptions); + + MessageReceived?.Invoke(this, new UiMessageEventArgs(UiMessageType.Error, message, title, uiMessageOptions)); + + return Task.CompletedTask; + } + + public Task Confirm(string message, string? title = null, Action? options = null) + { + var uiMessageOptions = CreateDefaultOptions(); + options?.Invoke(uiMessageOptions); + + var callback = new TaskCompletionSource(); + + MessageReceived?.Invoke(this, new UiMessageEventArgs(UiMessageType.Confirmation, message, title, uiMessageOptions, callback)); + + return callback.Task; + } + + protected virtual UiMessageOptions CreateDefaultOptions() + { + return new UiMessageOptions + { + CenterMessage = true, + ShowMessageIcon = true, + OkButtonText = _localizer["Ok"], + CancelButtonText = _localizer["Cancel"], + ConfirmButtonText = _localizer["Yes"], + }; + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/MudBlazorUiNotificationService.cs b/framework/src/Volo.Abp.MudBlazorUI/MudBlazorUiNotificationService.cs new file mode 100644 index 0000000000..e8fd9b493a --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/MudBlazorUiNotificationService.cs @@ -0,0 +1,60 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Components.Notifications; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.MudBlazorUI; + +[Dependency(ReplaceServices = true)] +public class MudBlazorUiNotificationService : IUiNotificationService, IScopedDependency +{ + /// + /// An event raised after the notification is received. + /// + public event EventHandler? NotificationReceived; + + public Task Info(string message, string? title = null, Action? options = null) + { + var uiNotificationOptions = CreateDefaultOptions(); + options?.Invoke(uiNotificationOptions); + + NotificationReceived?.Invoke(this, new UiNotificationEventArgs(UiNotificationType.Info, message, title, uiNotificationOptions)); + + return Task.CompletedTask; + } + + public Task Success(string message, string? title = null, Action? options = null) + { + var uiNotificationOptions = CreateDefaultOptions(); + options?.Invoke(uiNotificationOptions); + + NotificationReceived?.Invoke(this, new UiNotificationEventArgs(UiNotificationType.Success, message, title, uiNotificationOptions)); + + return Task.CompletedTask; + } + + public Task Warn(string message, string? title = null, Action? options = null) + { + var uiNotificationOptions = CreateDefaultOptions(); + options?.Invoke(uiNotificationOptions); + + NotificationReceived?.Invoke(this, new UiNotificationEventArgs(UiNotificationType.Warning, message, title, uiNotificationOptions)); + + return Task.CompletedTask; + } + + public Task Error(string message, string? title = null, Action? options = null) + { + var uiNotificationOptions = CreateDefaultOptions(); + options?.Invoke(uiNotificationOptions); + + NotificationReceived?.Invoke(this, new UiNotificationEventArgs(UiNotificationType.Error, message, title, uiNotificationOptions)); + + return Task.CompletedTask; + } + + protected virtual UiNotificationOptions CreateDefaultOptions() + { + return new UiNotificationOptions(); + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/MudBlazorUiObjectExtensionPropertyInfoExtensions.cs b/framework/src/Volo.Abp.MudBlazorUI/MudBlazorUiObjectExtensionPropertyInfoExtensions.cs new file mode 100644 index 0000000000..db55e67058 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/MudBlazorUiObjectExtensionPropertyInfoExtensions.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Frozen; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using Volo.Abp.MudBlazorUI.Components.ObjectExtending; +using Volo.Abp.ObjectExtending; +using Volo.Abp.Reflection; + +namespace Volo.Abp.MudBlazorUI; + +public static class MudBlazorUiObjectExtensionPropertyInfoExtensions +{ + private static readonly Type[] DateTimeTypes = + { + typeof(DateTime), + typeof(DateTime?), + typeof(DateTimeOffset), + typeof(DateTimeOffset?) + }; + + internal static readonly FrozenSet NumberTypes = new HashSet + { + typeof(int), + typeof(long), + typeof(byte), + typeof(sbyte), + typeof(short), + typeof(ushort), + typeof(uint), + typeof(ulong), + typeof(float), + typeof(double), + typeof(decimal), + typeof(int?), + typeof(long?), + typeof(byte?), + typeof(sbyte?), + typeof(short?), + typeof(ushort?), + typeof(uint?), + typeof(ulong?), + typeof(float?), + typeof(double?), + typeof(decimal?) + }.ToFrozenSet(); + + private static readonly FrozenSet TextEditSupportedAttributeTypes = new HashSet + { + typeof(EmailAddressAttribute), + typeof(UrlAttribute), + typeof(PhoneAttribute) + }.ToFrozenSet(); + + public static bool IsDate(this IBasicObjectExtensionPropertyInfo property) + { + return DateTimeTypes.Contains(property.Type) && + property.GetDataTypeOrNull() == DataType.Date; + } + + public static bool IsDateTime(this IBasicObjectExtensionPropertyInfo property) + { + return DateTimeTypes.Contains(property.Type) && + !property.IsDate(); + } + + public static DataType? GetDataTypeOrNull(this IBasicObjectExtensionPropertyInfo property) + { + return property + .Attributes + .OfType() + .FirstOrDefault()?.DataType; + } + + public static string? GetDateEditInputFormatOrNull(this IBasicObjectExtensionPropertyInfo property) + { + var dataFormatString = property.GetDataFormatStringOrNull(); + if (dataFormatString != null) + { + return dataFormatString; + } + + if (property.IsDate()) + { + return "{0:yyyy-MM-dd}"; + } + + if (property.IsDateTime()) + { + return "{0:yyyy-MM-ddTHH:mm}"; + } + + return null; + } + + public static string? GetDataFormatStringOrNull(this IBasicObjectExtensionPropertyInfo property) + { + return property + .Attributes + .OfType() + .FirstOrDefault()?.DataFormatString; + } + + public static string? GetTextInputValueOrNull(this IBasicObjectExtensionPropertyInfo property, object? value) + { + if (value == null) + { + return null; + } + + if (TypeHelper.IsFloatingType(property.Type)) + { + return value.ToString()?.Replace(',', '.'); + } + + return value.ToString(); + } + + public static T? GetInputValueOrDefault(this IBasicObjectExtensionPropertyInfo property, object? value) + { + if (value == null) + { + return default; + } + + return (T)value; + } + + public static Type GetInputType(this ObjectExtensionPropertyInfo propertyInfo) + { + foreach (var attribute in propertyInfo.Attributes) + { + var inputTypeByAttribute = GetInputTypeFromAttributeOrNull(attribute, propertyInfo.Type); + if (inputTypeByAttribute != null) + { + return inputTypeByAttribute; + } + } + return GetInputTypeFromTypeOrNull(propertyInfo.Type) + ?? typeof(MudTextExtensionProperty<,>); //default + } + + public static bool IsEnum(this ObjectExtensionPropertyInfo propertyInfo) + { + return propertyInfo.Type.IsEnum || TypeHelper.IsNullableEnum(propertyInfo.Type); + } + + private static Type? GetInputTypeFromAttributeOrNull(Attribute attribute, Type propertyType) + { + var hasTextEditSupport = TextEditSupportedAttributeTypes.Any(t => t == attribute.GetType()); + + if (hasTextEditSupport) + { + return typeof(MudTextExtensionProperty<,>); + } + + if (attribute is DataTypeAttribute dataTypeAttribute) + { + switch (dataTypeAttribute.DataType) + { + case DataType.Password: + case DataType.EmailAddress: + case DataType.Url: + case DataType.PhoneNumber: + return typeof(MudTextExtensionProperty<,>); + case DataType.Date: + case DataType.DateTime: + return propertyType == typeof(DateTimeOffset) || propertyType == typeof(DateTimeOffset?) + ? typeof(MudDateTimeOffsetExtensionProperty<,>) + : typeof(MudDateTimeExtensionProperty<,>); + case DataType.Time: + return typeof(MudTimeExtensionProperty<,>); + case DataType.MultilineText: + return typeof(MudTextAreaExtensionProperty<,>); + } + } + + return null; + } + + private static Type? GetInputTypeFromTypeOrNull(Type type) + { + if (type == typeof(bool)) + { + return typeof(MudCheckExtensionProperty<,>); + } + + if (type == typeof(DateTime) || type == typeof(DateTime?)) + { + return typeof(MudDateTimeExtensionProperty<,>); + } + + if (type == typeof(DateTimeOffset) || type == typeof(DateTimeOffset?)) + { + return typeof(MudDateTimeOffsetExtensionProperty<,>); + } + + if (NumberTypes.Contains(type)) + { + return typeof(MudTextExtensionProperty<,>); + } + + return null; + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/MudBlazorUiPageProgressService.cs b/framework/src/Volo.Abp.MudBlazorUI/MudBlazorUiPageProgressService.cs new file mode 100644 index 0000000000..0104eebebf --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/MudBlazorUiPageProgressService.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Components.Progression; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.MudBlazorUI; + +[Dependency(ReplaceServices = true)] +public class MudBlazorUiPageProgressService : IUiPageProgressService, IScopedDependency +{ + /// + /// An event raised after the progress is changed. + /// + public event EventHandler? ProgressChanged; + + public Task Go(int? percentage, Action? options = null) + { + var uiPageProgressOptions = CreateDefaultOptions(); + options?.Invoke(uiPageProgressOptions); + + ProgressChanged?.Invoke(this, new UiPageProgressEventArgs(percentage, uiPageProgressOptions)); + + return Task.CompletedTask; + } + + protected virtual UiPageProgressOptions CreateDefaultOptions() + { + return new UiPageProgressOptions(); + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Validation/MudValidationAttributeHelper.cs b/framework/src/Volo.Abp.MudBlazorUI/Validation/MudValidationAttributeHelper.cs new file mode 100644 index 0000000000..40dde703df --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Validation/MudValidationAttributeHelper.cs @@ -0,0 +1,52 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace Volo.Abp.MudBlazorUI.Validation; + +public static class MudValidationAttributeHelper +{ + internal static readonly PropertyInfo ValidationAttributeErrorMessageStringProperty = typeof(ValidationAttribute) + .GetProperty("ErrorMessageString", BindingFlags.Instance | BindingFlags.NonPublic)!; + + internal static readonly PropertyInfo ValidationAttributeCustomErrorMessageSetProperty = typeof(ValidationAttribute) + .GetProperty("CustomErrorMessageSet", BindingFlags.Instance | BindingFlags.NonPublic)!; + + private const string PlaceholderPrefix = "__Abp_"; + + public static string SetErrorMessagePlaceholders(string? errorMessage) + { + if (string.IsNullOrEmpty(errorMessage)) + { + return string.Empty; + } + + return Regex.Replace(errorMessage, @"\{(\d+)\}", m => PlaceholderPrefix + m.Groups[1].Value); + } + + public static string RevertErrorMessagePlaceholders(string? errorMessage) + { + if (string.IsNullOrEmpty(errorMessage)) + { + return string.Empty; + } + + return Regex.Replace(errorMessage, PlaceholderPrefix + @"(\d+)", m => "{" + m.Groups[1].Value + "}"); + } + + public static string GetDefaultErrorMessage(ValidationAttribute validationAttribute) + { + if (validationAttribute is StringLengthAttribute stringLengthAttribute && stringLengthAttribute.MinimumLength != 0) + { + var customErrorMessageSet = ValidationAttributeCustomErrorMessageSetProperty.GetValue(validationAttribute) as bool?; + if (customErrorMessageSet != true) + { + return SetErrorMessagePlaceholders("The field {0} must be a string with a minimum length of {2} and a maximum length of {1}."); + } + } + + var errorMessageString = ValidationAttributeErrorMessageStringProperty.GetValue(validationAttribute) as string; + return SetErrorMessagePlaceholders(errorMessageString); + } +} diff --git a/framework/src/Volo.Abp.MudBlazorUI/Volo.Abp.MudBlazorUI.csproj b/framework/src/Volo.Abp.MudBlazorUI/Volo.Abp.MudBlazorUI.csproj new file mode 100644 index 0000000000..16ed2f6423 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/Volo.Abp.MudBlazorUI.csproj @@ -0,0 +1,24 @@ + + + + + + + net10.0 + enable + Nullable + + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.MudBlazorUI/_Imports.razor b/framework/src/Volo.Abp.MudBlazorUI/_Imports.razor new file mode 100644 index 0000000000..5a0411ed78 --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/_Imports.razor @@ -0,0 +1,2 @@ +@using Microsoft.AspNetCore.Components.Web +@using MudBlazor diff --git a/framework/src/Volo.Abp.MudBlazorUI/wwwroot/volo.abp.mudblazorui.css b/framework/src/Volo.Abp.MudBlazorUI/wwwroot/volo.abp.mudblazorui.css new file mode 100644 index 0000000000..bd6174466b --- /dev/null +++ b/framework/src/Volo.Abp.MudBlazorUI/wwwroot/volo.abp.mudblazorui.css @@ -0,0 +1,60 @@ +/* ABP MudBlazor UI Custom Styles */ + +/* Entity Actions Styling */ +.abp-entity-actions .mud-menu-activator { + min-width: auto; +} + +/* Data Grid Customizations */ +.abp-mud-data-grid .mud-table-container { + overflow-x: auto; +} + +/* Extension Property Form Spacing */ +.abp-extension-property { + margin-bottom: 16px; +} + +/* Dialog Customizations */ +.abp-mud-dialog .mud-dialog-title { + padding: 16px 24px; +} + +.abp-mud-dialog .mud-dialog-content { + padding: 0 24px 16px; +} + +.abp-mud-dialog .mud-dialog-actions { + padding: 8px 24px 16px; +} + +/* Message Alert Icon Sizing */ +.abp-message-alert-icon { + font-size: 3rem; +} + +/* Page Progress Bar */ +.abp-page-progress { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 9999; +} + +/* CRUD Page Layout */ +.abp-crud-page-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; +} + +.abp-crud-page-header-title { + margin: 0; +} + +.abp-crud-page-header-actions { + display: flex; + gap: 8px; +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ChannelPool.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ChannelPool.cs index 03d6029b69..933e1bf005 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ChannelPool.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/ChannelPool.cs @@ -61,29 +61,45 @@ public class ChannelPool : IChannelPool, ISingletonDependency } } - poolItem.Acquire(); - if (poolItem.Channel.IsClosed) { - await poolItem.DisposeAsync(); - Channels.TryRemove(channelName, out _); - - using (await Semaphore.LockAsync()) + ChannelPoolItem? staleItem = null; + try { - if (Channels.TryGetValue(channelName, out var existingChannelPoolItem3)) + using (await Semaphore.LockAsync()) { - poolItem = existingChannelPoolItem3; + if (Channels.TryGetValue(channelName, out var currentChannelPoolItem) && + ReferenceEquals(currentChannelPoolItem, poolItem)) + { + staleItem = poolItem; + Channels.TryRemove(channelName, out _); + + poolItem = new ChannelPoolItem(await CreateChannelAsync(channelName, connectionName)); + Channels.TryAdd(channelName, poolItem); + } + else if (currentChannelPoolItem != null) + { + poolItem = currentChannelPoolItem; + } + else + { + poolItem = new ChannelPoolItem(await CreateChannelAsync(channelName, connectionName)); + Channels.TryAdd(channelName, poolItem); + } } - else + } + finally + { + if (staleItem != null) { - poolItem = new ChannelPoolItem(await CreateChannelAsync(channelName, connectionName)); - Channels.TryAdd(channelName, poolItem); + staleItem.WaitIfInUse(TotalDisposeWaitDuration); + await staleItem.DisposeAsync(); } } - - poolItem.Acquire(); } + poolItem.Acquire(); + return new ChannelAccessor( poolItem.Channel, channelName, diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/ITemplateRenderingEngine.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/ITemplateRenderingEngine.cs index e100825011..e9f74595c8 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/ITemplateRenderingEngine.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/ITemplateRenderingEngine.cs @@ -9,6 +9,13 @@ public interface ITemplateRenderingEngine { string Name { get; } + /// + /// True when this engine restricts templates to a DSL without direct .NET + /// interop (e.g. Scriban). False when templates compile to fully-trusted + /// .NET code (e.g. Razor). + /// + bool IsSandboxed { get; } + /// /// Renders a text template. /// diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs index 62c8dee43c..7e5e10d86c 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs @@ -8,6 +8,8 @@ public abstract class TemplateRenderingEngineBase : ITemplateRenderingEngine { public abstract string Name { get; } + public virtual bool IsSandboxed => false; + protected readonly ITemplateDefinitionManager TemplateDefinitionManager; protected readonly ITemplateContentProvider TemplateContentProvider; protected readonly IStringLocalizerFactory StringLocalizerFactory; diff --git a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine.cs b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine.cs index 485ca44121..b6c120fe9e 100644 --- a/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine.cs +++ b/framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine.cs @@ -17,6 +17,8 @@ public class RazorTemplateRenderingEngine : TemplateRenderingEngineBase, ITransi public const string EngineName = "Razor"; public override string Name => EngineName; + public override bool IsSandboxed => false; + protected readonly IServiceScopeFactory ServiceScopeFactory; public RazorTemplateRenderingEngine( diff --git a/framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine.cs b/framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine.cs index 7a5ac79592..9b7b3393a6 100644 --- a/framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine.cs +++ b/framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections.Generic; +using System.Reflection; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.Localization; @@ -14,6 +15,8 @@ public class ScribanTemplateRenderingEngine : TemplateRenderingEngineBase, ITran public const string EngineName = "Scriban"; public override string Name => EngineName; + public override bool IsSandboxed => true; + public ScribanTemplateRenderingEngine( ITemplateDefinitionManager templateDefinitionManager, ITemplateContentProvider templateContentProvider, @@ -118,7 +121,10 @@ public class ScribanTemplateRenderingEngine : TemplateRenderingEngineBase, ITran Dictionary globalContext, object? model = null) { - var context = new TemplateContext(); + var context = new TemplateContext + { + MemberFilter = IsMemberAllowed + }; var scriptObject = new ScriptObject(); @@ -140,4 +146,12 @@ public class ScribanTemplateRenderingEngine : TemplateRenderingEngineBase, ITran return context; } + + /// + /// Scriban member filter: only public properties on imported objects are exposed. + /// + protected virtual bool IsMemberAllowed(MemberInfo member) + { + return member is PropertyInfo; + } } diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo.Abp.UI.Navigation.csproj b/framework/src/Volo.Abp.UI.Navigation/Volo.Abp.UI.Navigation.csproj index c5bc49a2bb..c01959bb61 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo.Abp.UI.Navigation.csproj +++ b/framework/src/Volo.Abp.UI.Navigation/Volo.Abp.UI.Navigation.csproj @@ -23,6 +23,7 @@ + diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/AbpUiNavigationModule.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/AbpUiNavigationModule.cs index 9074943396..70fb9d978d 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/AbpUiNavigationModule.cs +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/AbpUiNavigationModule.cs @@ -1,4 +1,5 @@ using Volo.Abp.Authorization; +using Volo.Abp.Features; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; @@ -7,7 +8,7 @@ using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.UI.Navigation; -[DependsOn(typeof(AbpUiModule), typeof(AbpAuthorizationModule), typeof(AbpMultiTenancyModule))] +[DependsOn(typeof(AbpUiModule), typeof(AbpAuthorizationModule), typeof(AbpFeaturesModule), typeof(AbpMultiTenancyModule))] public class AbpUiNavigationModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuManager.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuManager.cs index f1d8ae22d7..49cbfaae03 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuManager.cs +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuManager.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Volo.Abp.Authorization.Permissions; using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; using Volo.Abp.SimpleStateChecking; namespace Volo.Abp.UI.Navigation; @@ -82,6 +83,7 @@ public class MenuManager : IMenuManager, ITransientDependency using (var scope = ServiceScopeFactory.CreateScope()) { using (RequirePermissionsSimpleBatchStateChecker.Use(new RequirePermissionsSimpleBatchStateChecker())) + using (RequireFeaturesSimpleBatchStateChecker.Use(new RequireFeaturesSimpleBatchStateChecker())) { var context = new MenuConfigurationContext(menu, scope.ServiceProvider); diff --git a/framework/test/Volo.Abp.AspNetCore.Components.Web.Theming.Tests/Volo.Abp.AspNetCore.Components.Web.Theming.Tests.abppkg b/framework/test/Volo.Abp.AspNetCore.Components.Web.Theming.Tests/Volo.Abp.AspNetCore.Components.Web.Theming.Tests.abppkg new file mode 100644 index 0000000000..64c1552e37 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Components.Web.Theming.Tests/Volo.Abp.AspNetCore.Components.Web.Theming.Tests.abppkg @@ -0,0 +1,3 @@ +{ + "role": "lib.test" +} diff --git a/framework/test/Volo.Abp.AspNetCore.Components.Web.Theming.Tests/Volo.Abp.AspNetCore.Components.Web.Theming.Tests.csproj b/framework/test/Volo.Abp.AspNetCore.Components.Web.Theming.Tests/Volo.Abp.AspNetCore.Components.Web.Theming.Tests.csproj new file mode 100644 index 0000000000..4e317e99ce --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Components.Web.Theming.Tests/Volo.Abp.AspNetCore.Components.Web.Theming.Tests.csproj @@ -0,0 +1,17 @@ + + + + + + net10.0 + + enable + + + + + + + + + diff --git a/framework/test/Volo.Abp.AspNetCore.Components.Web.Theming.Tests/Volo/Abp/AspNetCore/Components/Web/Theming/Bundling/ComponentBundleUrlBuilder_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Components.Web.Theming.Tests/Volo/Abp/AspNetCore/Components/Web/Theming/Bundling/ComponentBundleUrlBuilder_Tests.cs new file mode 100644 index 0000000000..a13abafe97 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Components.Web.Theming.Tests/Volo/Abp/AspNetCore/Components/Web/Theming/Bundling/ComponentBundleUrlBuilder_Tests.cs @@ -0,0 +1,119 @@ +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Volo.Abp.AspNetCore.Components.Web.Theming.Bundling; + +public class ComponentBundleUrlBuilder_Tests +{ + private readonly IComponentBundleUrlBuilder _builder = new ComponentBundleUrlBuilder(); + + [Fact] + public async Task Should_Return_FileName_When_No_PathBase_Available() + { + (await _builder.BuildAsync("/__bundles/Global.css", appBasePath: null, navigationBaseUri: null)) + .ShouldBe("/__bundles/Global.css"); + } + + [Fact] + public async Task Should_Return_FileName_When_NavigationBaseUri_Has_Root_PathBase() + { + (await _builder.BuildAsync("/__bundles/Global.css", appBasePath: null, navigationBaseUri: "https://localhost/")) + .ShouldBe("/__bundles/Global.css"); + } + + [Fact] + public async Task Should_Use_Explicit_AppBasePath_Over_NavigationBaseUri() + { + (await _builder.BuildAsync("/__bundles/Global.css", appBasePath: "/explicit", navigationBaseUri: "https://localhost/from-nav/")) + .ShouldBe("/explicit/__bundles/Global.css"); + } + + [Fact] + public async Task Should_Resolve_PathBase_From_NavigationBaseUri() + { + (await _builder.BuildAsync("/__bundles/Global.css", appBasePath: null, navigationBaseUri: "https://localhost/foo/")) + .ShouldBe("/foo/__bundles/Global.css"); + } + + [Fact] + public async Task Should_Resolve_PathBase_From_NavigationBaseUri_When_AppBasePath_Empty() + { + (await _builder.BuildAsync("/__bundles/Global.css", appBasePath: "", navigationBaseUri: "https://localhost/foo/")) + .ShouldBe("/foo/__bundles/Global.css"); + } + + [Fact] + public async Task Should_Handle_Nested_PathBase() + { + (await _builder.BuildAsync("/__bundles/Global.css", appBasePath: null, navigationBaseUri: "https://localhost/foo/bar/")) + .ShouldBe("/foo/bar/__bundles/Global.css"); + } + + [Fact] + public async Task Should_Normalize_AppBasePath_Without_Trailing_Slash() + { + (await _builder.BuildAsync("/__bundles/Global.css", appBasePath: "/foo", navigationBaseUri: null)) + .ShouldBe("/foo/__bundles/Global.css"); + } + + [Fact] + public async Task Should_Not_Duplicate_Leading_Slash() + { + (await _builder.BuildAsync("/__bundles/Global.css", appBasePath: "/foo/", navigationBaseUri: null)) + .ShouldBe("/foo/__bundles/Global.css"); + } + + [Fact] + public async Task Should_Handle_FileName_Without_Leading_Slash() + { + (await _builder.BuildAsync("__bundles/Global.css", appBasePath: "/foo", navigationBaseUri: null)) + .ShouldBe("/foo/__bundles/Global.css"); + } + + [Fact] + public async Task Should_Return_FileName_When_NavigationBaseUri_Is_Invalid() + { + (await _builder.BuildAsync("/__bundles/Global.css", appBasePath: null, navigationBaseUri: "not-a-uri")) + .ShouldBe("/__bundles/Global.css"); + } + + [Fact] + public async Task Should_Throw_When_FileName_Is_Null() + { + await Should.ThrowAsync(async () => + await _builder.BuildAsync(null!, appBasePath: "/foo", navigationBaseUri: null)); + } + + [Theory] + [InlineData("https://cdn.example.com/foo.css")] + [InlineData("http://cdn.example.com/foo.css")] + [InlineData("//cdn.example.com/foo.css")] + [InlineData("data:text/css;base64,Zm9vIA==")] + public async Task Should_Not_Prefix_External_Urls(string externalUrl) + { + (await _builder.BuildAsync(externalUrl, appBasePath: "/foo", navigationBaseUri: "https://localhost/foo/")) + .ShouldBe(externalUrl); + } + + [Fact] + public async Task Should_Treat_Whitespace_AppBasePath_As_Not_Provided() + { + (await _builder.BuildAsync("/__bundles/Global.css", appBasePath: " ", navigationBaseUri: "https://localhost/foo/")) + .ShouldBe("/foo/__bundles/Global.css"); + } + + [Fact] + public async Task Should_Be_Idempotent_For_Already_Prefixed_FileName() + { + (await _builder.BuildAsync("/foo/__bundles/Global.css", appBasePath: "/foo", navigationBaseUri: null)) + .ShouldBe("/foo/__bundles/Global.css"); + } + + [Fact] + public async Task Should_Be_Idempotent_When_PathBase_Resolved_From_NavigationBaseUri() + { + (await _builder.BuildAsync("/foo/__bundles/Global.css", appBasePath: null, navigationBaseUri: "https://localhost/foo/")) + .ShouldBe("/foo/__bundles/Global.css"); + } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Tests/Volo/Abp/AspNetCore/Mvc/UI/Bootstrap/TagHelpers/Form/AbpInputTagHelperService_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Tests/Volo/Abp/AspNetCore/Mvc/UI/Bootstrap/TagHelpers/Form/AbpInputTagHelperService_Tests.cs index 33e2e35bec..31ced995cb 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Tests/Volo/Abp/AspNetCore/Mvc/UI/Bootstrap/TagHelpers/Form/AbpInputTagHelperService_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Tests/Volo/Abp/AspNetCore/Mvc/UI/Bootstrap/TagHelpers/Form/AbpInputTagHelperService_Tests.cs @@ -1,9 +1,14 @@ +#nullable enable + using System.Collections.Generic; +using System.Linq; +using System.Reflection; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.Localization; using Shouldly; using Xunit; @@ -45,6 +50,109 @@ public class AbpInputTagHelperService_Tests service.LastGroupHtml.ShouldContain("mb-3"); } + [Fact] + public async Task Info_text_should_be_rendered_as_div_with_form_text_class() + { + var service = new TestAbpInputTagHelperServiceForInfo(); + var tagHelper = new AbpInputTagHelper(service) + { + AspFor = CreateModelExpression(), + InfoText = "Description" + }; + + var output = CreateOutput(); + + await tagHelper.ProcessAsync(CreateContext(), output); + + service.LastGroupHtml.ShouldContain("
a.Name == "aria-describedby").ToList(); + ariaDescribedby.Count.ShouldBe(1); + ariaDescribedby[0].Value.ToString().ShouldBe("TestInputInfoText"); + } + private static TagHelperContext CreateContext() { return new TagHelperContext( @@ -69,6 +177,21 @@ public class AbpInputTagHelperService_Tests metadataProvider.GetModelExplorerForType(typeof(string), null)); } + private static ModelExpression CreateModelExpressionWithInputInfoText() + { + var metadataProvider = new EmptyModelMetadataProvider(); + var modelExplorer = metadataProvider + .GetModelExplorerForType(typeof(TestModelWithInputInfoText), null) + .GetExplorerForProperty(nameof(TestModelWithInputInfoText.TestInput)); + return new ModelExpression(nameof(TestModelWithInputInfoText.TestInput), modelExplorer); + } + + private class TestModelWithInputInfoText + { + [InputInfoText("Description from attribute")] + public string TestInput { get; set; } = string.Empty; + } + private sealed class TestAbpInputTagHelperService : AbpInputTagHelperService { private readonly string _inputTypeName; @@ -119,4 +242,75 @@ public class AbpInputTagHelperService_Tests suppress = false; } } + + private sealed class TestAbpInputTagHelperServiceForInfo : AbpInputTagHelperService + { + private readonly string? _inputId; + private readonly string? _existingAriaDescribedby; + + public string LastGroupHtml { get; private set; } = string.Empty; + + public TagHelperOutput? LastInputTag { get; private set; } + + public TestAbpInputTagHelperServiceForInfo(string? inputId = "TestInput", string? existingAriaDescribedby = null) + : base(null!, HtmlEncoder.Default, new FakeTagHelperLocalizer()) + { + _inputId = inputId; + _existingAriaDescribedby = existingAriaDescribedby; + } + + protected override Task<(TagHelperOutput, bool)> GetInputTagHelperOutputAsync(TagHelperContext context, TagHelperOutput output) + { + var attributes = new TagHelperAttributeList + { + { "type", "text" }, + { "class", "form-control" } + }; + if (!string.IsNullOrEmpty(_inputId)) + { + attributes.Add("id", _inputId); + } + if (!string.IsNullOrEmpty(_existingAriaDescribedby)) + { + attributes.Add("aria-describedby", _existingAriaDescribedby); + } + + LastInputTag = new TagHelperOutput( + "input", + attributes, + (_, _) => Task.FromResult(new DefaultTagHelperContent())) + { + TagMode = TagMode.SelfClosing + }; + + AddInfoTextId(LastInputTag); + + return Task.FromResult((LastInputTag, false)); + } + + protected override Task GetLabelAsHtmlAsync(TagHelperContext context, TagHelperOutput output, TagHelperOutput inputTag, bool isCheckbox) + { + return Task.FromResult(string.Empty); + } + + protected override Task GetValidationAsHtmlAsync(TagHelperContext context, TagHelperOutput output, TagHelperOutput inputTag) + { + return Task.FromResult(string.Empty); + } + + protected override void AddGroupToFormGroupContents(TagHelperContext context, string propertyName, string html, int order, out bool suppress) + { + LastGroupHtml = html; + suppress = false; + } + } + + private sealed class FakeTagHelperLocalizer : IAbpTagHelperLocalizer + { + public string GetLocalizedText(string text, ModelExplorer explorer) => text; + + public IStringLocalizer? GetLocalizerOrNull(ModelExplorer explorer) => null; + + public IStringLocalizer? GetLocalizerOrNull(Assembly assembly) => null; + } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Tests/Volo/Abp/AspNetCore/Mvc/UI/Bootstrap/TagHelpers/Form/AbpSelectTagHelperService_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Tests/Volo/Abp/AspNetCore/Mvc/UI/Bootstrap/TagHelpers/Form/AbpSelectTagHelperService_Tests.cs new file mode 100644 index 0000000000..9c2e90ca25 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.UI.Tests/Volo/Abp/AspNetCore/Mvc/UI/Bootstrap/TagHelpers/Form/AbpSelectTagHelperService_Tests.cs @@ -0,0 +1,246 @@ +#nullable enable + +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.Localization; +using Shouldly; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form; + +public class AbpSelectTagHelperService_Tests +{ + [Fact] + public async Task Info_text_should_be_rendered_as_div_with_form_text_class() + { + var service = new TestAbpSelectTagHelperService(); + var tagHelper = new AbpSelectTagHelper(service) + { + AspFor = CreateModelExpression(), + InfoText = "Description" + }; + + var output = CreateOutput(); + + await tagHelper.ProcessAsync(CreateContext(), output); + + service.LastGroupHtml.ShouldContain("
a.Name == "aria-describedby").ToList(); + ariaDescribedby.Count.ShouldBe(1); + ariaDescribedby[0].Value.ToString().ShouldBe("TestSelectInfoText"); + } + + private static TagHelperContext CreateContext() + { + return new TagHelperContext( + new TagHelperAttributeList(), + new Dictionary(), + "test"); + } + + private static TagHelperOutput CreateOutput() + { + return new TagHelperOutput( + "abp-select", + new TagHelperAttributeList(), + (_, _) => Task.FromResult(new DefaultTagHelperContent())); + } + + private static ModelExpression CreateModelExpression() + { + var metadataProvider = new EmptyModelMetadataProvider(); + return new ModelExpression( + "TestSelect", + metadataProvider.GetModelExplorerForType(typeof(string), null)); + } + + private static ModelExpression CreateModelExpressionWithInputInfoText() + { + var metadataProvider = new EmptyModelMetadataProvider(); + var modelExplorer = metadataProvider + .GetModelExplorerForType(typeof(TestModelWithInputInfoText), null) + .GetExplorerForProperty(nameof(TestModelWithInputInfoText.TestSelect)); + return new ModelExpression(nameof(TestModelWithInputInfoText.TestSelect), modelExplorer); + } + + private class TestModelWithInputInfoText + { + [InputInfoText("Description from attribute")] + public string TestSelect { get; set; } = string.Empty; + } + + private sealed class TestAbpSelectTagHelperService : AbpSelectTagHelperService + { + private readonly string? _selectId; + private readonly string? _existingAriaDescribedby; + + public string LastGroupHtml { get; private set; } = string.Empty; + + public TagHelperOutput? LastSelectTag { get; private set; } + + public TestAbpSelectTagHelperService(string? selectId = "TestSelect", string? existingAriaDescribedby = null) + : base(null!, HtmlEncoder.Default, new FakeTagHelperLocalizer(), null!, null!) + { + _selectId = selectId; + _existingAriaDescribedby = existingAriaDescribedby; + } + + protected override Task GetSelectTagAsync(TagHelperContext context, TagHelperOutput output, TagHelperContent childContent) + { + var attributes = new TagHelperAttributeList(); + if (!string.IsNullOrEmpty(_selectId)) + { + attributes.Add("id", _selectId); + } + if (!string.IsNullOrEmpty(_existingAriaDescribedby)) + { + attributes.Add("aria-describedby", _existingAriaDescribedby); + } + + LastSelectTag = new TagHelperOutput( + "select", + attributes, + (_, _) => Task.FromResult(new DefaultTagHelperContent())) + { + TagMode = TagMode.StartTagAndEndTag + }; + + AddInfoTextId(LastSelectTag); + + return Task.FromResult(LastSelectTag); + } + + protected override Task GetLabelAsHtmlAsync(TagHelperContext context, TagHelperOutput output, TagHelperOutput selectTag) + { + return Task.FromResult(string.Empty); + } + + protected override Task GetValidationAsHtmlAsync(TagHelperContext context, TagHelperOutput output, TagHelperOutput selectTag) + { + return Task.FromResult(string.Empty); + } + + protected override void AddGroupToFormGroupContents(TagHelperContext context, string propertyName, string html, int order, out bool suppress) + { + LastGroupHtml = html; + suppress = false; + } + } + + private sealed class FakeTagHelperLocalizer : IAbpTagHelperLocalizer + { + public string GetLocalizedText(string text, ModelExplorer explorer) => text; + + public IStringLocalizer? GetLocalizerOrNull(ModelExplorer explorer) => null; + + public IStringLocalizer? GetLocalizerOrNull(Assembly assembly) => null; + } +} diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo.Abp.Authorization.Tests.csproj b/framework/test/Volo.Abp.Authorization.Tests/Volo.Abp.Authorization.Tests.csproj index 9a7119a6a9..f1deb2761a 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo.Abp.Authorization.Tests.csproj +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo.Abp.Authorization.Tests.csproj @@ -13,6 +13,7 @@ + diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs index 15ec4e0641..70a3bf1aa0 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AbpAuthorizationTestModule.cs @@ -5,6 +5,7 @@ using Volo.Abp.Authorization.TestServices.Resources; using Volo.Abp.Autofac; using Volo.Abp.DynamicProxy; using Volo.Abp.ExceptionHandling; +using Volo.Abp.Features; using Volo.Abp.Modularity; namespace Volo.Abp.Authorization; @@ -12,6 +13,7 @@ namespace Volo.Abp.Authorization; [DependsOn(typeof(AbpAutofacModule))] [DependsOn(typeof(AbpAuthorizationModule))] [DependsOn(typeof(AbpExceptionHandlingModule))] +[DependsOn(typeof(AbpFeaturesModule))] public class AbpAuthorizationTestModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/Authorization_Tests.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/Authorization_Tests.cs index a32939fa53..33058e5468 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/Authorization_Tests.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/Authorization_Tests.cs @@ -69,7 +69,8 @@ public class Authorization_Tests : AuthorizationTestBase [Fact] public async Task Should_Permission_Definition_GetGroup() { - (await _permissionDefinitionManager.GetGroupsAsync()).Count.ShouldBe(1); + var groups = await _permissionDefinitionManager.GetGroupsAsync(); + groups.ShouldContain(g => g.Name == "TestGroup"); } [Fact] diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/RequirePermissionsSimpleBatchStateChecker_Tests.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/RequirePermissionsSimpleBatchStateChecker_Tests.cs index d52718fd50..6bcc214623 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/RequirePermissionsSimpleBatchStateChecker_Tests.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/RequirePermissionsSimpleBatchStateChecker_Tests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Shouldly; using Volo.Abp.Authorization.Permissions; @@ -56,6 +57,21 @@ public class RequirePermissionsSimpleBatchStateChecker_Tests : AuthorizationTest result[myStateEntities[3]].ShouldBeTrue(); } + [Fact] + public async Task Current_Should_Not_Be_Null_In_Fresh_ExecutionContext() + { + _ = RequirePermissionsSimpleBatchStateChecker.Current; + + Task> task; + using (ExecutionContext.SuppressFlow()) + { + task = Task.Run(() => RequirePermissionsSimpleBatchStateChecker.Current); + } + + var current = await task; + current.ShouldNotBeNull(); + } + class MyStateEntity : IHasSimpleStateCheckers { public List> StateCheckers { get; } @@ -75,4 +91,14 @@ public class RequirePermissionsSimpleBatchStateChecker_Tests : AuthorizationTest StateCheckers = new List>(); } } + + class MyStateEntity3 : IHasSimpleStateCheckers + { + public List> StateCheckers { get; } + + public MyStateEntity3() + { + StateCheckers = new List>(); + } + } } diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/StaticPermissionDefinitionStore_Tests.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/StaticPermissionDefinitionStore_Tests.cs index 19ab829ba1..a2d7f43b13 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/StaticPermissionDefinitionStore_Tests.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/StaticPermissionDefinitionStore_Tests.cs @@ -1,7 +1,10 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using Shouldly; using Volo.Abp.Authorization.Permissions; using Volo.Abp.Authorization.TestServices.Resources; +using Volo.Abp.StaticDefinitions; using Xunit; namespace Volo.Abp.Authorization; @@ -84,4 +87,37 @@ public class StaticPermissionDefinitionStore_Tests : AuthorizationTestBase permissions.ShouldContain(x => x.Name == "MyResourcePermission7" && x.ResourceName == TestEntityResource2.ResourceName); } + + [Fact] + public async Task Should_Rebuild_Definitions_In_Fresh_ExecutionContext_After_Cache_Clear() + { + var groupCache = GetRequiredService, List)>>(); + var definitionCache = GetRequiredService>>(); + + await groupCache.ClearAsync(); + await definitionCache.ClearAsync(); + + // Touch the type initializer (if any) on the test ExecutionContext first, mirroring + // the production scenario where startup pre-warms it on a different ExecutionContext. + _ = await _store.GetOrNullAsync("FeatureGatedPermission"); + + await groupCache.ClearAsync(); + await definitionCache.ClearAsync(); + + PermissionDefinition permission = null; + Task task; + using (ExecutionContext.SuppressFlow()) + { + task = Task.Run(async () => + { + permission = await _store.GetOrNullAsync("FeatureGatedPermission"); + }); + } + await task; + + permission.ShouldNotBeNull(); + permission.Name.ShouldBe("FeatureGatedPermission"); + } } diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/FeatureGatedTestPermissionDefinitionProvider.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/FeatureGatedTestPermissionDefinitionProvider.cs new file mode 100644 index 0000000000..0fcfe7c259 --- /dev/null +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/TestServices/FeatureGatedTestPermissionDefinitionProvider.cs @@ -0,0 +1,15 @@ +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Features; + +namespace Volo.Abp.Authorization.TestServices; + +public class FeatureGatedTestPermissionDefinitionProvider : PermissionDefinitionProvider +{ + public override void Define(IPermissionDefinitionContext context) + { + var group = context.AddGroup("FeatureGatedTestGroup"); + + group.AddPermission("FeatureGatedPermission") + .RequireFeatures("FeatureGatedTestFeature"); + } +} diff --git a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundWorkers/DynamicBackgroundWorkerManager_Tests.cs b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundWorkers/DynamicBackgroundWorkerManager_Tests.cs index a61c99fdc1..955b2104dd 100644 --- a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundWorkers/DynamicBackgroundWorkerManager_Tests.cs +++ b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundWorkers/DynamicBackgroundWorkerManager_Tests.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Shouldly; +using Volo.Abp; using Volo.Abp.BackgroundJobs; using Volo.Abp.BackgroundWorkers; using Xunit; @@ -20,6 +21,13 @@ public class DynamicBackgroundWorkerManager_Tests : BackgroundJobsTestBase _dynamicWorkerManager = GetRequiredService(); } + [Fact] + public void Should_Report_Provider_Capabilities_Using_Marker_Interfaces() + { + (_dynamicWorkerManager is ISupportsRuntimeRegistration).ShouldBeTrue(); + (_dynamicWorkerManager is ISupportsCronScheduling).ShouldBeFalse(); + } + [Fact] public async Task Should_Register_Dynamic_Worker() { @@ -235,6 +243,49 @@ public class DynamicBackgroundWorkerManager_Tests : BackgroundJobsTestBase }); } + [Fact] + public async Task Should_Throw_When_CronExpression_Is_Set() + { + var workerName = "dynamic-worker-" + Guid.NewGuid(); + + await Assert.ThrowsAsync(async () => + { + await _dynamicWorkerManager.AddAsync( + workerName, + new DynamicBackgroundWorkerSchedule + { + Period = 1000, + CronExpression = "0 */5 * * * *" + }, + (_, _) => Task.CompletedTask + ); + }); + } + + [Fact] + public async Task Should_Throw_When_CronExpression_Is_Set_On_UpdateSchedule() + { + var workerName = "dynamic-worker-" + Guid.NewGuid(); + + await _dynamicWorkerManager.AddAsync( + workerName, + new DynamicBackgroundWorkerSchedule { Period = 1000 }, + (_, _) => Task.CompletedTask + ); + + await Assert.ThrowsAsync(async () => + { + await _dynamicWorkerManager.UpdateScheduleAsync( + workerName, + new DynamicBackgroundWorkerSchedule + { + Period = 1000, + CronExpression = "0 */5 * * * *" + } + ); + }); + } + [Fact] public async Task Should_Continue_Running_After_Handler_Throws_Exception() { diff --git a/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundWorkers/InMemoryDynamicBackgroundWorker_Registration_Tests.cs b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundWorkers/InMemoryDynamicBackgroundWorker_Registration_Tests.cs new file mode 100644 index 0000000000..677efbbc5e --- /dev/null +++ b/framework/test/Volo.Abp.BackgroundJobs.Tests/Volo/Abp/BackgroundWorkers/InMemoryDynamicBackgroundWorker_Registration_Tests.cs @@ -0,0 +1,37 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Xunit; + +namespace Volo.Abp.BackgroundWorkers; + +public class InMemoryDynamicBackgroundWorker_Registration_Tests +{ + // Reproduces the original failure: ASP.NET Core in Development enables ValidateOnBuild, + // and InMemoryDynamicBackgroundWorker has a `string workerName` constructor parameter + // that DI cannot resolve. Without [DisableConventionalRegistration] this throws: + // Unable to resolve service for type 'System.String' while attempting to activate + // 'Volo.Abp.BackgroundWorkers.InMemoryDynamicBackgroundWorker'. + [Fact] + public async Task BuildServiceProvider_With_ValidateOnBuild_Should_Not_Throw() + { + using var application = await AbpApplicationFactory.CreateAsync(); + + var act = () => application.Services.BuildServiceProvider( + new ServiceProviderOptions { ValidateOnBuild = true }); + + act.ShouldNotThrow(); + } + + // Verifies the fix: InMemoryDynamicBackgroundWorker is created on demand by + // DefaultDynamicBackgroundWorkerManager (`new InMemoryDynamicBackgroundWorker(...)`) + // and must stay out of the conventional registration loop. + [Fact] + public async Task InMemoryDynamicBackgroundWorker_Should_Not_Be_Registered_As_Service() + { + using var application = await AbpApplicationFactory.CreateAsync(); + + application.Services.ShouldNotContain(d => d.ServiceType == typeof(InMemoryDynamicBackgroundWorker)); + } +} diff --git a/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/ProjectBuilding/RemoteServiceExceptionHandler_Tests.cs b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/ProjectBuilding/RemoteServiceExceptionHandler_Tests.cs new file mode 100644 index 0000000000..32e60e9605 --- /dev/null +++ b/framework/test/Volo.Abp.Cli.Core.Tests/Volo/Abp/Cli/ProjectBuilding/RemoteServiceExceptionHandler_Tests.cs @@ -0,0 +1,193 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Cli.ProjectBuilding; +using Volo.Abp.Json; +using Volo.Abp.Json.SystemTextJson; +using Xunit; + +namespace Volo.Abp.Cli.ProjectBuilding; + +public class RemoteServiceExceptionHandler_Tests +{ + private readonly RemoteServiceExceptionHandler _handler; + + public RemoteServiceExceptionHandler_Tests() + { + var jsonSerializer = new AbpSystemTextJsonSerializer( + Microsoft.Extensions.Options.Options.Create(new AbpSystemTextJsonSerializerOptions()) + ); + _handler = new RemoteServiceExceptionHandler(jsonSerializer); + } + + [Fact] + public async Task EnsureSuccessfulHttpResponseAsync_Should_Not_Throw_On_Success() + { + var response = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{}") + }; + + await _handler.EnsureSuccessfulHttpResponseAsync(response); + } + + [Fact] + public async Task EnsureSuccessfulHttpResponseAsync_Should_Not_Throw_When_Response_Is_Null() + { + await _handler.EnsureSuccessfulHttpResponseAsync(null); + } + + [Fact] + public async Task Should_Wrap_Html_Body_Without_Json_Parse_Exception() + { + var response = new HttpResponseMessage(HttpStatusCode.Forbidden) + { + ReasonPhrase = "Forbidden", + Content = new StringContent("Forbidden", System.Text.Encoding.UTF8, "text/html") + }; + + var exception = await Should.ThrowAsync(() => _handler.EnsureSuccessfulHttpResponseAsync(response)); + + exception.Message.ShouldContain("403-Forbidden"); + exception.Message.ShouldNotContain("invalid start of a value"); + } + + [Fact] + public async Task Should_Surface_Server_Error_Message_When_Body_Is_Valid_Json() + { + var response = new HttpResponseMessage(HttpStatusCode.Forbidden) + { + ReasonPhrase = "Forbidden", + Content = new StringContent( + "{\"error\":{\"code\":\"LicenseExpired\",\"message\":\"Your ABP license has expired.\"}}", + System.Text.Encoding.UTF8, + "application/json") + }; + + var exception = await Should.ThrowAsync(() => _handler.EnsureSuccessfulHttpResponseAsync(response)); + + exception.Message.ShouldContain("403-Forbidden"); + exception.Message.ShouldContain("LicenseExpired"); + exception.Message.ShouldContain("Your ABP license has expired."); + } + + [Fact] + public async Task Should_Surface_Server_Error_Message_For_5xx_With_Json_Body() + { + var response = new HttpResponseMessage(HttpStatusCode.InternalServerError) + { + ReasonPhrase = "Internal Server Error", + Content = new StringContent( + "{\"error\":{\"code\":\"InternalError\",\"message\":\"Database connection failed\"}}", + System.Text.Encoding.UTF8, + "application/json") + }; + + var exception = await Should.ThrowAsync(() => _handler.EnsureSuccessfulHttpResponseAsync(response)); + + exception.Message.ShouldContain("500-Internal Server Error"); + exception.Message.ShouldContain("InternalError"); + exception.Message.ShouldContain("Database connection failed"); + } + + [Fact] + public async Task GetAbpRemoteServiceErrorAsync_Should_Propagate_OperationCanceledException() + { + var response = new HttpResponseMessage(HttpStatusCode.Forbidden) + { + Content = new CanceledStringContent() + }; + + await Should.ThrowAsync( + () => _handler.GetAbpRemoteServiceErrorAsync(response) + ); + } + + [Fact] + public async Task GetAbpRemoteServiceErrorAsync_Should_Return_Null_For_Html_Body() + { + var response = new HttpResponseMessage(HttpStatusCode.Forbidden) + { + Content = new StringContent("", System.Text.Encoding.UTF8, "text/html") + }; + + var result = await _handler.GetAbpRemoteServiceErrorAsync(response); + + result.ShouldBeNull(); + } + + [Fact] + public async Task GetAbpRemoteServiceErrorAsync_Should_Return_Null_For_Newtonsoft_JsonException() + { + var handler = new RemoteServiceExceptionHandler( + new ThrowingJsonSerializer(new Newtonsoft.Json.JsonException("Invalid JSON")) + ); + var response = new HttpResponseMessage(HttpStatusCode.Forbidden) + { + Content = new StringContent("{}") + }; + + var result = await handler.GetAbpRemoteServiceErrorAsync(response); + + result.ShouldBeNull(); + } + + [Fact] + public async Task GetAbpRemoteServiceErrorAsync_Should_Propagate_Non_Json_Exceptions() + { + var handler = new RemoteServiceExceptionHandler( + new ThrowingJsonSerializer(new InvalidOperationException("Unexpected serializer failure")) + ); + var response = new HttpResponseMessage(HttpStatusCode.Forbidden) + { + Content = new StringContent("{}") + }; + + var exception = await Should.ThrowAsync( + () => handler.GetAbpRemoteServiceErrorAsync(response) + ); + + exception.Message.ShouldBe("Unexpected serializer failure"); + } + + private class CanceledStringContent : HttpContent + { + protected override Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext context) + { + throw new OperationCanceledException(); + } + + protected override bool TryComputeLength(out long length) + { + length = 0; + return false; + } + } + + private class ThrowingJsonSerializer : IJsonSerializer + { + private readonly Exception _exception; + + public ThrowingJsonSerializer(Exception exception) + { + _exception = exception; + } + + public string Serialize(object obj, bool camelCase = true, bool indented = false) + { + throw new NotImplementedException(); + } + + public T Deserialize(string jsonString, bool camelCase = true) + { + throw _exception; + } + + public object Deserialize(Type type, string jsonString, bool camelCase = true) + { + throw _exception; + } + } +} diff --git a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/SimpleStateChecking/SimpleStateChecker_AndCombineResults_Tests.cs b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/SimpleStateChecking/SimpleStateChecker_AndCombineResults_Tests.cs new file mode 100644 index 0000000000..6fdc54b940 --- /dev/null +++ b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/SimpleStateChecking/SimpleStateChecker_AndCombineResults_Tests.cs @@ -0,0 +1,101 @@ +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.DependencyInjection; +using Xunit; + +namespace Volo.Abp.SimpleStateChecking; + +public class SimpleStateChecker_AndCombineResults_Tests : SimpleStateCheckerTestBase +{ + [Fact] + public async Task False_Result_From_One_Batch_Checker_Should_Not_Be_Overwritten_By_Later_True_Checker() + { + // Regression test for the bug fixed in SimpleStateCheckerManager.IsEnabledAsync: + // when a state had multiple batch checkers the manager used to OVERWRITE + // result[x.Key] on each checker, so a later "true" silently masked an earlier "false". + // The fix AND-combines results. + // + // To make sure we actually hit the second batch checker (and not the early-return + // `result.Values.All(x => !x)` short-circuit), we use two states: + // - state1: [falseChecker, trueChecker] - the target case + // - state2: [trueChecker] - keeps result.Values not all-false + // so the loop continues to the next checker + + var falseChecker = new AlwaysFalseBatchStateChecker(); + var trueChecker = new AlwaysTrueBatchStateChecker(); + + var state1 = new MyStateEntity(); + state1.AddSimpleStateChecker(falseChecker); + state1.AddSimpleStateChecker(trueChecker); + + var state2 = new MyStateEntity(); + state2.AddSimpleStateChecker(trueChecker); + + var result = await SimpleStateCheckerManager.IsEnabledAsync(new[] { state1, state2 }); + + result[state1].ShouldBeFalse(); // before the fix this was True (bug) + result[state2].ShouldBeTrue(); + } + + [Fact] + public async Task True_Result_From_One_Batch_Checker_Should_Be_AND_Combined_With_Later_False_Checker() + { + // Reverse order: true first then false. Add a second always-true state to keep + // result.Values not all-false after both checkers ran. + var trueChecker = new AlwaysTrueBatchStateChecker(); + var falseChecker = new AlwaysFalseBatchStateChecker(); + + var state1 = new MyStateEntity(); + state1.AddSimpleStateChecker(trueChecker); + state1.AddSimpleStateChecker(falseChecker); + + var state2 = new MyStateEntity(); + state2.AddSimpleStateChecker(trueChecker); + + var result = await SimpleStateCheckerManager.IsEnabledAsync(new[] { state1, state2 }); + + result[state1].ShouldBeFalse(); + result[state2].ShouldBeTrue(); + } + + [Fact] + public async Task All_True_Batch_Checkers_Should_Produce_True() + { + var trueChecker1 = new AlwaysTrueBatchStateChecker(); + var trueChecker2 = new AlwaysTrueBatchStateChecker(); + + var state = new MyStateEntity(); + state.AddSimpleStateChecker(trueChecker1); + state.AddSimpleStateChecker(trueChecker2); + + var result = await SimpleStateCheckerManager.IsEnabledAsync(new[] { state }); + + result[state].ShouldBeTrue(); + } + + public class AlwaysFalseBatchStateChecker : SimpleBatchStateCheckerBase, ITransientDependency + { + public override Task> IsEnabledAsync(SimpleBatchStateCheckerContext context) + { + var result = new SimpleStateCheckerResult(context.States); + foreach (var x in result) + { + result[x.Key] = false; + } + return Task.FromResult(result); + } + } + + public class AlwaysTrueBatchStateChecker : SimpleBatchStateCheckerBase, ITransientDependency + { + public override Task> IsEnabledAsync(SimpleBatchStateCheckerContext context) + { + var result = new SimpleStateCheckerResult(context.States); + foreach (var x in result) + { + result[x.Key] = true; + } + return Task.FromResult(result); + } + } +} diff --git a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/SimpleStateChecking/SimpleStateChecker_BatchSingleScope_Tests.cs b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/SimpleStateChecking/SimpleStateChecker_BatchSingleScope_Tests.cs new file mode 100644 index 0000000000..1edf00230d --- /dev/null +++ b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/SimpleStateChecking/SimpleStateChecker_BatchSingleScope_Tests.cs @@ -0,0 +1,111 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Volo.Abp.SimpleStateChecking; + +/// +/// Tests that batch IsEnabledAsync evaluates non-batch state checkers correctly +/// when reusing a single DI scope (instead of creating N scopes via InternalIsEnabledAsync). +/// +public class SimpleStateChecker_BatchSingleScope_Tests : SimpleStateCheckerTestBase +{ + [Fact] + public async Task Batch_Should_Evaluate_NonBatch_Checkers_Correctly() + { + var enabled = new MyStateEntity + { + CreationTime = DateTime.Parse("2021-01-01", CultureInfo.InvariantCulture) + }; + enabled.AddSimpleStateChecker(new MySimpleStateChecker()); + + var disabled = new MyStateEntity + { + CreationTime = DateTime.Parse("2001-01-01", CultureInfo.InvariantCulture) + }; + disabled.AddSimpleStateChecker(new MySimpleStateChecker()); + + var result = await SimpleStateCheckerManager.IsEnabledAsync(new[] { enabled, disabled }); + + result[enabled].ShouldBeTrue(); + result[disabled].ShouldBeFalse(); + enabled.CheckCount.ShouldBe(1); + disabled.CheckCount.ShouldBe(1); + } + + [Fact] + public async Task Batch_Should_Skip_NonBatch_Check_When_BatchChecker_Already_Disabled() + { + // Entity disabled by batch checker should not have non-batch checker invoked + var entity = new MyStateEntity + { + CreationTime = DateTime.Parse("2001-01-01", CultureInfo.InvariantCulture) // fails batch checker + }; + entity.AddSimpleStateChecker(new MySimpleBatchStateChecker()); + entity.AddSimpleStateChecker(new MySimpleStateChecker()); + + var result = await SimpleStateCheckerManager.IsEnabledAsync(new[] { entity }); + + result[entity].ShouldBeFalse(); + entity.MultipleCheckCount.ShouldBe(1); // batch checker was called + entity.CheckCount.ShouldBe(0); // non-batch checker was NOT called (skipped because batch disabled it) + } + + [Fact] + public async Task Batch_Should_Handle_Mix_Of_Entities_With_And_Without_Checkers() + { + var noChecker = new MyStateEntity + { + CreationTime = DateTime.Parse("2021-01-01", CultureInfo.InvariantCulture) + }; + + var withChecker = new MyStateEntity + { + CreationTime = DateTime.Parse("2021-01-01", CultureInfo.InvariantCulture) + }; + withChecker.AddSimpleStateChecker(new MySimpleStateChecker()); + + var failingChecker = new MyStateEntity + { + CreationTime = DateTime.Parse("2001-01-01", CultureInfo.InvariantCulture) + }; + failingChecker.AddSimpleStateChecker(new MySimpleStateChecker()); + + var result = await SimpleStateCheckerManager.IsEnabledAsync( + new[] { noChecker, withChecker, failingChecker }); + + result[noChecker].ShouldBeTrue(); + result[withChecker].ShouldBeTrue(); + result[failingChecker].ShouldBeFalse(); + + noChecker.CheckCount.ShouldBe(0); + withChecker.CheckCount.ShouldBe(1); + failingChecker.CheckCount.ShouldBe(1); + } + + [Fact] + public async Task Batch_Should_Handle_Large_Number_Of_Entities() + { + var entities = new MyStateEntity[1000]; + for (int i = 0; i < 1000; i++) + { + entities[i] = new MyStateEntity + { + CreationTime = i % 2 == 0 + ? DateTime.Parse("2021-01-01", CultureInfo.InvariantCulture) + : DateTime.Parse("2001-01-01", CultureInfo.InvariantCulture) + }; + entities[i].AddSimpleStateChecker(new MySimpleStateChecker()); + } + + var result = await SimpleStateCheckerManager.IsEnabledAsync(entities); + + for (int i = 0; i < 1000; i++) + { + result[entities[i]].ShouldBe(i % 2 == 0); + entities[i].CheckCount.ShouldBe(1); + } + } +} diff --git a/framework/test/Volo.Abp.Core.Tests/Volo/Abp/SimpleStateChecking/SimpleStateChecker_ScopeIsolation_Tests.cs b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/SimpleStateChecking/SimpleStateChecker_ScopeIsolation_Tests.cs new file mode 100644 index 0000000000..0606dec6e8 --- /dev/null +++ b/framework/test/Volo.Abp.Core.Tests/Volo/Abp/SimpleStateChecking/SimpleStateChecker_ScopeIsolation_Tests.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Concurrent; +using System.Globalization; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Xunit; + +namespace Volo.Abp.SimpleStateChecking; + +/// +/// Probe-based tests that verify: +/// 1. Batch path shares a single DI scope but isolates CachedServiceProvider per state. +/// 2. Single-state path uses separate scopes (original behavior). +/// 3. Nested single-state calls from within batch checkers get their own scope. +/// +public class SimpleStateChecker_ScopeIsolation_Tests : SimpleStateCheckerTestBase +{ + protected override void AfterAddApplication(IServiceCollection services) + { + services.AddScoped(); + services.AddTransient(); + base.AfterAddApplication(services); + } + + [Fact] + public async Task Batch_Should_Share_Scope_But_Isolate_CachedProvider_Across_States() + { + var observations = new ConcurrentDictionary(); + var checker = new ScopeProbeStateChecker(observations); + + var stateA = new MyStateEntity + { + CreationTime = DateTime.Parse("2021-01-01", CultureInfo.InvariantCulture) + }; + stateA.AddSimpleStateChecker(checker); + + var stateB = new MyStateEntity + { + CreationTime = DateTime.Parse("2021-01-01", CultureInfo.InvariantCulture) + }; + stateB.AddSimpleStateChecker(checker); + + var stateC = new MyStateEntity + { + CreationTime = DateTime.Parse("2021-01-01", CultureInfo.InvariantCulture) + }; + stateC.AddSimpleStateChecker(checker); + + await SimpleStateCheckerManager.IsEnabledAsync(new[] { stateA, stateB, stateC }); + + observations.Count.ShouldBe(3); + + // All states in the batch should see the same scoped service (shared DI scope) + observations[stateA].ScopeId.ShouldBe(observations[stateB].ScopeId); + observations[stateB].ScopeId.ShouldBe(observations[stateC].ScopeId); + + // Each state should get its own transient instance (isolated cached provider) + observations[stateA].TransientId.ShouldNotBe(observations[stateB].TransientId); + observations[stateB].TransientId.ShouldNotBe(observations[stateC].TransientId); + } + + [Fact] + public async Task Single_State_Calls_Should_Use_Separate_Scopes() + { + var observations = new ConcurrentDictionary(); + var checker = new ScopeProbeStateChecker(observations); + + var stateA = new MyStateEntity + { + CreationTime = DateTime.Parse("2021-01-01", CultureInfo.InvariantCulture) + }; + stateA.AddSimpleStateChecker(checker); + + var stateB = new MyStateEntity + { + CreationTime = DateTime.Parse("2021-01-01", CultureInfo.InvariantCulture) + }; + stateB.AddSimpleStateChecker(checker); + + await SimpleStateCheckerManager.IsEnabledAsync(stateA); + await SimpleStateCheckerManager.IsEnabledAsync(stateB); + + observations.Count.ShouldBe(2); + + // Single-state calls should each get their own scope + observations[stateA].ScopeId.ShouldNotBe(observations[stateB].ScopeId); + } + + [Fact] + public async Task Nested_Single_State_Call_From_Batch_Checker_Should_Get_Own_Scope() + { + var observations = new ConcurrentDictionary(); + + // This state will be evaluated via a nested single-state call during batch evaluation + var nestedState = new MyStateEntity + { + CreationTime = DateTime.Parse("2021-01-01", CultureInfo.InvariantCulture) + }; + nestedState.AddSimpleStateChecker(new ScopeProbeStateChecker(observations)); + + // This checker triggers a nested IsEnabledAsync(state) during batch evaluation + var nestedCallChecker = new NestedSingleCallChecker( + SimpleStateCheckerManager, nestedState, observations); + + var batchState = new MyStateEntity + { + CreationTime = DateTime.Parse("2021-01-01", CultureInfo.InvariantCulture) + }; + batchState.AddSimpleStateChecker(nestedCallChecker); + + await SimpleStateCheckerManager.IsEnabledAsync(new[] { batchState }); + + observations.Count.ShouldBe(2); + + // The nested single-state call should get its own scope, not the batch scope + observations[batchState].ScopeId.ShouldNotBe(observations[nestedState].ScopeId); + } + + public record Observation(Guid ScopeId, Guid TransientId); + + public class ScopeIdProbe + { + public Guid Id { get; } = Guid.NewGuid(); + } + + public class TransientIdProbe + { + public Guid Id { get; } = Guid.NewGuid(); + } + + public class ScopeProbeStateChecker : ISimpleStateChecker + { + private readonly ConcurrentDictionary _observations; + + public ScopeProbeStateChecker( + ConcurrentDictionary observations) + { + _observations = observations; + } + + public Task IsEnabledAsync(SimpleStateCheckerContext context) + { + var scopeProbe = context.ServiceProvider.GetRequiredService(); + var transientProbe = context.ServiceProvider.GetRequiredService(); + _observations[context.State] = new Observation(scopeProbe.Id, transientProbe.Id); + return Task.FromResult(true); + } + } + + public class NestedSingleCallChecker : ISimpleStateChecker + { + private readonly ISimpleStateCheckerManager _manager; + private readonly MyStateEntity _nestedState; + private readonly ConcurrentDictionary _observations; + + public NestedSingleCallChecker( + ISimpleStateCheckerManager manager, + MyStateEntity nestedState, + ConcurrentDictionary observations) + { + _manager = manager; + _nestedState = nestedState; + _observations = observations; + } + + public async Task IsEnabledAsync(SimpleStateCheckerContext context) + { + var scopeProbe = context.ServiceProvider.GetRequiredService(); + var transientProbe = context.ServiceProvider.GetRequiredService(); + _observations[context.State] = new Observation(scopeProbe.Id, transientProbe.Id); + + // Trigger a nested single-state call during batch evaluation + await _manager.IsEnabledAsync(_nestedState); + + return true; + } + } +} diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo.Abp.EntityFrameworkCore.Tests.csproj b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo.Abp.EntityFrameworkCore.Tests.csproj index acbe19a353..77b1448ded 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo.Abp.EntityFrameworkCore.Tests.csproj +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo.Abp.EntityFrameworkCore.Tests.csproj @@ -17,6 +17,12 @@ + + + + + + diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/EfCoreDatabaseProviderHelper_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/EfCoreDatabaseProviderHelper_Tests.cs new file mode 100644 index 0000000000..f4faf5aacb --- /dev/null +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/EfCoreDatabaseProviderHelper_Tests.cs @@ -0,0 +1,111 @@ +using Microsoft.EntityFrameworkCore; +using Shouldly; +using Xunit; + +namespace Volo.Abp.EntityFrameworkCore; + +public class EfCoreDatabaseProviderHelper_Tests +{ + [Fact] + public void Should_Detect_SqlServer_From_Real_Assembly() + { + var builder = new DbContextOptionsBuilder(); + Microsoft.EntityFrameworkCore.SqlServerDbContextOptionsExtensions.UseSqlServer(builder, "Server=localhost;Database=test"); + using var context = new EmptyDbContext(builder.Options); + EfCoreDatabaseProviderHelper.GetDatabaseProviderOrNull(context.Database.ProviderName) + .ShouldBe(EfCoreDatabaseProvider.SqlServer); + } + + [Fact] + public void Should_Detect_PostgreSql_From_Real_Assembly() + { + var builder = new DbContextOptionsBuilder(); + Microsoft.EntityFrameworkCore.NpgsqlDbContextOptionsBuilderExtensions.UseNpgsql(builder, "Host=localhost;Database=test"); + using var context = new EmptyDbContext(builder.Options); + EfCoreDatabaseProviderHelper.GetDatabaseProviderOrNull(context.Database.ProviderName) + .ShouldBe(EfCoreDatabaseProvider.PostgreSql); + } + + [Fact] + public void Should_Detect_MySql_Pomelo_From_Assembly_Name() + { + var providerName = typeof(Microsoft.EntityFrameworkCore.MySqlDbContextOptionsBuilderExtensions).Assembly.GetName().Name; + EfCoreDatabaseProviderHelper.GetDatabaseProviderOrNull(providerName) + .ShouldBe(EfCoreDatabaseProvider.MySql); + } + + [Fact] + public void Should_Detect_MySql_Oracle_From_Real_Assembly() + { + var builder = new DbContextOptionsBuilder(); + Microsoft.EntityFrameworkCore.MySQLDbContextOptionsExtensions.UseMySQL(builder, "Server=localhost;Database=test"); + using var context = new EmptyDbContext(builder.Options); + EfCoreDatabaseProviderHelper.GetDatabaseProviderOrNull(context.Database.ProviderName) + .ShouldBe(EfCoreDatabaseProvider.MySql); + } + + [Fact] + public void Should_Detect_Oracle_From_Real_Assembly() + { + var builder = new DbContextOptionsBuilder(); + Microsoft.EntityFrameworkCore.OracleDbContextOptionsExtensions.UseOracle(builder, "Data Source=localhost/XE"); + using var context = new EmptyDbContext(builder.Options); + EfCoreDatabaseProviderHelper.GetDatabaseProviderOrNull(context.Database.ProviderName) + .ShouldBe(EfCoreDatabaseProvider.Oracle); + } + + [Fact] + public void Should_Detect_Sqlite_From_Real_Assembly() + { + var builder = new DbContextOptionsBuilder(); + Microsoft.EntityFrameworkCore.SqliteDbContextOptionsBuilderExtensions.UseSqlite(builder, "Data Source=:memory:"); + using var context = new EmptyDbContext(builder.Options); + EfCoreDatabaseProviderHelper.GetDatabaseProviderOrNull(context.Database.ProviderName) + .ShouldBe(EfCoreDatabaseProvider.Sqlite); + } + + [Fact] + public void Should_Detect_InMemory_From_Real_Assembly() + { + var builder = new DbContextOptionsBuilder(); + Microsoft.EntityFrameworkCore.InMemoryDbContextOptionsExtensions.UseInMemoryDatabase(builder, "test"); + using var context = new EmptyDbContext(builder.Options); + EfCoreDatabaseProviderHelper.GetDatabaseProviderOrNull(context.Database.ProviderName) + .ShouldBe(EfCoreDatabaseProvider.InMemory); + } + + [Theory] + [InlineData("Devart.Data.Oracle.Entity.EFCore", EfCoreDatabaseProvider.Oracle)] + [InlineData("FirebirdSql.EntityFrameworkCore.Firebird", EfCoreDatabaseProvider.Firebird)] + [InlineData("Microsoft.EntityFrameworkCore.Cosmos", EfCoreDatabaseProvider.Cosmos)] + public void Should_Detect_Providers_Without_Package_Reference(string providerName, EfCoreDatabaseProvider expected) + { + EfCoreDatabaseProviderHelper.GetDatabaseProviderOrNull(providerName).ShouldBe(expected); + } + + [Theory] + [InlineData("microsoft.entityframeworkcore.sqlserver", EfCoreDatabaseProvider.SqlServer)] + [InlineData("POMELO.ENTITYFRAMEWORKCORE.MYSQL", EfCoreDatabaseProvider.MySql)] + [InlineData("npgsql.entityframeworkcore.postgresql", EfCoreDatabaseProvider.PostgreSql)] + public void Should_Detect_Providers_Case_Insensitively(string providerName, EfCoreDatabaseProvider expected) + { + EfCoreDatabaseProviderHelper.GetDatabaseProviderOrNull(providerName).ShouldBe(expected); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + [InlineData("Some.Unknown.Provider")] + public void Should_Return_Null_For_Unknown_Or_Empty(string? providerName) + { + EfCoreDatabaseProviderHelper.GetDatabaseProviderOrNull(providerName).ShouldBeNull(); + } + + private class EmptyDbContext : DbContext + { + public EmptyDbContext(DbContextOptions options) : base(options) + { + } + } +} diff --git a/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker_Tests.cs b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker_Tests.cs new file mode 100644 index 0000000000..acd961931e --- /dev/null +++ b/framework/test/Volo.Abp.Features.Tests/Volo/Abp/Features/RequireFeaturesSimpleBatchStateChecker_Tests.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.MultiTenancy; +using Volo.Abp.SimpleStateChecking; +using Xunit; + +namespace Volo.Abp.Features; + +public class RequireFeaturesSimpleBatchStateChecker_Tests : FeatureTestBase +{ + private readonly ISimpleStateCheckerManager _simpleStateCheckerManager; + private readonly ICurrentTenant _currentTenant; + + public RequireFeaturesSimpleBatchStateChecker_Tests() + { + _simpleStateCheckerManager = GetRequiredService>(); + _currentTenant = GetRequiredService(); + } + + [Fact] + public void Switch_Current_Checker_Test() + { + var checker = RequireFeaturesSimpleBatchStateChecker.Current; + checker.ShouldNotBeNull(); + + RequireFeaturesSimpleBatchStateChecker checker2 = null; + + using (RequireFeaturesSimpleBatchStateChecker.Use(new RequireFeaturesSimpleBatchStateChecker())) + { + checker2 = RequireFeaturesSimpleBatchStateChecker.Current; + checker2.ShouldNotBeNull(); + checker2.ShouldNotBe(checker); + } + + checker2.ShouldNotBeNull(); + checker2.ShouldNotBe(checker); + } + + [Fact] + public async Task RequireFeaturesSimpleBatchStateChecker_Test() + { + // Tenant1: BooleanTestFeature1=true, BooleanTestFeature2=true + // Tenant2: no boolean features set → false + using (_currentTenant.Change(TestFeatureStore.Tenant1Id)) + { + var myStateEntities = new MyStateEntity[] + { + new MyStateEntity().RequireFeatures(requiresAll: true, batchCheck: true, "BooleanTestFeature1"), + new MyStateEntity().RequireFeatures(requiresAll: true, batchCheck: true, "BooleanTestFeature2"), + new MyStateEntity().RequireFeatures(requiresAll: true, batchCheck: true, "BooleanTestFeature1", "BooleanTestFeature2"), + new MyStateEntity().RequireFeatures(requiresAll: true, batchCheck: true, "BooleanTestFeature1", "BooleanTestFeature2"), + }; + + var result = await _simpleStateCheckerManager.IsEnabledAsync(myStateEntities); + + result.Count.ShouldBe(myStateEntities.Length); + + result[myStateEntities[0]].ShouldBeTrue(); + result[myStateEntities[1]].ShouldBeTrue(); + result[myStateEntities[2]].ShouldBeTrue(); + result[myStateEntities[3]].ShouldBeTrue(); + } + + using (_currentTenant.Change(TestFeatureStore.Tenant2Id)) + { + var myStateEntities = new MyStateEntity[] + { + new MyStateEntity().RequireFeatures(requiresAll: true, batchCheck: true, "BooleanTestFeature1"), + new MyStateEntity().RequireFeatures(requiresAll: true, batchCheck: true, "BooleanTestFeature2"), + new MyStateEntity().RequireFeatures(requiresAll: true, batchCheck: true, "BooleanTestFeature1", "BooleanTestFeature2"), + new MyStateEntity().RequireFeatures(requiresAll: false, batchCheck: true, "BooleanTestFeature1", "BooleanTestFeature2"), + }; + + var result = await _simpleStateCheckerManager.IsEnabledAsync(myStateEntities); + + result.Count.ShouldBe(myStateEntities.Length); + + result[myStateEntities[0]].ShouldBeFalse(); + result[myStateEntities[1]].ShouldBeFalse(); + result[myStateEntities[2]].ShouldBeFalse(); + result[myStateEntities[3]].ShouldBeFalse(); + } + } + + [Fact] + public void GetModelOrNull_Returns_First_Win_When_Same_State_Registered_Twice() + { + // Mirrors IsEnabledAsync's modelLookup behaviour: when the same state is registered + // multiple times, the first registration wins. Backed by the dictionary index. + + var checker = new RequireFeaturesSimpleBatchStateChecker(); + var state = new NamedState("A"); + + checker.AddCheckModels( + new RequireFeaturesSimpleBatchStateCheckerModel(state, new[] { "First" }, true)); + checker.AddCheckModels( + new RequireFeaturesSimpleBatchStateCheckerModel(state, new[] { "Second" }, true)); + + checker.GetModelOrNull(state)!.FeatureNames.ShouldBe(new[] { "First" }); + } + + [Fact] + public void GetModelOrNull_Uses_Same_Equality_As_Runtime() + { + // The runtime path (IsEnabledAsync) looks up models via HashSet(context.States), + // i.e. EqualityComparer.Default. GetModelOrNull must use the same semantics or + // a custom TState.Equals would make the runtime gate and the serializer disagree. + + var checker = new RequireFeaturesSimpleBatchStateChecker(); + var stateA1 = new NamedState("A"); + var stateA2 = new NamedState("A"); // distinct instance, equal by Name + var stateB = new NamedState("B"); + + checker.AddCheckModels( + new RequireFeaturesSimpleBatchStateCheckerModel(stateA1, new[] { "F1" }, true), + new RequireFeaturesSimpleBatchStateCheckerModel(stateB, new[] { "F2" }, true)); + + // Same equality semantics as the runtime: A2 hits A1's model. + checker.GetModelOrNull(stateA1).ShouldNotBeNull(); + checker.GetModelOrNull(stateA2).ShouldNotBeNull(); + checker.GetModelOrNull(stateA2)!.FeatureNames.ShouldBe(new[] { "F1" }); + checker.GetModelOrNull(new NamedState("missing")).ShouldBeNull(); + } + + private sealed class NamedState : IHasSimpleStateCheckers, IEquatable + { + public string Name { get; } + public List> StateCheckers { get; } = new(); + + public NamedState(string name) => Name = name; + + public bool Equals(NamedState? other) => other is not null && other.Name == Name; + public override bool Equals(object? obj) => obj is NamedState other && Equals(other); + public override int GetHashCode() => Name.GetHashCode(); + } + + [Fact] + public async Task Current_Should_Not_Be_Null_In_Fresh_ExecutionContext() + { + _ = RequireFeaturesSimpleBatchStateChecker.Current; + + Task> task; + using (ExecutionContext.SuppressFlow()) + { + task = Task.Run(() => RequireFeaturesSimpleBatchStateChecker.Current); + } + + var current = await task; + current.ShouldNotBeNull(); + } + + class MyStateEntity : IHasSimpleStateCheckers + { + public List> StateCheckers { get; } + + public MyStateEntity() + { + StateCheckers = new List>(); + } + } + + class MyStateEntity2 : IHasSimpleStateCheckers + { + public List> StateCheckers { get; } + + public MyStateEntity2() + { + StateCheckers = new List>(); + } + } + + class MyStateEntity3 : IHasSimpleStateCheckers + { + public List> StateCheckers { get; } + + public MyStateEntity3() + { + StateCheckers = new List>(); + } + } +} diff --git a/framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/Files/abp.gif b/framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/Files/abp.gif new file mode 100644 index 0000000000..27f6a1e4dc Binary files /dev/null and b/framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/Files/abp.gif differ diff --git a/framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/ImageFileHelper.cs b/framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/ImageFileHelper.cs index de249fb2e5..2696bcbd3b 100644 --- a/framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/ImageFileHelper.cs +++ b/framework/test/Volo.Abp.Imaging.Abstractions.Tests/Volo/Abp/Imaging/ImageFileHelper.cs @@ -19,7 +19,12 @@ public static class ImageFileHelper { return GetTestFileStream("abp.webp"); } - + + public static Stream GetGifTestFileStream() + { + return GetTestFileStream("abp.gif"); + } + private static Stream GetTestFileStream(string fileName) { var assembly = typeof(ImageFileHelper).Assembly; diff --git a/framework/test/Volo.Abp.Imaging.SkiaSharp.Tests/Volo/Abp/Imaging/SkiaSharpImageCompressorTests.cs b/framework/test/Volo.Abp.Imaging.SkiaSharp.Tests/Volo/Abp/Imaging/SkiaSharpImageCompressorTests.cs new file mode 100644 index 0000000000..4c575d9f04 --- /dev/null +++ b/framework/test/Volo.Abp.Imaging.SkiaSharp.Tests/Volo/Abp/Imaging/SkiaSharpImageCompressorTests.cs @@ -0,0 +1,179 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Xunit; + +namespace Volo.Abp.Imaging; + +public class SkiaSharpImageCompressorTests : AbpImagingSkiaSharpTestBase +{ + public IImageCompressor ImageCompressor { get; } + + public SkiaSharpImageCompressorTests() + { + ImageCompressor = GetRequiredService(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + services.Configure(options => + { + options.Quality = 50; + }); + + base.AfterAddApplication(services); + } + + [Fact] + public async Task Should_Compress_Jpg() + { + await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); + var compressedImage = await ImageCompressor.CompressAsync(jpegImage); + + compressedImage.ShouldNotBeNull(); + compressedImage.State.ShouldBe(ImageProcessState.Done); + compressedImage.Result.Length.ShouldBeLessThan(jpegImage.Length); + compressedImage.Result.Dispose(); + } + + [Fact] + public async Task Should_Compress_Png() + { + await using var pngImage = ImageFileHelper.GetPngTestFileStream(); + var compressedImage = await ImageCompressor.CompressAsync(pngImage); + + compressedImage.ShouldNotBeNull(); + + if (compressedImage.State == ImageProcessState.Done) + { + compressedImage.Result.Length.ShouldBeLessThan(pngImage.Length); + } + else + { + compressedImage.State.ShouldBe(ImageProcessState.Canceled); + compressedImage.Result.Length.ShouldBe(pngImage.Length); + } + + compressedImage.Result.Dispose(); + } + + [Fact] + public async Task Should_Compress_Webp() + { + await using var webpImage = ImageFileHelper.GetWebpTestFileStream(); + var compressedImage = await ImageCompressor.CompressAsync(webpImage); + + compressedImage.ShouldNotBeNull(); + compressedImage.State.ShouldBe(ImageProcessState.Done); + compressedImage.Result.Length.ShouldBeLessThan(webpImage.Length); + compressedImage.Result.Dispose(); + } + + [Fact] + public async Task Should_Compress_Stream_And_Byte_Array_The_Same() + { + await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); + var byteArr = await jpegImage.GetAllBytesAsync(); + + var compressedImage1 = await ImageCompressor.CompressAsync(jpegImage); + var compressedImage2 = await ImageCompressor.CompressAsync(byteArr); + + compressedImage1.ShouldNotBeNull(); + compressedImage1.State.ShouldBe(ImageProcessState.Done); + + compressedImage2.ShouldNotBeNull(); + compressedImage2.State.ShouldBe(ImageProcessState.Done); + + compressedImage1.Result.Length.ShouldBeLessThan(jpegImage.Length); + compressedImage2.Result.LongLength.ShouldBeLessThan(jpegImage.Length); + + compressedImage1.Result.Length.ShouldBe(compressedImage2.Result.LongLength); + + compressedImage1.Result.Dispose(); + } + + [Fact] + public async Task Should_Return_Compressed_Stream_Positioned_At_Start() + { + await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); + var compressedImage = await ImageCompressor.CompressAsync(jpegImage); + + compressedImage.ShouldNotBeNull(); + compressedImage.State.ShouldBe(ImageProcessState.Done); + compressedImage.Result.Position.ShouldBe(0); + + using var copy = new MemoryStream(); + await compressedImage.Result.CopyToAsync(copy); + copy.Length.ShouldBe(compressedImage.Result.Length); + + compressedImage.Result.Dispose(); + } + + [Fact] + public async Task Should_Return_Unsupported_For_Gif_Stream() + { + await using var gifImage = ImageFileHelper.GetGifTestFileStream(); + var compressedImage = await ImageCompressor.CompressAsync(gifImage); + + compressedImage.ShouldNotBeNull(); + compressedImage.State.ShouldBe(ImageProcessState.Unsupported); + compressedImage.Result.ShouldBe(gifImage); + } + + [Fact] + public async Task Should_Return_Unsupported_For_Gif_Bytes() + { + await using var gifImage = ImageFileHelper.GetGifTestFileStream(); + var bytes = await gifImage.GetAllBytesAsync(); + var compressedImage = await ImageCompressor.CompressAsync(bytes); + + compressedImage.ShouldNotBeNull(); + compressedImage.State.ShouldBe(ImageProcessState.Unsupported); + compressedImage.Result.ShouldBe(bytes); + } + + [Fact] + public async Task Should_Handle_Non_Seekable_Stream_Directly_On_Contributor() + { + var contributor = GetRequiredService(); + + await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); + var bytes = await jpegImage.GetAllBytesAsync(); + await using var nonSeekable = new NonSeekableStream(new MemoryStream(bytes)); + + var compressedImage = await contributor.TryCompressAsync(nonSeekable, "image/jpeg"); + + compressedImage.ShouldNotBeNull(); + compressedImage.State.ShouldBeOneOf(ImageProcessState.Done, ImageProcessState.Canceled); + if (compressedImage.State == ImageProcessState.Done) + { + compressedImage.Result.ShouldNotBe(nonSeekable); + compressedImage.Result.Dispose(); + } + } + + private sealed class NonSeekableStream : Stream + { + private readonly Stream _inner; + public NonSeekableStream(Stream inner) { _inner = inner; } + public override bool CanRead => _inner.CanRead; + public override bool CanSeek => false; + public override bool CanWrite => false; + public override long Length => throw new NotSupportedException(); + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + public override int Read(byte[] buffer, int offset, int count) => _inner.Read(buffer, offset, count); + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => _inner.ReadAsync(buffer, offset, count, cancellationToken); + public override void Flush() { } + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + protected override void Dispose(bool disposing) + { + if (disposing) _inner.Dispose(); + base.Dispose(disposing); + } + } +} diff --git a/framework/test/Volo.Abp.Imaging.SkiaSharp.Tests/Volo/Abp/Imaging/SkiaSharpImageResizerTests.cs b/framework/test/Volo.Abp.Imaging.SkiaSharp.Tests/Volo/Abp/Imaging/SkiaSharpImageResizerTests.cs index 460c83bf5b..593beb506d 100644 --- a/framework/test/Volo.Abp.Imaging.SkiaSharp.Tests/Volo/Abp/Imaging/SkiaSharpImageResizerTests.cs +++ b/framework/test/Volo.Abp.Imaging.SkiaSharp.Tests/Volo/Abp/Imaging/SkiaSharpImageResizerTests.cs @@ -1,6 +1,7 @@ using System.IO; using System.Threading.Tasks; using Shouldly; +using SkiaSharp; using Xunit; namespace Volo.Abp.Imaging; @@ -72,4 +73,120 @@ public class SkiaSharpImageResizerTests : AbpImagingSkiaSharpTestBase resizedImage1.Result.Dispose(); } + + [Fact] + public async Task Should_Return_Resized_Stream_Positioned_At_Start() + { + await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); + var resizedImage = await ImageResizer.ResizeAsync(jpegImage, new ImageResizeArgs(100, 100)); + + resizedImage.ShouldNotBeNull(); + resizedImage.State.ShouldBe(ImageProcessState.Done); + resizedImage.Result.Position.ShouldBe(0); + + using var copy = new MemoryStream(); + await resizedImage.Result.CopyToAsync(copy); + copy.Length.ShouldBe(resizedImage.Result.Length); + + resizedImage.Result.Dispose(); + } + + [Fact] + public async Task Should_Return_Unsupported_For_Gif_Stream() + { + await using var gifImage = ImageFileHelper.GetGifTestFileStream(); + var resizedImage = await ImageResizer.ResizeAsync(gifImage, new ImageResizeArgs(100, 100)); + + resizedImage.ShouldNotBeNull(); + resizedImage.State.ShouldBe(ImageProcessState.Unsupported); + resizedImage.Result.ShouldBe(gifImage); + } + + [Fact] + public async Task Should_Return_Unsupported_For_Gif_Bytes() + { + await using var gifImage = ImageFileHelper.GetGifTestFileStream(); + var bytes = await gifImage.GetAllBytesAsync(); + var resizedImage = await ImageResizer.ResizeAsync(bytes, new ImageResizeArgs(100, 100)); + + resizedImage.ShouldNotBeNull(); + resizedImage.State.ShouldBe(ImageProcessState.Unsupported); + resizedImage.Result.ShouldBe(bytes); + } + + [Theory] + [InlineData(ImageResizeMode.None)] + [InlineData(ImageResizeMode.Stretch)] + [InlineData(ImageResizeMode.Crop)] + [InlineData(ImageResizeMode.Pad)] + [InlineData(ImageResizeMode.BoxPad)] + [InlineData(ImageResizeMode.Default)] + public async Task Should_Produce_Exact_Target_Size_For_Fixed_Modes(ImageResizeMode mode) + { + await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); + var resizedImage = await ImageResizer.ResizeAsync(jpegImage, new ImageResizeArgs(120, 80, mode)); + + resizedImage.State.ShouldBe(ImageProcessState.Done); + using var decoded = SKBitmap.Decode(resizedImage.Result); + decoded.ShouldNotBeNull(); + decoded.Width.ShouldBe(120); + decoded.Height.ShouldBe(80); + resizedImage.Result.Dispose(); + } + + [Fact] + public async Task Should_Produce_Bounded_Size_For_Max_Mode() + { + await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); + var resizedImage = await ImageResizer.ResizeAsync(jpegImage, new ImageResizeArgs(120, 80, ImageResizeMode.Max)); + + resizedImage.State.ShouldBe(ImageProcessState.Done); + using var decoded = SKBitmap.Decode(resizedImage.Result); + decoded.Width.ShouldBeLessThanOrEqualTo(120); + decoded.Height.ShouldBeLessThanOrEqualTo(80); + (decoded.Width == 120 || decoded.Height == 80).ShouldBeTrue(); + resizedImage.Result.Dispose(); + } + + [Fact] + public async Task Should_Max_Fit_Source_Larger_Than_Target_For_BoxPad_Mode() + { + using var src = new SKBitmap(400, 100); + using (var canvas = new SKCanvas(src)) + { + canvas.Clear(SKColors.Red); + } + using var srcImage = SKImage.FromBitmap(src); + using var srcData = srcImage.Encode(SKEncodedImageFormat.Png, 100); + using var srcStream = new MemoryStream(); + srcData.SaveTo(srcStream); + srcStream.Position = 0; + + var resizedImage = await ImageResizer.ResizeAsync(srcStream, new ImageResizeArgs(100, 100, ImageResizeMode.BoxPad)); + + resizedImage.State.ShouldBe(ImageProcessState.Done); + using var decoded = SKBitmap.Decode(resizedImage.Result); + decoded.Width.ShouldBe(100); + decoded.Height.ShouldBe(100); + + decoded.GetPixel(50, 0).Alpha.ShouldBe((byte)0); + decoded.GetPixel(50, 99).Alpha.ShouldBe((byte)0); + decoded.GetPixel(50, 50).Red.ShouldBeGreaterThan((byte)200); + + resizedImage.Result.Dispose(); + } + + [Fact] + public async Task Should_Produce_Bounded_Size_For_Min_Mode() + { + await using var jpegImage = ImageFileHelper.GetJpgTestFileStream(); + var resizedImage = await ImageResizer.ResizeAsync(jpegImage, new ImageResizeArgs(120, 80, ImageResizeMode.Min)); + + resizedImage.State.ShouldBe(ImageProcessState.Done); + using var decoded = SKBitmap.Decode(resizedImage.Result); + decoded.Width.ShouldBeGreaterThanOrEqualTo(120); + decoded.Height.ShouldBeGreaterThanOrEqualTo(80); + (decoded.Width == 120 || decoded.Height == 80).ShouldBeTrue(); + resizedImage.Result.Dispose(); + } } diff --git a/framework/test/Volo.Abp.RabbitMQ.Tests/Volo.Abp.RabbitMQ.Tests.abppkg b/framework/test/Volo.Abp.RabbitMQ.Tests/Volo.Abp.RabbitMQ.Tests.abppkg new file mode 100644 index 0000000000..64c1552e37 --- /dev/null +++ b/framework/test/Volo.Abp.RabbitMQ.Tests/Volo.Abp.RabbitMQ.Tests.abppkg @@ -0,0 +1,3 @@ +{ + "role": "lib.test" +} diff --git a/framework/test/Volo.Abp.RabbitMQ.Tests/Volo.Abp.RabbitMQ.Tests.csproj b/framework/test/Volo.Abp.RabbitMQ.Tests/Volo.Abp.RabbitMQ.Tests.csproj new file mode 100644 index 0000000000..0c4b2a2260 --- /dev/null +++ b/framework/test/Volo.Abp.RabbitMQ.Tests/Volo.Abp.RabbitMQ.Tests.csproj @@ -0,0 +1,16 @@ + + + + + + net10.0 + + + + + + + + + + diff --git a/framework/test/Volo.Abp.RabbitMQ.Tests/Volo/Abp/RabbitMQ/ChannelPool_Tests.cs b/framework/test/Volo.Abp.RabbitMQ.Tests/Volo/Abp/RabbitMQ/ChannelPool_Tests.cs new file mode 100644 index 0000000000..f631ce2d51 --- /dev/null +++ b/framework/test/Volo.Abp.RabbitMQ.Tests/Volo/Abp/RabbitMQ/ChannelPool_Tests.cs @@ -0,0 +1,359 @@ +#nullable enable + +using System; +using System.Threading; +using System.Threading.Tasks; +using NSubstitute; +using RabbitMQ.Client; +using Shouldly; +using Xunit; + +namespace Volo.Abp.RabbitMQ; + +public class ChannelPool_Tests +{ + private static readonly TimeSpan RaceTimeout = TimeSpan.FromSeconds(5); + + // ---- Regression tests for issue #25310 / PR #25311 -------------------- + + /// + /// After a RabbitMQ restart the cached channel becomes closed. Two callers + /// that both observe the stale poolItem must not deadlock while the pool + /// replaces it with a fresh channel. + /// + [Fact] + public async Task AcquireAsync_Should_Not_Hang_When_Channel_Closed_With_Concurrent_Callers() + { + var fixture = BuildConnectionPool(); + var channelPool = new TestChannelPool(fixture.Pool); + + using (await channelPool.AcquireAsync("q")) + { + } + + fixture.Channel1.IsClosed.Returns(true); + fixture.Channel1 + .DisposeAsync() + .Returns(_ => new ValueTask(Task.Delay(300))); + + using var barrier = new Barrier(2); + + var firstCaller = Task.Run(async () => + { + barrier.SignalAndWait(RaceTimeout); + using var accessor = await channelPool.AcquireAsync("q"); + }); + + var secondCaller = Task.Run(async () => + { + barrier.SignalAndWait(RaceTimeout); + await Task.Delay(50); + using var accessor = await channelPool.AcquireAsync("q"); + }); + + var completed = Task.WhenAll(firstCaller, secondCaller); + var winner = await Task.WhenAny(completed, Task.Delay(RaceTimeout)); + + winner.ShouldBe( + completed, + $"AcquireAsync is hanging on a stale poolItem. firstCaller={firstCaller.Status}, secondCaller={secondCaller.Status}"); + + await completed; + } + + /// + /// When several callers race through the closed-channel recovery path, only one new + /// channel must be created and the dictionary must keep exactly the channel that callers + /// receive — otherwise the replacement leaks on the broker side. + /// + [Fact] + public async Task AcquireAsync_Should_Create_Only_One_Replacement_Channel_When_Many_Callers_Race() + { + var fixture = BuildConnectionPool(); + var channelPool = new TestChannelPool(fixture.Pool); + + using (await channelPool.AcquireAsync("q")) + { + } + + fixture.Channel1.IsClosed.Returns(true); + + const int callerCount = 8; + using var barrier = new Barrier(callerCount); + var tasks = new Task[callerCount]; + for (var i = 0; i < callerCount; i++) + { + tasks[i] = Task.Run(async () => + { + barrier.SignalAndWait(RaceTimeout); + using var accessor = await channelPool.AcquireAsync("q"); + return accessor.Channel; + }); + } + + var channels = await Task.WhenAll(tasks); + + foreach (var channel in channels) + { + channel.ShouldBe(fixture.Channel2, "every caller must receive the single replacement channel"); + } + + fixture.CreateChannelCalls.ShouldBe( + 2, + $"exactly one initial + one replacement channel should be created, but got {fixture.CreateChannelCalls}"); + } + + // ---- Behavior guard-rails: make sure the refactor preserves core semantics ---- + + [Fact] + public async Task AcquireAsync_Should_Return_Channel_From_ConnectionPool() + { + var fixture = BuildConnectionPool(); + var channelPool = new TestChannelPool(fixture.Pool); + + using var accessor = await channelPool.AcquireAsync("q"); + + accessor.Channel.ShouldBe(fixture.Channel1); + accessor.Name.ShouldBe("q"); + fixture.CreateChannelCalls.ShouldBe(1); + } + + [Fact] + public async Task AcquireAsync_Should_Reuse_Cached_Channel_On_Subsequent_Calls() + { + var fixture = BuildConnectionPool(); + var channelPool = new TestChannelPool(fixture.Pool); + + using (var first = await channelPool.AcquireAsync("q")) + { + first.Channel.ShouldBe(fixture.Channel1); + } + using (var second = await channelPool.AcquireAsync("q")) + { + second.Channel.ShouldBe(fixture.Channel1); + } + using (var third = await channelPool.AcquireAsync("q")) + { + third.Channel.ShouldBe(fixture.Channel1); + } + + fixture.CreateChannelCalls.ShouldBe(1, "a healthy cached channel must be reused"); + } + + [Fact] + public async Task AcquireAsync_Should_Keep_Separate_PoolItems_For_Different_ChannelNames() + { + var fixture = BuildConnectionPool(); + var channelPool = new TestChannelPool(fixture.Pool); + + using var a = await channelPool.AcquireAsync("queue-a"); + using var b = await channelPool.AcquireAsync("queue-b"); + + a.Channel.ShouldBe(fixture.Channel1); + b.Channel.ShouldBe(fixture.Channel2); + a.Name.ShouldBe("queue-a"); + b.Name.ShouldBe("queue-b"); + fixture.CreateChannelCalls.ShouldBe(2); + } + + [Fact] + public async Task AcquireAsync_Should_Serialize_Concurrent_Callers_On_Same_Channel() + { + var fixture = BuildConnectionPool(); + var channelPool = new TestChannelPool(fixture.Pool); + + var first = await channelPool.AcquireAsync("q"); + + var secondTask = Task.Run(() => channelPool.AcquireAsync("q")); + + await Task.Delay(100); + secondTask.IsCompleted.ShouldBeFalse("second caller must block while the channel is held"); + + first.Dispose(); + + var completed = await Task.WhenAny(secondTask, Task.Delay(RaceTimeout)); + completed.ShouldBe(secondTask, "second caller must be unblocked after release"); + + var secondAccessor = await secondTask; + secondAccessor.Channel.ShouldBe(fixture.Channel1, "the cached channel is reused"); + + secondAccessor.Dispose(); + fixture.CreateChannelCalls.ShouldBe(1, "channel is reused, never recreated"); + } + + [Fact] + public async Task AcquireAsync_Should_Not_Rebuild_When_Cached_Channel_Is_Healthy() + { + var fixture = BuildConnectionPool(); + var channelPool = new TestChannelPool(fixture.Pool); + + using (await channelPool.AcquireAsync("q")) + { + } + // Channel stays open, so subsequent calls must not go through the rebuild branch. + using (await channelPool.AcquireAsync("q")) + { + } + + await fixture.Channel1.DidNotReceive().DisposeAsync(); + fixture.CreateChannelCalls.ShouldBe(1); + } + + [Fact] + public async Task AcquireAsync_Should_Propagate_Exception_When_CreateChannel_Fails() + { + var channel = Substitute.For(); + channel.IsClosed.Returns(false); + + var connection = Substitute.For(); + var attempts = 0; + connection + .CreateChannelAsync(Arg.Any(), Arg.Any()) + .Returns(_ => + { + var n = Interlocked.Increment(ref attempts); + if (n == 1) + { + throw new InvalidOperationException("broker down"); + } + return Task.FromResult(channel); + }); + + var connectionPool = Substitute.For(); + connectionPool.GetAsync(Arg.Any()).Returns(Task.FromResult(connection)); + + var channelPool = new TestChannelPool(connectionPool); + + await Should.ThrowAsync(() => channelPool.AcquireAsync("q")); + + // Subsequent call should be able to succeed (broker came back). + using var accessor = await channelPool.AcquireAsync("q"); + accessor.Channel.ShouldBe(channel); + attempts.ShouldBe(2); + } + + [Fact] + public async Task AcquireAsync_Should_Dispose_Stale_Channel_Even_When_Recreate_Fails() + { + var staleChannel = Substitute.For(); + staleChannel.IsClosed.Returns(false); + + var connection = Substitute.For(); + var attempts = 0; + connection + .CreateChannelAsync(Arg.Any(), Arg.Any()) + .Returns(_ => + { + var n = Interlocked.Increment(ref attempts); + if (n == 1) + { + return Task.FromResult(staleChannel); + } + throw new InvalidOperationException("broker still down"); + }); + + var connectionPool = Substitute.For(); + connectionPool.GetAsync(Arg.Any()).Returns(Task.FromResult(connection)); + + var channelPool = new TestChannelPool(connectionPool); + + using (await channelPool.AcquireAsync("q")) + { + } + + staleChannel.IsClosed.Returns(true); + + await Should.ThrowAsync(() => channelPool.AcquireAsync("q")); + + await staleChannel.Received(1).DisposeAsync(); + } + + [Fact] + public async Task AcquireAsync_Should_Throw_After_Pool_Disposed() + { + var fixture = BuildConnectionPool(); + var channelPool = new TestChannelPool(fixture.Pool); + + await channelPool.DisposeAsync(); + + await Should.ThrowAsync(() => channelPool.AcquireAsync("q")); + } + + [Fact] + public async Task AcquireAsync_Should_Only_Create_One_Channel_Even_When_First_Callers_Race() + { + var fixture = BuildConnectionPool(); + var channelPool = new TestChannelPool(fixture.Pool); + + const int callerCount = 8; + using var barrier = new Barrier(callerCount); + var tasks = new Task[callerCount]; + for (var i = 0; i < callerCount; i++) + { + tasks[i] = Task.Run(async () => + { + barrier.SignalAndWait(RaceTimeout); + using var accessor = await channelPool.AcquireAsync("q"); + return accessor.Channel; + }); + } + + var channels = await Task.WhenAll(tasks); + + foreach (var channel in channels) + { + channel.ShouldBe(fixture.Channel1, "the first-creation semaphore must serialize initial creation"); + } + fixture.CreateChannelCalls.ShouldBe(1); + } + + // ---- Fixture --------------------------------------------------------- + + private static ConnectionPoolFixture BuildConnectionPool() + { + var channel1 = Substitute.For(); + channel1.IsClosed.Returns(false); + + var channel2 = Substitute.For(); + channel2.IsClosed.Returns(false); + + var fixture = new ConnectionPoolFixture(channel1, channel2); + + var connection = Substitute.For(); + connection + .CreateChannelAsync(Arg.Any(), Arg.Any()) + .Returns(_ => + { + var n = Interlocked.Increment(ref fixture.CreateChannelCallsField); + return n == 1 ? Task.FromResult(channel1) : Task.FromResult(channel2); + }); + + var connectionPool = Substitute.For(); + connectionPool.GetAsync(Arg.Any()).Returns(Task.FromResult(connection)); + + fixture.Pool = connectionPool; + return fixture; + } + + private sealed class ConnectionPoolFixture + { + public IConnectionPool Pool { get; set; } = default!; + public IChannel Channel1 { get; } + public IChannel Channel2 { get; } + public int CreateChannelCallsField; + public int CreateChannelCalls => CreateChannelCallsField; + + public ConnectionPoolFixture(IChannel channel1, IChannel channel2) + { + Channel1 = channel1; + Channel2 = channel2; + } + } + + private sealed class TestChannelPool : ChannelPool + { + public TestChannelPool(IConnectionPool connectionPool) : base(connectionPool) + { + } + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine_IsSandboxed_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine_IsSandboxed_Tests.cs new file mode 100644 index 0000000000..aba81af6f3 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine_IsSandboxed_Tests.cs @@ -0,0 +1,29 @@ +using Shouldly; +using Xunit; + +namespace Volo.Abp.TextTemplating.Razor; + +public class RazorTemplateRenderingEngine_IsSandboxed_Tests : AbpTextTemplatingTestBase +{ + private readonly RazorTemplateRenderingEngine _engine; + + public RazorTemplateRenderingEngine_IsSandboxed_Tests() + { + _engine = GetRequiredService(); + } + + [Fact] + public void Razor_Engine_Should_Not_Be_Sandboxed() + { + // Razor templates compile into fully-trusted .NET code; editing them is + // equivalent to granting server-side code execution. + _engine.IsSandboxed.ShouldBeFalse(); + } + + [Fact] + public void Razor_Engine_Should_Expose_IsSandboxed_Through_Interface() + { + ITemplateRenderingEngine asInterface = _engine; + asInterface.IsSandboxed.ShouldBeFalse(); + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/MethodInvocationAttempt.tpl b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/MethodInvocationAttempt.tpl new file mode 100644 index 0000000000..9666a9e986 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/MethodInvocationAttempt.tpl @@ -0,0 +1 @@ +danger=[{{ model.dangerous_action }}] diff --git a/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/NestedPropertyAccess.tpl b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/NestedPropertyAccess.tpl new file mode 100644 index 0000000000..14eb44cce2 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/NestedPropertyAccess.tpl @@ -0,0 +1 @@ +name=[{{ model.name }}] email=[{{ model.inner.email }}] diff --git a/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ReflectionEscapeAttempt.tpl b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ReflectionEscapeAttempt.tpl new file mode 100644 index 0000000000..6ccfcfcef6 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ReflectionEscapeAttempt.tpl @@ -0,0 +1 @@ +getType=[{{ model.GetType }}] diff --git a/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ReflectionEscapeChain.tpl b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ReflectionEscapeChain.tpl new file mode 100644 index 0000000000..ada402ac57 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/ReflectionEscapeChain.tpl @@ -0,0 +1 @@ +loaded=[{{ model.GetType.Assembly.GetType "System.IO.File" }}] diff --git a/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine_IsSandboxed_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine_IsSandboxed_Tests.cs new file mode 100644 index 0000000000..2aa0cd018f --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine_IsSandboxed_Tests.cs @@ -0,0 +1,123 @@ +using System.Threading.Tasks; +using global::Scriban.Syntax; +using Shouldly; +using Xunit; + +namespace Volo.Abp.TextTemplating.Scriban; + +public class ScribanTemplateRenderingEngine_IsSandboxed_Tests : AbpTextTemplatingTestBase +{ + private readonly ScribanTemplateRenderingEngine _engine; + private readonly ITemplateRenderer _templateRenderer; + + public ScribanTemplateRenderingEngine_IsSandboxed_Tests() + { + _engine = GetRequiredService(); + _templateRenderer = GetRequiredService(); + } + + [Fact] + public void Scriban_Engine_Should_Be_Sandboxed() + { + // Scriban interprets templates as a restricted DSL without .NET interop; + // editing template content is safe for non-developer users. + _engine.IsSandboxed.ShouldBeTrue(); + } + + [Fact] + public void Scriban_Engine_Should_Expose_IsSandboxed_Through_Interface() + { + ITemplateRenderingEngine asInterface = _engine; + asInterface.IsSandboxed.ShouldBeTrue(); + } + + [Fact] + public async Task Should_Hide_GetType_Member_On_Imported_Model() + { + // The engine projects .NET model objects into a ScriptObject containing + // only the model's public readable properties. Reflection entry points + // such as object.GetType are not part of the projection, so + // "{{ model.GetType }}" silently resolves to null (empty output) instead + // of leaking the runtime type. + var result = await _templateRenderer.RenderAsync( + ScribanTestTemplateDefinitionProvider.ReflectionEscapeAttempt, + model: new ReflectionEscapeModel { Name = "John" }); + + result.ShouldBe("getType=[]\n"); + result.ShouldNotContain("RuntimeType"); + result.ShouldNotContain("Volo.Abp.TextTemplating"); + } + + [Fact] + public async Task Should_Block_Reflection_Escape_Chain() + { + // Direct reflection chain that an attacker would attempt: + // {{ model.GetType.Assembly.GetType "System.IO.File" }} + // Because the projected ScriptObject does not expose GetType, the first + // hop yields null and Scriban raises a ScriptRuntimeException when the + // chain dereferences a null. Surfacing an error is desirable: silent + // failure could mask escape attempts. + var ex = await Should.ThrowAsync(async () => + await _templateRenderer.RenderAsync( + ScribanTestTemplateDefinitionProvider.ReflectionEscapeChain, + model: new ReflectionEscapeModel { Name = "John" })); + + // The error must reference the broken chain (null), not a leaked Type + // or Assembly value. + ex.Message.ShouldContain("null"); + ex.Message.ShouldNotContain("System.IO.File"); + ex.Message.ShouldNotContain("System.Private.CoreLib"); + } + + [Fact] + public async Task Should_Hide_Methods_Of_Imported_Model() + { + // Methods on .NET objects are not exposed by the projection so + // attackers cannot invoke side-effecting methods (e.g. repository + // mutators) even when the host accidentally imports a service-like + // object as a model. + var result = await _templateRenderer.RenderAsync( + ScribanTestTemplateDefinitionProvider.MethodInvocationAttempt, + model: new ServiceLikeModel()); + + result.ShouldBe("danger=[]\n"); + result.ShouldNotContain("UNSAFE"); + } + + [Fact] + public async Task Should_Project_Properties_Including_Nested_Objects() + { + // The projection must still expose user-defined readable properties + // (and recurse into nested objects) so legitimate template usage keeps + // working after sandboxing. + var result = await _templateRenderer.RenderAsync( + ScribanTestTemplateDefinitionProvider.NestedPropertyAccess, + model: new OuterModel { Name = "John", Inner = new InnerModel { Email = "j@a.b" } }); + + result.ShouldBe("name=[John] email=[j@a.b]\n"); + } + + private class ReflectionEscapeModel + { + public string Name { get; set; } = default!; + } + + private class ServiceLikeModel + { + public string DangerousAction() + { + return "UNSAFE"; + } + } + + private class OuterModel + { + public string Name { get; set; } = default!; + public InnerModel Inner { get; set; } = default!; + } + + private class InnerModel + { + public string Email { get; set; } = default!; + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTestTemplateDefinitionProvider.cs index 0087288c32..006f2db450 100644 --- a/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTestTemplateDefinitionProvider.cs +++ b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTestTemplateDefinitionProvider.cs @@ -2,6 +2,11 @@ public class ScribanTestTemplateDefinitionProvider : TemplateDefinitionProvider { + public const string ReflectionEscapeAttempt = "ReflectionEscapeAttempt"; + public const string ReflectionEscapeChain = "ReflectionEscapeChain"; + public const string MethodInvocationAttempt = "MethodInvocationAttempt"; + public const string NestedPropertyAccess = "NestedPropertyAccess"; + public override void Define(ITemplateDefinitionContext context) { context.GetOrNull(TestTemplates.WelcomeEmail)? @@ -19,5 +24,21 @@ public class ScribanTestTemplateDefinitionProvider : TemplateDefinitionProvider context.GetOrNull(TestTemplates.ShowDecimalNumber)? .WithVirtualFilePath("/SampleTemplates/ShowDecimalNumber.tpl", true) .WithScribanEngine(); + + context.Add(new TemplateDefinition(ReflectionEscapeAttempt) + .WithVirtualFilePath("/SampleTemplates/ReflectionEscapeAttempt.tpl", true) + .WithScribanEngine()); + + context.Add(new TemplateDefinition(ReflectionEscapeChain) + .WithVirtualFilePath("/SampleTemplates/ReflectionEscapeChain.tpl", true) + .WithScribanEngine()); + + context.Add(new TemplateDefinition(MethodInvocationAttempt) + .WithVirtualFilePath("/SampleTemplates/MethodInvocationAttempt.tpl", true) + .WithScribanEngine()); + + context.Add(new TemplateDefinition(NestedPropertyAccess) + .WithVirtualFilePath("/SampleTemplates/NestedPropertyAccess.tpl", true) + .WithScribanEngine()); } } diff --git a/latest-versions.json b/latest-versions.json index 7cba54292c..ccd634c25c 100644 --- a/latest-versions.json +++ b/latest-versions.json @@ -1,4 +1,22 @@ [ + { + "version": "10.4.0", + "releaseDate": "", + "type": "stable", + "message": "", + "leptonx": { + "version": "5.4.0" + } + }, + { + "version": "10.3.0", + "releaseDate": "", + "type": "stable", + "message": "", + "leptonx": { + "version": "5.3.0" + } + }, { "version": "10.2.1", "releaseDate": "", diff --git a/modules/account/Volo.Abp.Account.slnx b/modules/account/Volo.Abp.Account.slnx index 031e142df9..1beae2db60 100644 --- a/modules/account/Volo.Abp.Account.slnx +++ b/modules/account/Volo.Abp.Account.slnx @@ -2,6 +2,7 @@ + diff --git a/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/AbpAccountBlazorMappers.cs b/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/AbpAccountBlazorMappers.cs new file mode 100644 index 0000000000..eda8b42eb9 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/AbpAccountBlazorMappers.cs @@ -0,0 +1,27 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Account.Blazor.MudBlazor.Pages.Account; +using Volo.Abp.Mapperly; +using Volo.Abp.Identity; + +namespace Volo.Abp.Account.Blazor.MudBlazor; + +[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] +[MapExtraProperties] +public partial class ProfileDtoToPersonalInfoModelMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(PersonalInfoModel.PhoneNumberConfirmed))] + [MapperIgnoreTarget(nameof(PersonalInfoModel.EmailConfirmed))] + public override partial PersonalInfoModel Map(ProfileDto source); + + [MapperIgnoreTarget(nameof(PersonalInfoModel.PhoneNumberConfirmed))] + [MapperIgnoreTarget(nameof(PersonalInfoModel.EmailConfirmed))] + public override partial void Map(ProfileDto source, PersonalInfoModel destination); +} + +[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] +[MapExtraProperties] +public partial class PersonalInfoModelToUpdateProfileDtoMapper : MapperBase +{ + public override partial UpdateProfileDto Map(PersonalInfoModel source); + public override partial void Map(PersonalInfoModel source, UpdateProfileDto destination); +} diff --git a/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/AbpAccountBlazorMudBlazorModule.cs b/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/AbpAccountBlazorMudBlazorModule.cs new file mode 100644 index 0000000000..40427f0f43 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/AbpAccountBlazorMudBlazorModule.cs @@ -0,0 +1,50 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Account.Blazor.MudBlazor.Pages.Account; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; +using Volo.Abp.Mapperly; +using Volo.Abp.Modularity; +using Volo.Abp.ObjectExtending; +using Volo.Abp.ObjectExtending.Modularity; +using Volo.Abp.Threading; +using Volo.Abp.UI.Navigation; + +namespace Volo.Abp.Account.Blazor.MudBlazor; + +[DependsOn( + typeof(AbpAspNetCoreComponentsWebThemingMudBlazorModule), + typeof(AbpMapperlyModule), + typeof(AbpAccountApplicationContractsModule) + )] +public class AbpAccountBlazorMudBlazorModule : AbpModule +{ + private readonly static OneTimeRunner OneTimeRunner = new OneTimeRunner(); + + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddMapperlyObjectMapper(); + + Configure(options => + { + options.MenuContributors.Add(new AbpAccountBlazorUserMenuContributor()); + }); + + Configure(options => + { + options.AdditionalAssemblies.Add(typeof(AbpAccountBlazorMudBlazorModule).Assembly); + }); + } + + public override void PostConfigureServices(ServiceConfigurationContext context) + { + OneTimeRunner.Run(() => + { + ModuleExtensionConfigurationHelper + .ApplyEntityConfigurationToUi( + IdentityModuleExtensionConsts.ModuleName, + IdentityModuleExtensionConsts.EntityNames.User, + editFormTypes: new[] { typeof(PersonalInfoModel) } + ); + }); + } +} diff --git a/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/AbpAccountBlazorUserMenuContributor.cs b/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/AbpAccountBlazorUserMenuContributor.cs new file mode 100644 index 0000000000..bbb230938d --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/AbpAccountBlazorUserMenuContributor.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; +using Volo.Abp.Account.Localization; +using Volo.Abp.UI.Navigation; + +namespace Volo.Abp.Account.Blazor.MudBlazor; + +public class AbpAccountBlazorUserMenuContributor : IMenuContributor +{ + public Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name != StandardMenus.User) + { + return Task.CompletedTask; + } + + var accountResource = context.GetLocalizer(); + + context.Menu.AddItem(new ApplicationMenuItem("Account.Manage", accountResource["MyAccount"], url: "account/manage-profile", icon: "fa fa-cog")); + + return Task.CompletedTask; + } +} diff --git a/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/AbpAccountComponentBase.cs b/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/AbpAccountComponentBase.cs new file mode 100644 index 0000000000..b2c1f74c67 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/AbpAccountComponentBase.cs @@ -0,0 +1,13 @@ +using Volo.Abp.Account.Localization; +using Volo.Abp.AspNetCore.Components; + +namespace Volo.Abp.Account.Blazor.MudBlazor; + +public abstract class AbpAccountComponentBase : AbpComponentBase +{ + protected AbpAccountComponentBase() + { + LocalizationResource = typeof(AccountResource); + ObjectMapperContext = typeof(AbpAccountBlazorMudBlazorModule); + } +} diff --git a/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/FodyWeavers.xml b/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/FodyWeavers.xsd b/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/Pages/Account/AccountManage.razor b/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/Pages/Account/AccountManage.razor new file mode 100644 index 0000000000..c25bc622f8 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/Pages/Account/AccountManage.razor @@ -0,0 +1,100 @@ +@page "/account/manage-profile" +@page "/{culture}/account/manage-profile" +@using global::MudBlazor +@using Volo.Abp.Account.Localization +@using Volo.Abp.MudBlazorUI.Components.ObjectExtending +@using Volo.Abp.ObjectExtending + +@inherits AbpAccountComponentBase + + + + + + @if (ChangePasswordModel != null) + { + + + + + + @L["Save"] + + + } + + + @if (PersonalInfoModel != null) + { + + + + + + + + + + + + + + @* Extension Properties *@ + @foreach (var propertyInfo in ObjectExtensionManager.Instance.GetProperties()) + { + var isAllowed = propertyInfo.Configuration.GetOrDefault(IdentityModuleExtensionConsts.ConfigurationNames.AllowUserToEdit); + + if (isAllowed == null || !isAllowed.Equals(true)) + { + continue; + } + + if (!propertyInfo.Name.EndsWith("_Text")) + { + if (propertyInfo.IsEnum()) + { + + } + else if (!propertyInfo.Lookup.Url.IsNullOrEmpty()) + { + + } + else + { + var inputType = propertyInfo.GetInputType(); + __builder.OpenComponent(0, inputType.MakeGenericType(new[] { typeof(PersonalInfoModel), typeof(AccountResource) })); + __builder.AddAttribute(1, "PropertyInfo", propertyInfo); + __builder.AddAttribute(2, "Entity", PersonalInfoModel); + __builder.AddAttribute(3, "LH", _localizerHelper); + __builder.CloseComponent(); + } + } + } + + + @L["Save"] + + + } + + + + + diff --git a/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/Pages/Account/AccountManage.razor.cs b/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/Pages/Account/AccountManage.razor.cs new file mode 100644 index 0000000000..a4fc96941c --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/Pages/Account/AccountManage.razor.cs @@ -0,0 +1,159 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; +using MudBlazor; +using Volo.Abp.Account.Localization; +using Volo.Abp.AspNetCore.Components.Messages; +using Volo.Abp.Identity; +using Volo.Abp.MudBlazorUI; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.Account.Blazor.MudBlazor.Pages.Account; + +public partial class AccountManage +{ + [Parameter] + public string? Culture { get; set; } + + [Inject] protected IProfileAppService ProfileAppService { get; set; } = default!; + + [Inject] protected IUiMessageService UiMessageService { get; set; } = default!; + + [Inject] protected ISnackbar Snackbar { get; set; } = default!; + + [Inject] protected IStringLocalizer Localizer { get; set; } = default!; + + protected string SelectedTab = "Password"; + + protected ChangePasswordModel? ChangePasswordModel; + + protected PersonalInfoModel? PersonalInfoModel; + + private int _activeTabIndex = 0; + private MudForm? _changePasswordForm; + private MudForm? _personalInfoForm; + private AbpMudBlazorMessageLocalizerHelper? _localizerHelper; + + protected override async Task OnInitializedAsync() + { + _localizerHelper = new AbpMudBlazorMessageLocalizerHelper(Localizer); + await GetUserInformations(); + } + + protected async Task GetUserInformations() + { + var user = await ProfileAppService.GetAsync(); + + ChangePasswordModel = new ChangePasswordModel + { + HideOldPasswordInput = !user.HasPassword + }; + + PersonalInfoModel = ObjectMapper.Map(user); + } + + protected async Task ChangePasswordAsync() + { + if (_changePasswordForm == null) + { + return; + } + + await _changePasswordForm.Validate(); + if (!_changePasswordForm.IsValid) + { + return; + } + + if (ChangePasswordModel == null || string.IsNullOrWhiteSpace(ChangePasswordModel.CurrentPassword)) + { + return; + } + + if (ChangePasswordModel.NewPassword != ChangePasswordModel.NewPasswordConfirm) + { + await UiMessageService.Warn(L["NewPasswordConfirmFailed"]); + return; + } + + if (ChangePasswordModel.CurrentPassword == ChangePasswordModel.NewPassword) + { + await UiMessageService.Warn(L["NewPasswordSameAsOld"]); + return; + } + + await ProfileAppService.ChangePasswordAsync(new ChangePasswordInput + { + CurrentPassword = ChangePasswordModel.CurrentPassword, + NewPassword = ChangePasswordModel.NewPassword + }); + + ChangePasswordModel.Clear(); + + await UiMessageService.Success(L["PasswordChanged"]); + } + + protected virtual async Task UpdatePersonalInfoAsync() + { + if (_personalInfoForm == null) + { + return; + } + + await _personalInfoForm.Validate(); + + if (!_personalInfoForm.IsValid) + { + return; + } + + if (PersonalInfoModel == null) + { + return; + } + + await ProfileAppService.UpdateAsync( + ObjectMapper.Map(PersonalInfoModel) + ); + + await UiMessageService.Success(L["PersonalSettingsSaved"]); + } + +} + +public class ChangePasswordModel +{ + public string CurrentPassword { get; set; } = string.Empty; + + public string NewPassword { get; set; } = string.Empty; + + public string NewPasswordConfirm { get; set; } = string.Empty; + + public bool HideOldPasswordInput { get; set; } + + public void Clear() + { + CurrentPassword = string.Empty; + NewPassword = string.Empty; + NewPasswordConfirm = string.Empty; + } +} + +public class PersonalInfoModel : ExtensibleObject +{ + public string UserName { get; set; } = string.Empty; + + public string Email { get; set; } = string.Empty; + + public string Name { get; set; } = string.Empty; + + public string Surname { get; set; } = string.Empty; + + public string PhoneNumber { get; set; } = string.Empty; + + public bool PhoneNumberConfirmed { get; set; } + + public bool EmailConfirmed { get; set; } + + public string ConcurrencyStamp { get; set; } = string.Empty; +} diff --git a/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/Volo.Abp.Account.Blazor.MudBlazor.csproj b/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/Volo.Abp.Account.Blazor.MudBlazor.csproj new file mode 100644 index 0000000000..8ee2deb508 --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/Volo.Abp.Account.Blazor.MudBlazor.csproj @@ -0,0 +1,20 @@ + + + + + + + net10.0 + Volo.Abp.Account.Blazor.MudBlazor + + + + + + + + + + + + diff --git a/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/_Imports.razor b/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/_Imports.razor new file mode 100644 index 0000000000..95b2bfdc7f --- /dev/null +++ b/modules/account/src/Volo.Abp.Account.Blazor.MudBlazor/_Imports.razor @@ -0,0 +1,5 @@ +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Forms +@using Volo.Abp.MudBlazorUI +@using Volo.Abp.MudBlazorUI.Components +@using MudBlazor diff --git a/modules/basic-theme/Volo.Abp.BasicTheme.slnx b/modules/basic-theme/Volo.Abp.BasicTheme.slnx index 44256584c5..42b7f7973c 100644 --- a/modules/basic-theme/Volo.Abp.BasicTheme.slnx +++ b/modules/basic-theme/Volo.Abp.BasicTheme.slnx @@ -1,9 +1,13 @@ + + + + diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/AbpAspNetCoreComponentsServerMudBlazorBasicThemeModule.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/AbpAspNetCoreComponentsServerMudBlazorBasicThemeModule.cs new file mode 100644 index 0000000000..9bf19a712e --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/AbpAspNetCoreComponentsServerMudBlazorBasicThemeModule.cs @@ -0,0 +1,46 @@ +using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Bundling; +using Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor; +using Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor.Bundling; +using Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Toolbars; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.Modularity; +using BlazorStandardBundles = Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor.Bundling.BlazorServerMudBlazorStandardBundles; + +namespace Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme; + +[DependsOn( + typeof(AbpAspNetCoreComponentsWebMudBlazorBasicThemeModule), + typeof(AbpAspNetCoreComponentsServerThemingMudBlazorModule) + )] +public class AbpAspNetCoreComponentsServerMudBlazorBasicThemeModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Contributors.Add(new MudBlazorBasicThemeToolbarContributor()); + }); + + Configure(options => + { + options + .StyleBundles + .Add(BlazorMudBlazorBasicThemeBundles.Styles.Global, bundle => + { + bundle + .AddBaseBundles(BlazorStandardBundles.Styles.Global) + .AddContributors(typeof(BlazorServerMudBlazorBasicThemeStyleContributor)); + }); + + options + .ScriptBundles + .Add(BlazorMudBlazorBasicThemeBundles.Scripts.Global, bundle => + { + bundle + .AddBaseBundles(BlazorStandardBundles.Scripts.Global) + .AddContributors(typeof(BlazorServerMudBlazorBasicThemeScriptContributor)); + }); + }); + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Bundling/BlazorMudBlazorBasicThemeBundles.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Bundling/BlazorMudBlazorBasicThemeBundles.cs new file mode 100644 index 0000000000..8b1efca7ea --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Bundling/BlazorMudBlazorBasicThemeBundles.cs @@ -0,0 +1,14 @@ +namespace Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Bundling; + +public static class BlazorMudBlazorBasicThemeBundles +{ + public static class Styles + { + public static string Global = "Blazor.MudBlazorBasicTheme.Global"; + } + + public static class Scripts + { + public static string Global = "Blazor.MudBlazorBasicTheme.Global"; + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Bundling/BlazorServerMudBlazorBasicThemeScriptContributor.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Bundling/BlazorServerMudBlazorBasicThemeScriptContributor.cs new file mode 100644 index 0000000000..d2d6bf6bb9 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Bundling/BlazorServerMudBlazorBasicThemeScriptContributor.cs @@ -0,0 +1,8 @@ +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Bundling; + +public class BlazorServerMudBlazorBasicThemeScriptContributor : BundleContributor +{ + +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Bundling/BlazorServerMudBlazorBasicThemeStyleContributor.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Bundling/BlazorServerMudBlazorBasicThemeStyleContributor.cs new file mode 100644 index 0000000000..8e9ce50bee --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Bundling/BlazorServerMudBlazorBasicThemeStyleContributor.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Bundling; + +public class BlazorServerMudBlazorBasicThemeStyleContributor : BundleContributor +{ + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("/_content/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/themes/basic/main.css"); + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/FodyWeavers.xml b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/FodyWeavers.xsd b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/MudBlazorBasicThemeToolbarContributor.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/MudBlazorBasicThemeToolbarContributor.cs new file mode 100644 index 0000000000..0ba46719c4 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/MudBlazorBasicThemeToolbarContributor.cs @@ -0,0 +1,20 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Themes.Basic; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Toolbars; + +namespace Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme; + +public class MudBlazorBasicThemeToolbarContributor : IToolbarContributor +{ + public Task ConfigureToolbarAsync(IToolbarConfigurationContext context) + { + if (context.Toolbar.Name == StandardToolbars.Main) + { + context.Toolbar.Items.Add(new ToolbarItem(typeof(LoginDisplay))); + context.Toolbar.Items.Add(new ToolbarItem(typeof(LanguageSwitch))); + } + + return Task.CompletedTask; + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Themes/Basic/LanguageSwitch.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Themes/Basic/LanguageSwitch.razor new file mode 100644 index 0000000000..6f68415b1f --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Themes/Basic/LanguageSwitch.razor @@ -0,0 +1,70 @@ +@using Volo.Abp.Localization +@using System.Globalization +@using System.Collections.Immutable +@using Microsoft.AspNetCore.RequestLocalization +@inject ILanguageProvider LanguageProvider +@inject NavigationManager NavigationManager +@inject IAbpRequestLocalizationOptionsProvider RequestLocalizationOptionsProvider + +@if (_otherLanguages != null && _otherLanguages.Any()) +{ + + + + + + @_currentLanguage.DisplayName + + @foreach (var language in _otherLanguages) + { + @language.DisplayName + } + + +} + +@code { + private IReadOnlyList? _otherLanguages; + private LanguageInfo? _currentLanguage; + + protected override async Task OnInitializedAsync() + { + var languages = await LanguageProvider.GetLanguagesAsync(); + var currentLanguage = languages.FindByCulture( + CultureInfo.CurrentCulture.Name, + CultureInfo.CurrentUICulture.Name + ); + + if (currentLanguage == null) + { + var localizationOptions = await RequestLocalizationOptionsProvider.GetLocalizationOptionsAsync(); + if (localizationOptions.DefaultRequestCulture != null) + { + currentLanguage = new LanguageInfo( + localizationOptions.DefaultRequestCulture.Culture.Name, + localizationOptions.DefaultRequestCulture.UICulture.Name, + localizationOptions.DefaultRequestCulture.UICulture.DisplayName); + } + else + { + currentLanguage = new LanguageInfo( + CultureInfo.CurrentCulture.Name, + CultureInfo.CurrentUICulture.Name, + CultureInfo.CurrentUICulture.DisplayName); + } + } + + _currentLanguage = currentLanguage; + _otherLanguages = languages.Where(l => l != _currentLanguage).ToImmutableList(); + } + + private void ChangeLanguage(LanguageInfo language) + { + var relativeUrl = NavigationManager.Uri.RemovePreFix(NavigationManager.BaseUri).EnsureStartsWith('/').EnsureStartsWith('~'); + + NavigationManager.NavigateTo( + $"Abp/Languages/Switch?culture={language.CultureName}&uiCulture={language.UiCultureName}&returnUrl={relativeUrl}", + forceLoad: true + ); + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Themes/Basic/LoginDisplay.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Themes/Basic/LoginDisplay.razor new file mode 100644 index 0000000000..295080fa04 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Themes/Basic/LoginDisplay.razor @@ -0,0 +1,44 @@ +@namespace Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Themes.Basic +@using Volo.Abp.Users +@using Volo.Abp.MultiTenancy +@using Microsoft.Extensions.Localization +@using global::Localization.Resources.AbpUi +@using Microsoft.AspNetCore.Components.Authorization +@using Volo.Abp.UI.Navigation +@implements IDisposable +@inject ICurrentUser CurrentUser +@inject ICurrentTenant CurrentTenant +@inject NavigationManager Navigation +@inject IStringLocalizer L + + + + + + + + + + @if (CurrentTenant.Name != null) + { + @CurrentTenant.Name\@CurrentUser.UserName + } + else + { + @CurrentUser.UserName + } + + @if (Menu != null) + { + @foreach (var menuItem in Menu.Items) + { + @menuItem.DisplayName + } + } + + + + + @L["Login"] + + diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Themes/Basic/LoginDisplay.razor.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Themes/Basic/LoginDisplay.razor.cs new file mode 100644 index 0000000000..ea1db6a03c --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Themes/Basic/LoginDisplay.razor.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Routing; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.RequestLocalization; +using Volo.Abp.UI.Navigation; + +namespace Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Themes.Basic; + +public partial class LoginDisplay : IDisposable +{ + [Inject] + protected IMenuManager MenuManager { get; set; } + + [Inject] + protected IHttpContextAccessor HttpContextAccessor { get; set; } + + protected ApplicationMenu Menu { get; set; } + + protected override async Task OnInitializedAsync() + { + Menu = await MenuManager.GetAsync(StandardMenus.User); + + Navigation.LocationChanged += OnLocationChanged; + } + + protected string GetLoginUrl() + { + var culture = AbpRequestCultureCookieHelper.GetRouteCulture(HttpContextAccessor.HttpContext); + return string.IsNullOrEmpty(culture) ? "Account/Login" : $"{culture}/Account/Login"; + } + + protected virtual void OnLocationChanged(object sender, LocationChangedEventArgs e) + { + InvokeAsync(StateHasChanged); + } + + public void Dispose() + { + Navigation.LocationChanged -= OnLocationChanged; + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Themes/Basic/_Imports.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Themes/Basic/_Imports.razor new file mode 100644 index 0000000000..4e91324cd8 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Themes/Basic/_Imports.razor @@ -0,0 +1,4 @@ +@using Microsoft.AspNetCore.Components.Web +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor +@using MudBlazor +@using Volo.Abp.MudBlazorUI diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.csproj b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.csproj new file mode 100644 index 0000000000..82741a876a --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.csproj @@ -0,0 +1,19 @@ + + + + + + + net10.0 + + + + + + + + + + + + diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/AbpAspNetCoreComponentsWebMudBlazorBasicThemeModule.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/AbpAspNetCoreComponentsWebMudBlazorBasicThemeModule.cs new file mode 100644 index 0000000000..d0bee588e6 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/AbpAspNetCoreComponentsWebMudBlazorBasicThemeModule.cs @@ -0,0 +1,24 @@ +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Theming; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme; + +[DependsOn( + typeof(AbpAspNetCoreComponentsWebThemingMudBlazorModule) +)] +public class AbpAspNetCoreComponentsWebMudBlazorBasicThemeModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Themes.Add(); + + if (options.DefaultThemeName == null) + { + options.DefaultThemeName = MudBlazorBasicTheme.Name; + } + }); + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/FodyWeavers.xml b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/FodyWeavers.xsd b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/MudBlazorBasicTheme.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/MudBlazorBasicTheme.cs new file mode 100644 index 0000000000..a777bcfc32 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/MudBlazorBasicTheme.cs @@ -0,0 +1,26 @@ +using System; +using Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme.Themes.Basic; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Layout; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Theming; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme; + +[ThemeName(Name)] +public class MudBlazorBasicTheme : ITheme, ITransientDependency +{ + public const string Name = "MudBlazorBasic"; + + public virtual Type GetLayout(string name, bool fallbackToDefault = true) + { + switch (name) + { + case StandardLayouts.Application: + case StandardLayouts.Account: + case StandardLayouts.Empty: + return typeof(MainLayout); + default: + return fallbackToDefault ? typeof(MainLayout) : typeof(NullLayout); + } + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/App.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/App.razor new file mode 100644 index 0000000000..84edd134d0 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/App.razor @@ -0,0 +1,28 @@ +@using Microsoft.Extensions.Options +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing +@using global::MudBlazor +@inject IOptions RouterOptions + + + + + + @if (!context.User.Identity.IsAuthenticated) + { + + } + else + { + You are not authorized to access this resource. + } + + + + + + Sorry, there's nothing at this address. + + + + diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/AppWithoutAuth.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/AppWithoutAuth.razor new file mode 100644 index 0000000000..1c1d1b1366 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/AppWithoutAuth.razor @@ -0,0 +1,14 @@ +@using Microsoft.Extensions.Options +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing +@inject IOptions RouterOptions + + + + + + +

Sorry, there's nothing at this address.

+
+
+
diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/MainLayout.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/MainLayout.razor new file mode 100644 index 0000000000..2bcfa890bc --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/MainLayout.razor @@ -0,0 +1,53 @@ +@using Volo.Abp.Ui.LayoutHooks +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Components.LayoutHooks +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Components +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Layout +@using Volo.Abp.Ui.Branding +@inherits LayoutComponentBase +@inject IBrandingProvider BrandingProvider + + + + + + + + + + @BrandingProvider.AppName + + + + + + + + + + + + + @Body + + + + + + + + + +@code { + private bool _drawerOpen = true; + + private void ToggleDrawer() + { + _drawerOpen = !_drawerOpen; + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/NavMenu.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/NavMenu.razor new file mode 100644 index 0000000000..1ffc0db3a3 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/NavMenu.razor @@ -0,0 +1,19 @@ + + @if(Menu != null && !Menu.Items.IsNullOrEmpty()) + { + foreach (var menuItem in Menu.Items) + { + if (menuItem.Items.IsNullOrEmpty()) + { + + + @menuItem.DisplayName + + } + else + { + + } + } + } + diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/NavMenu.razor.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/NavMenu.razor.cs new file mode 100644 index 0000000000..8387850970 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/NavMenu.razor.cs @@ -0,0 +1,38 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Volo.Abp.AspNetCore.Components.Web.Security; +using Volo.Abp.UI.Navigation; + +namespace Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme.Themes.Basic; + +public partial class NavMenu : IDisposable +{ + [Inject] + protected IMenuManager MenuManager { get; set; } + + [Inject] + protected ApplicationConfigurationChangedService ApplicationConfigurationChangedService { get; set; } + + protected ApplicationMenu Menu { get; set; } + + protected async override Task OnInitializedAsync() + { + Menu = await MenuManager.GetMainMenuAsync(); + ApplicationConfigurationChangedService.Changed += ApplicationConfigurationChanged; + } + + private void ApplicationConfigurationChanged() + { + _ = InvokeAsync(async () => + { + Menu = await MenuManager.GetMainMenuAsync(); + StateHasChanged(); + }); + } + + public void Dispose() + { + ApplicationConfigurationChangedService.Changed -= ApplicationConfigurationChanged; + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/NavMenuItem.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/NavMenuItem.razor new file mode 100644 index 0000000000..20d854dcd4 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/NavMenuItem.razor @@ -0,0 +1,78 @@ +@implements IDisposable +@using Volo.Abp.UI.Navigation +@using MudBlazor +@using Microsoft.AspNetCore.Components +@using Microsoft.AspNetCore.Components.Routing +@using System +@{ + var customComponentType = MenuItem.GetComponentTypeOrDefault(); +} + +@if (customComponentType != null && typeof(ComponentBase).IsAssignableFrom(customComponentType)) +{ + +} +else +{ + + + + @MenuItem.DisplayName + + + @foreach (var item in MenuItem.Items) + { + @if (item.Items.Any()) + { + + } + else + { + + + @item.DisplayName + + } + } + + +} + +@code { + [Parameter] + public ApplicationMenuItem MenuItem { get; set; } = default!; + + [Inject] + private NavigationManager NavigationManager { get; set; } = default!; + + protected override void OnInitialized() + { + NavigationManager.LocationChanged += OnLocationChanged; + } + + protected virtual void OnLocationChanged(object? sender, LocationChangedEventArgs e) + { + // Menu will close automatically on navigation + } + + protected virtual bool IsExpanded(ApplicationMenuItem item) + { + if (!item.Url.IsNullOrEmpty()) + { + var menuItemPath = item.Url.Replace("~/", string.Empty).Trim('/'); + var currentPagePath = new Uri(NavigationManager.Uri.TrimEnd('/')).AbsolutePath.Trim('/'); + + if (menuItemPath.TrimEnd('/').Equals(currentPagePath, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return item.Items.Any(IsExpanded); + } + + public void Dispose() + { + NavigationManager.LocationChanged -= OnLocationChanged; + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/NavToolbar.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/NavToolbar.razor new file mode 100644 index 0000000000..91ded03c1e --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/NavToolbar.razor @@ -0,0 +1,60 @@ +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Toolbars +@using Volo.Abp.AspNetCore.Components.Web.Security +@using System.Collections.Generic +@using System +@using Microsoft.AspNetCore.Components +@using MudBlazor + +@implements IDisposable + +@foreach (var render in ToolbarItemRenders) +{ + @render +} + +@code { + [Inject] + private IToolbarManager ToolbarManager { get; set; } = default!; + + [Inject] + protected ApplicationConfigurationChangedService ApplicationConfigurationChangedService { get; set; } = default!; + + private List ToolbarItemRenders { get; set; } = new List(); + + protected async override Task OnInitializedAsync() + { + await GetToolbarItemRendersAsync(); + ApplicationConfigurationChangedService.Changed += ApplicationConfigurationChanged; + } + + private async Task GetToolbarItemRendersAsync() + { + var toolbar = await ToolbarManager.GetAsync(StandardToolbars.Main); + + ToolbarItemRenders.Clear(); + + var sequence = 0; + foreach (var item in toolbar.Items) + { + ToolbarItemRenders.Add(builder => + { + builder.OpenComponent(sequence++, item.ComponentType); + builder.CloseComponent(); + }); + } + } + + private void ApplicationConfigurationChanged() + { + _ = InvokeAsync(async () => + { + await GetToolbarItemRendersAsync(); + StateHasChanged(); + }); + } + + public void Dispose() + { + ApplicationConfigurationChangedService.Changed -= ApplicationConfigurationChanged; + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/NullLayout.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/NullLayout.razor new file mode 100644 index 0000000000..ad53deb649 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/NullLayout.razor @@ -0,0 +1,22 @@ +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Components +@using Volo.Abp.Ui.LayoutHooks +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Components.LayoutHooks +@inherits LayoutComponentBase +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Layout + + + + + + + + + + @Body + + + + + + + diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/RedirectToLogin.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/RedirectToLogin.razor new file mode 100644 index 0000000000..d241d93470 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/RedirectToLogin.razor @@ -0,0 +1,9 @@ +@inject NavigationManager Navigation +@using System + +@code { + protected override void OnInitialized() + { + Navigation.NavigateTo($"account/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}", true); + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/_Imports.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/_Imports.razor new file mode 100644 index 0000000000..f291088477 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Themes/Basic/_Imports.razor @@ -0,0 +1,9 @@ +@using MudBlazor +@using System.Net.Http +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.JSInterop +@using Volo.Abp.MudBlazorUI +@using Volo.Abp.MudBlazorUI.Components \ No newline at end of file diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme.csproj b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme.csproj new file mode 100644 index 0000000000..03c8c3477a --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme.csproj @@ -0,0 +1,14 @@ + + + + + + + net10.0 + + + + + + + diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/wwwroot/themes/basic/main.css b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/wwwroot/themes/basic/main.css new file mode 100644 index 0000000000..03d1488842 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/wwwroot/themes/basic/main.css @@ -0,0 +1,3 @@ +/* MudBlazor Basic Theme Styles */ + +/* Add any custom theme-specific styles here */ diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling/AbpAspNetCoreComponentsWebAssemblyMudBlazorBasicThemeBundlingModule.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling/AbpAspNetCoreComponentsWebAssemblyMudBlazorBasicThemeBundlingModule.cs new file mode 100644 index 0000000000..730f3936fc --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling/AbpAspNetCoreComponentsWebAssemblyMudBlazorBasicThemeBundlingModule.cs @@ -0,0 +1,20 @@ +using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling; + +[DependsOn( + typeof(AbpAspNetCoreComponentsWebAssemblyThemingMudBlazorBundlingModule) +)] +public class AbpAspNetCoreComponentsWebAssemblyMudBlazorBasicThemeBundlingModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + var globalStyles = options.StyleBundles.Get(BlazorWebAssemblyMudBlazorStandardBundles.Styles.Global); + globalStyles.AddContributors(typeof(WebAssemblyMudBlazorBasicThemeBundleStyleContributor)); + }); + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling/FodyWeavers.xml b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling/FodyWeavers.xsd b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling.csproj b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling.csproj new file mode 100644 index 0000000000..c71770c813 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling.csproj @@ -0,0 +1,15 @@ + + + + + + + net10.0 + Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling + + + + + + + diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling/WebAssemblyMudBlazorBasicThemeBundleStyleContributor.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling/WebAssemblyMudBlazorBasicThemeBundleStyleContributor.cs new file mode 100644 index 0000000000..3fed44f2a0 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling/WebAssemblyMudBlazorBasicThemeBundleStyleContributor.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling; + +public class WebAssemblyMudBlazorBasicThemeBundleStyleContributor : BundleContributor +{ + public override void ConfigureBundle(BundleConfigurationContext context) + { + context.Files.AddIfNotContains("_content/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme/themes/basic/main.css"); + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/AbpAspNetCoreComponentsWebAssemblyMudBlazorBasicThemeModule.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/AbpAspNetCoreComponentsWebAssemblyMudBlazorBasicThemeModule.cs new file mode 100644 index 0000000000..907dba6e43 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/AbpAspNetCoreComponentsWebAssemblyMudBlazorBasicThemeModule.cs @@ -0,0 +1,42 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Components.Web; +using Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Toolbars; +using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling; +using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor; +using Volo.Abp.Http.Client.IdentityModel.WebAssembly; +using Volo.Abp.Modularity; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme; + +[DependsOn( + typeof(AbpAspNetCoreComponentsWebAssemblyMudBlazorBasicThemeBundlingModule), + typeof(AbpAspNetCoreComponentsWebMudBlazorBasicThemeModule), + typeof(AbpAspNetCoreComponentsWebAssemblyThemingMudBlazorModule), + typeof(AbpHttpClientIdentityModelWebAssemblyModule) + )] +public class AbpAspNetCoreComponentsWebAssemblyMudBlazorBasicThemeModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.AdditionalAssemblies.Add(typeof(AbpAspNetCoreComponentsWebAssemblyMudBlazorBasicThemeModule).Assembly); + }); + + Configure(options => + { + options.Contributors.Add(new MudBlazorBasicThemeToolbarContributor()); + }); + + if (context.Services.ExecutePreConfiguredActions().IsBlazorWebApp) + { + Configure(options => + { + options.LoginUrl = "Account/Login"; + options.LogoutUrl = "Account/Logout"; + }); + } + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/AuthenticationOptions.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/AuthenticationOptions.cs new file mode 100644 index 0000000000..9ca7223a83 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/AuthenticationOptions.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme; + +public class AuthenticationOptions +{ + public string LoginUrl { get; set; } = "authentication/login"; + + public string LogoutUrl { get; set; } = "authentication/logout"; +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/FodyWeavers.xml b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/FodyWeavers.xsd b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/MudBlazorBasicThemeToolbarContributor.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/MudBlazorBasicThemeToolbarContributor.cs new file mode 100644 index 0000000000..31282207a4 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/MudBlazorBasicThemeToolbarContributor.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Toolbars; +using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Themes.Basic; +using LoginDisplay = Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Themes.Basic.LoginDisplay; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme; + +public class MudBlazorBasicThemeToolbarContributor : IToolbarContributor +{ + public Task ConfigureToolbarAsync(IToolbarConfigurationContext context) + { + if (context.Toolbar.Name == StandardToolbars.Main) + { + context.Toolbar.Items.Add(new ToolbarItem(typeof(LanguageSwitch))); + + //TODO: Can we find a different way to understand if authentication was configured or not? + var authenticationStateProvider = context.ServiceProvider + .GetService(); + + if (authenticationStateProvider != null) + { + context.Toolbar.Items.Add(new ToolbarItem(typeof(LoginDisplay))); + } + } + + return Task.CompletedTask; + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Pages/Authentication.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Pages/Authentication.razor new file mode 100644 index 0000000000..dae4938580 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Pages/Authentication.razor @@ -0,0 +1,20 @@ +@page "/authentication/{action}" +@page "/{culture}/authentication/{action}" +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication +@using Volo.Abp.AspNetCore.Components.Web.Security + +@inject WebAssemblyCachedApplicationConfigurationClient WebAssemblyCachedApplicationConfigurationClient +@code{ + [Parameter] public string? Culture { get; set; } + [Parameter] public string? Action { get; set; } + + private async Task OnLogInSucceeded(RemoteAuthenticationState state) + { + await WebAssemblyCachedApplicationConfigurationClient.InitializeAsync(); + } + + private async Task OnLogOutSucceeded(RemoteAuthenticationState state) + { + await WebAssemblyCachedApplicationConfigurationClient.InitializeAsync(); + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Themes/Basic/LanguageSwitch.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Themes/Basic/LanguageSwitch.razor new file mode 100644 index 0000000000..7d059a91ff --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Themes/Basic/LanguageSwitch.razor @@ -0,0 +1,90 @@ +@using Volo.Abp.Localization +@using System.Globalization +@using System.Collections.Immutable +@using Microsoft.JSInterop +@using Volo.Abp.AspNetCore.Components.Web +@using MudBlazor +@inject ILanguageProvider LanguageProvider +@inject IJSRuntime JsRuntime +@inject ICookieService CookieService + +@if (_otherLanguages != null && _otherLanguages.Any()) +{ + + + + + + @_currentLanguage.DisplayName + + @foreach (var language in _otherLanguages) + { + @language.DisplayName + } + + +} + +@code { + private IReadOnlyList? _otherLanguages; + private LanguageInfo? _currentLanguage; + + protected override async Task OnInitializedAsync() + { + var selectedLanguageName = await JsRuntime.InvokeAsync( + "localStorage.getItem", + "Abp.SelectedLanguage" + ); + + var allLanguages = await LanguageProvider.GetLanguagesAsync(); + _otherLanguages = allLanguages; + + if (!_otherLanguages.Any()) + { + return; + } + + if (!selectedLanguageName.IsNullOrWhiteSpace()) + { + _currentLanguage = _otherLanguages.FirstOrDefault(l => l.UiCultureName == selectedLanguageName); + } + + if (_currentLanguage == null) + { + _currentLanguage = _otherLanguages.FirstOrDefault(l => l.UiCultureName == CultureInfo.CurrentUICulture.Name); + } + + if (_currentLanguage == null) + { + _currentLanguage = _otherLanguages.FirstOrDefault(); + } + + _otherLanguages = _otherLanguages.Where(l => l != _currentLanguage).ToImmutableList(); + } + + private async Task ChangeLanguageAsync(LanguageInfo language) + { + await JsRuntime.InvokeVoidAsync( + "localStorage.setItem", + "Abp.SelectedLanguage", + language.UiCultureName + ); + + await JsRuntime.InvokeVoidAsync( + "localStorage.setItem", + "Abp.IsRtl", + CultureInfo.GetCultureInfo(language.UiCultureName).TextInfo.IsRightToLeft + ); + + await CookieService.SetAsync( + ".AspNetCore.Culture", + $"c={language.CultureName}|uic={language.UiCultureName}", + new CookieOptions + { + Path = "/" + } + ); + + await JsRuntime.InvokeVoidAsync("location.reload"); + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Themes/Basic/LoginDisplay.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Themes/Basic/LoginDisplay.razor new file mode 100644 index 0000000000..1cb4c41718 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Themes/Basic/LoginDisplay.razor @@ -0,0 +1,49 @@ +@using Microsoft.Extensions.Localization +@using global::Localization.Resources.AbpUi +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.Extensions.Options +@using Microsoft.JSInterop +@using Volo.Abp.AspNetCore.Components.Web +@using Volo.Abp.AspNetCore.Components.Web.Security +@using Volo.Abp.UI.Navigation +@implements IDisposable +@inherits AbpComponentBase +@inject IJSRuntime JsRuntime +@inject NavigationManager Navigation +@inject IStringLocalizer UiLocalizer +@inject IOptions AuthenticationOptions +@inject IOptions AbpAspNetCoreComponentsWebOptions + + + + + + + + + + @if (CurrentTenant.Name != null) + { + @CurrentTenant.Name\@CurrentUser.UserName + } + else + { + @CurrentUser.UserName + } + + @if (Menu != null) + { + @foreach (var menuItem in Menu.Items) + { + @menuItem.DisplayName + } + } + + @UiLocalizer["Logout"] + + + + + @UiLocalizer["Login"] + + diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Themes/Basic/LoginDisplay.razor.cs b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Themes/Basic/LoginDisplay.razor.cs new file mode 100644 index 0000000000..32c60bd407 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Themes/Basic/LoginDisplay.razor.cs @@ -0,0 +1,71 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Routing; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; +using Microsoft.JSInterop; +using Volo.Abp.AspNetCore.Components.Web.Security; +using Volo.Abp.UI.Navigation; + +namespace Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Themes.Basic; + +public partial class LoginDisplay : IDisposable +{ + [Inject] + protected IMenuManager MenuManager { get; set; } + + [Inject] + protected ApplicationConfigurationChangedService ApplicationConfigurationChangedService { get; set; } + + protected ApplicationMenu Menu { get; set; } + + protected async override Task OnInitializedAsync() + { + Menu = await MenuManager.GetAsync(StandardMenus.User); + + Navigation.LocationChanged += OnLocationChanged; + + ApplicationConfigurationChangedService.Changed += ApplicationConfigurationChanged; + } + + protected virtual void OnLocationChanged(object sender, LocationChangedEventArgs e) + { + InvokeAsync(StateHasChanged); + } + + private async void ApplicationConfigurationChanged() + { + Menu = await MenuManager.GetAsync(StandardMenus.User); + await InvokeAsync(StateHasChanged); + } + + public void Dispose() + { + Navigation.LocationChanged -= OnLocationChanged; + ApplicationConfigurationChangedService.Changed -= ApplicationConfigurationChanged; + } + + private async Task NavigateToAsync(string uri, string target = null) + { + if (target == "_blank") + { + await JsRuntime.InvokeVoidAsync("open", uri, target); + } + else + { + Navigation.NavigateTo(uri); + } + } + + private void BeginSignOut() + { + if (AbpAspNetCoreComponentsWebOptions.Value.IsBlazorWebApp) + { + Navigation.NavigateTo(AuthenticationOptions.Value.LogoutUrl, forceLoad: true); + } + else + { + Navigation.NavigateToLogout(AuthenticationOptions.Value.LogoutUrl); + } + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Themes/Basic/WebAssemblyRedirectToLogin.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Themes/Basic/WebAssemblyRedirectToLogin.razor new file mode 100644 index 0000000000..e89235d49c --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Themes/Basic/WebAssemblyRedirectToLogin.razor @@ -0,0 +1,22 @@ +@using Volo.Abp.DependencyInjection +@using Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme.Themes.Basic +@using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor +@using Microsoft.Extensions.Options +@using Volo.Abp.AspNetCore.Components.WebAssembly +@using Volo.Abp.AspNetCore.Components.Web +@inherits RedirectToLogin +@attribute [ExposeServices(typeof(RedirectToLogin))] +@attribute [Dependency(ReplaceServices = true)] +@inject NavigationManager Navigation +@inject IOptions AuthOptions +@inject IRouteBasedCultureUrlHelper CultureUrlHelper +@inject IOptions AbpAspNetCoreComponentsWebOptions + +@code { + protected override void OnInitialized() { } + + protected override Task OnInitializedAsync() + { + return CultureAwareRedirectToLoginHelper.RedirectAsync(Navigation, AuthOptions.Value.LoginUrl, CultureUrlHelper, AbpAspNetCoreComponentsWebOptions); + } +} diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Themes/Basic/_Imports.razor b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Themes/Basic/_Imports.razor new file mode 100644 index 0000000000..4e91324cd8 --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Themes/Basic/_Imports.razor @@ -0,0 +1,4 @@ +@using Microsoft.AspNetCore.Components.Web +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor +@using MudBlazor +@using Volo.Abp.MudBlazorUI diff --git a/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.csproj b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.csproj new file mode 100644 index 0000000000..ef36174d1b --- /dev/null +++ b/modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.csproj @@ -0,0 +1,18 @@ + + + + + + + net10.0 + Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme + + + + + + + + + + diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json index b7ec085115..243fc50605 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json @@ -3,8 +3,8 @@ "name": "asp.net", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.shared": "~10.3.0", - "@abp/prismjs": "~10.3.0", - "@abp/highlight.js": "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.shared": "~10.4.0", + "@abp/prismjs": "~10.4.0", + "@abp/highlight.js": "~10.4.0" } } diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/yarn.lock b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/yarn.lock index 010fc908dc..0686fa964b 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/yarn.lock +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/yarn.lock @@ -2,204 +2,204 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.shared@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.3.0.tgz#bcf72b5dd1dea285c3fa7cb1b6ce27c29b985ff7" - integrity sha512-X5xmMSxnIzNJPexvKR9y6jyn0v5n2wscN1SECG/bMI1YdKugMlkfkSuyUmLn+RX9/YdcK0jOsdMhfuzs5mgAOA== - dependencies: - "@abp/aspnetcore.mvc.ui" "~10.3.0" - "@abp/bootstrap" "~10.3.0" - "@abp/bootstrap-datepicker" "~10.3.0" - "@abp/bootstrap-daterangepicker" "~10.3.0" - "@abp/datatables.net-bs5" "~10.3.0" - "@abp/font-awesome" "~10.3.0" - "@abp/jquery-validation-unobtrusive" "~10.3.0" - "@abp/lodash" "~10.3.0" - "@abp/luxon" "~10.3.0" - "@abp/malihu-custom-scrollbar-plugin" "~10.3.0" - "@abp/moment" "~10.3.0" - "@abp/select2" "~10.3.0" - "@abp/sweetalert2" "~10.3.0" - "@abp/timeago" "~10.3.0" - -"@abp/aspnetcore.mvc.ui@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.3.0.tgz#0d125efd172b6772c516eae82cfe35f144da73fc" - integrity sha512-kp0XdwrlNz+NeLdxSJ6xdUaB5PM7xdbiLsIyTUcwa7AUKBtcVU6yq6qttbk9VyO0KqDgLywSuEL+9PkFhthuPg== +"@abp/aspnetcore.mvc.ui.theme.shared@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.4.0.tgz#49002e55db00c98931d27357f7ea40eeab09e2e8" + integrity sha512-bNQ+WOnsfQhb98Z9YO0zZsW2K8ZEgOQ+7/86QaqoYTf+ZFvLJsICmNuHryNkCGRM1XJf9EaDZ3qSOwoOGQwS9A== + dependencies: + "@abp/aspnetcore.mvc.ui" "~10.4.0" + "@abp/bootstrap" "~10.4.0" + "@abp/bootstrap-datepicker" "~10.4.0" + "@abp/bootstrap-daterangepicker" "~10.4.0" + "@abp/datatables.net-bs5" "~10.4.0" + "@abp/font-awesome" "~10.4.0" + "@abp/jquery-validation-unobtrusive" "~10.4.0" + "@abp/lodash" "~10.4.0" + "@abp/luxon" "~10.4.0" + "@abp/malihu-custom-scrollbar-plugin" "~10.4.0" + "@abp/moment" "~10.4.0" + "@abp/select2" "~10.4.0" + "@abp/sweetalert2" "~10.4.0" + "@abp/timeago" "~10.4.0" + +"@abp/aspnetcore.mvc.ui@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.4.0.tgz#24e40fdc101c411f29a87adb77baa642299e979c" + integrity sha512-0qVFthS4f4fkCv//UY2GseO+NX+XYqlamZJJwOTmv1UXFW6LxAXxuEQqWYVfFKzwk2lbkXDUcgP8NhhQ9xp3Sw== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.3.0.tgz#032aaf0e474f971b9f90ca7fb83ff7ca4d7be933" - integrity sha512-7mRuRRIE4R0yw1cZuoIskgrqZPUa5/SJzse0K6u+/QDqvyLjjOsyE51WuqAXxlVfKpLp/fBvXZwnCDeCRU9iZQ== +"@abp/bootstrap-datepicker@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.4.0.tgz#a392fd5ce31c86e9fc07f7dedbd0c56a02af341e" + integrity sha512-kOpuCEO64zoJ7a9JASsCLFZIjEYgN0GG/oApS2L729pv6/oEhcupWpwrnvDUhsdGOezXwLIsgTzc6tiG3HxwGA== dependencies: bootstrap-datepicker "^1.10.1" -"@abp/bootstrap-daterangepicker@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.3.0.tgz#b2a9171595ee311c32f57460013870cde67c6b7f" - integrity sha512-ejLu3sWhfNDlsXeUXAQudZvPv0xM4McxIK65vuVEZiKIwFSnrLYil8jBi12EJJmAw5yYA/KC0EdyG82E+AWpmA== +"@abp/bootstrap-daterangepicker@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.4.0.tgz#95f68d3f488a12ede3c12352637b48782eeb031a" + integrity sha512-HX4HutkbPDQWBJNPDozpT1Um9mHcfV+RL+ukwEy0N4e7nD8VJyqLT/a+/FzQN7iUkwV+aNR2v5c0W+dgyqTaEA== dependencies: - "@abp/moment" "~10.3.0" + "@abp/moment" "~10.4.0" bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.3.0.tgz#23535c90613271b02fdad3eae1556b67844729e6" - integrity sha512-JnVOeJUyR78oo+QURvaNqa9xGZaVux0PRIpFpd4Qsqiiz9FvJrbCuSoBkkZoj/0eH23WoZ3bJxy0K9LDE19qvg== +"@abp/bootstrap@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.4.0.tgz#64b22bbdbf6b5bda279b21a7bae0652290b1221f" + integrity sha512-WQzXi5h1mej9twrAB6kD4aJtTNfSxpnuo1pxIOu/8GKGWoDayVY2NMQl+MCVel6dST+6EUTmuDRxc2q9qHRxLg== dependencies: - "@abp/core" "~10.3.0" - "@abp/popper.js" "~10.3.0" + "@abp/core" "~10.4.0" + "@abp/popper.js" "~10.4.0" bootstrap "^5.3.8" -"@abp/clipboard@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-10.3.0.tgz#7bfbfcae6a135065ee13afd1b7d3abc92176a203" - integrity sha512-Cah+jOzcG1KZh6PXWxo+BedU7gsqTuNlTDsCzBCk2NuV2/Blb5qumAwEP4GBKAEIY/XwcFQP/ubxuXMTNjr0jw== +"@abp/clipboard@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-10.4.0.tgz#bac5854af4f8bcaa987d39f234edd58b675a71c8" + integrity sha512-KTTzCDEe+wjpGLdud8MHlwnPr/NdzFDmfHtENgESdIa5KilGOwy2wv24lQ5cyJOB4ftoJzkBaHinKJxg/KU0pw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" clipboard "^2.0.11" -"@abp/core@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.3.0.tgz#9960f8179d1fdc42250d0bbba60e379590c8199c" - integrity sha512-DpL4qrsyCUolypUwxKVDDK8bPVhNbYKx6l2ytUjoOu73CwgPoCSGkvHWrmKIsEsmTfOC9J7+GDErh8jWR/5pVA== +"@abp/core@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.4.0.tgz#de0c09e26d1f20451da9430d939beebb5bb6e9dd" + integrity sha512-3yKLFqUtFo5rgBPQxWkNPsdq1bE9VKwm02yB14lu913EIYhP//akizY4uSK08BozocJNveCASQbgd66z4x6v/Q== dependencies: - "@abp/utils" "~10.3.0" + "@abp/utils" "~10.4.0" -"@abp/datatables.net-bs5@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.3.0.tgz#d8711c52f7c03df51d14d1a5f0e6b00f6d9551fc" - integrity sha512-I8rEU98kEhsNZjVdpPvOcG6F4xr1CgIRb0bTf5bfd8ufzxEUKB+xF9bguJIB5kYwjme8QbMlcJ0V/c+N4iewtQ== +"@abp/datatables.net-bs5@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.4.0.tgz#a24eef99399324e4b89a00a204d2c4c2de379da1" + integrity sha512-T4f7e6sR1qHcvygNUNfdPWsK61C3NQORV35Lzpl98eDwY8XLg2/RLAegpFI8GjESVy2+2PCKVqUoSYZtXaMFmw== dependencies: - "@abp/datatables.net" "~10.3.0" + "@abp/datatables.net" "~10.4.0" datatables.net-bs5 "^2.3.4" -"@abp/datatables.net@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.3.0.tgz#f98319d8e226da5aced4d1d94d3ababb11327074" - integrity sha512-imM25WmO1V0hqeG5iPeLvYMID2HX7SG9drz47qilr74MhpY2i2J7HlsvZDonNkjpxzhK4DHpGeQd6mE2QzG7Hw== +"@abp/datatables.net@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.4.0.tgz#ab48f6e3ccae8d0644f0c01011ff98cc6ca16181" + integrity sha512-esIUozaSEa29G7bdWbEZr22fRChaTuGfNNB8rgBHmVKwgkbHSG7qUTgsfGhNz0T2vfDsK3X37n55xmSdE9RElg== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" datatables.net "^2.3.4" -"@abp/font-awesome@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.3.0.tgz#43602605f5633132cc6fe21b6ce5ab19b86bdf27" - integrity sha512-RUOVHxDyT81hp01DhUuSshKvLM+NiTt9VP+O4/rSj+SOaSJAm3wX7eziCKrDi6H41FwhoZL75CifLhcZVyVO4A== +"@abp/font-awesome@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.4.0.tgz#327979191615fff571f0ea2e99d01bbcca5378ce" + integrity sha512-RBzDSi/hrjhLKc7h6Kf9CQqC+LCGvS7h8HrRVaIzWCZSeqTfIMFpixjCUrNgkqWI0bU4NDUKXZ4XCOv4nPVlBg== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" "@fortawesome/fontawesome-free" "^7.0.1" -"@abp/highlight.js@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/highlight.js/-/highlight.js-10.3.0.tgz#1b04355e84eba15c395cdfc7055c8d610296206b" - integrity sha512-cqApQta9gEs03jk/s+yQsm1mnHGNDZPHnkGYgxYKI41Nl9meV6+5Dkz0b4gnQoUKXvFlmuOuf2DOjot7a2yB7g== +"@abp/highlight.js@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/highlight.js/-/highlight.js-10.4.0.tgz#b1b53329743f3eeb203ec35ebcf9c6e79c9fbe93" + integrity sha512-L0mL/AErFsYdJbCLUu22lm/fn+Eelc0H57OQrUAzVy+22XXALiFtyq/cci3sjOcxfpf2CW/v+z0JKCouuJd7Ag== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" "@highlightjs/cdn-assets" "~11.11.1" -"@abp/jquery-validation-unobtrusive@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.3.0.tgz#5bc14ed642d48f9eae54761ad39bd9f51beeb005" - integrity sha512-j/9fA7tAs1/t3P2U1kKUiDlHAImmseul6KzvV77sNpnms7g4+x6Fgsa+sMzicSRGOLz7lK5RHdBFyvfKxt7/nA== +"@abp/jquery-validation-unobtrusive@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.4.0.tgz#e0114347344931667f48e0f2de5ea4bd6f9d2a88" + integrity sha512-AJncXVB+eTSizq0fTAwLii9a0GeYFPiY+gw+Ki5pak8Pp9LQivphsW32TqQmPC3qZVuYtd4myUUraWuOTT1Adg== dependencies: - "@abp/jquery-validation" "~10.3.0" + "@abp/jquery-validation" "~10.4.0" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.3.0.tgz#e669a54d28f6c0b863a284322e527a5c65cb7dbf" - integrity sha512-Wo95ZJLS1ScKm7dm3zFlEFUKqXrqo3m9+82DX5mFJUVCHHuBPrhxvDjxNJ18z4gpoOUu08uqxX7LFbphtkRM3g== +"@abp/jquery-validation@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.4.0.tgz#bd1ef0217c2500363f25223dd7316ba919a505b5" + integrity sha512-W9QiGnOpaq6uDDwBy4UEqWrP1p2UegHQShW5Cev9vr6g7Lk97UsS1Agh9lnjbvMzFSAiLl3M9gHmyMD4aU3NPA== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" jquery-validation "^1.21.0" -"@abp/jquery@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.3.0.tgz#3b60bcdfe8f9a33be79db1bea2500837fe09c67d" - integrity sha512-TndX/8bJx5vGAAwIykx2CJUZIFFDkwpD+cUjE45WO2dSrY2M5vNfWiuw95OcLGngO8RaT/gTkFGzPuZqKn9ggg== +"@abp/jquery@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.4.0.tgz#7ed3f1808682717ccfa7c6ac6e8db8a8b648b1e1" + integrity sha512-kjFoVIhqlaIHlEs917sIeP1sHmLWXMn+DVKaCkVQUXC2KVq+yvQRC8AZiFQVxOwLvzd9dFFpGwGZgpWkDfvhvg== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" jquery "~3.7.1" -"@abp/lodash@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.3.0.tgz#bd091fc811d85d2f535802631af6165ac1c8a198" - integrity sha512-7JDU9UlbD+9odhFQjolvyJZ6EQKG5kiL2Pt9T1RMVchNh47iEuZwfi8c+CIxa3YxA3n6lAsZ+8Pmxq4yOku+RQ== +"@abp/lodash@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.4.0.tgz#b6fc25b6e2fd74420ca16a5a13cb8fcd39b89efd" + integrity sha512-RuiIGAqJtSBS+4VAk4cg7Zsfj07B3xkiewCaK5j0xUcyjsCJKL1UdXe2zVNL3oE5bXTNMU7yNQWJS4knv2mfwA== dependencies: - "@abp/core" "~10.3.0" - lodash "^4.17.21" + "@abp/core" "~10.4.0" + lodash "^4.18.1" -"@abp/luxon@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.3.0.tgz#acbd92289456a546607ed4506dc36f71e6da53c9" - integrity sha512-IIi2+odQzuFK4qV1KJ2HtlTewSPby2F9CJFNNtNRMzX/P30dQkAGWpNV5/FZzJwFIihR+zSei5wlu11WpPugsw== +"@abp/luxon@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.4.0.tgz#731d20877ec679fa518b7199128676631d030a90" + integrity sha512-/hF+FBugJMkp/H9WNZxlsOy3Rl4DTeR2hqmRPI0sPcdFOixCziYrwzyHg/2Kx8cI/zHa6GRRt1B4Jz4yjQTvdw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" luxon "^3.7.2" -"@abp/malihu-custom-scrollbar-plugin@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.3.0.tgz#209931d02a994ef448371d6203e9ec74c72b61e6" - integrity sha512-hJjwEExP3hKE1MNDG+pcvoHRFUihh3t/0wCPNaoePFd8+QnJuyQ9U0VA+vdtykTxm8uFNE2uaCgyHffoHmzeZA== +"@abp/malihu-custom-scrollbar-plugin@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.4.0.tgz#e1c207d9c66cce64fcb4fe13c091c4b86b00b6d7" + integrity sha512-LC+imsGQganhKWybvQytATjQMXfQwfCfi6RRWREP+pejKPuHpqHzPd+AjKwZpx500K2/ZTwT62FKJ1uDiJXSKA== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.3.0.tgz#5a5c4ece14d1bc4dd33134f85d1d085733d3c975" - integrity sha512-8DKw2jgC/28nLrB/CR9BiyUSt0pUmWF+bEgAsduln6VrSCHn0aRLqgs+wi/9Ol+1bl7pO2wbJOYg1qbnkDhrAQ== +"@abp/moment@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.4.0.tgz#6737898b90b579ce5e0d683657757725a4def09d" + integrity sha512-WUtzjD6+1JGpgS6Yv2Qo/h4jM77LkkasPrywsetmtj/6zyVQWrkk7tKHTCjhAJtdinctcROwIIcwLsELi0nDHg== dependencies: moment "^2.30.1" -"@abp/popper.js@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-10.3.0.tgz#cb2415e45a905854980f4e864c00c8fe1156a6f9" - integrity sha512-Yfafs50t7DKnShxAl9WPpihZxG5cLAt99rJmIyXBYUQIpkHkxDyUIA5b4gCoVCqKgjFDOxG7i6L+Ovd/2N8k7g== +"@abp/popper.js@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-10.4.0.tgz#9d8d09c4e18020b6a75ba30daf6786606f555e84" + integrity sha512-KR5wGZ/MfR1rOPcmFr5ekraYhOSRul9gqempir69hlqrFKUFqt46F9/wLWyaJ7vlsMU6o4gBo6/jE5tICIpzNw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" "@popperjs/core" "^2.11.8" -"@abp/prismjs@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-10.3.0.tgz#4ea33ece50cefcfc8a89c17b0d265b484b7205ee" - integrity sha512-WvkLyg+izkcX6sEOYy41B9lebiniYxMQZ++c+ysyOQKZzrYEEFOOLlnFS9jPqSKLv9cNRizhG1CCfiwoYCxufQ== +"@abp/prismjs@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-10.4.0.tgz#0061d71d18252bc1e66e763aab6d81a7b4fe51b7" + integrity sha512-kjV11XNt2uP01zlhrrScr4WA27lgZ03Cz3svA7/HHnfhFy76MHYo+Dyn7sS4Gp3UobMcf1QWHl4KQNCUnYNTOQ== dependencies: - "@abp/clipboard" "~10.3.0" - "@abp/core" "~10.3.0" + "@abp/clipboard" "~10.4.0" + "@abp/core" "~10.4.0" prismjs "^1.30.0" -"@abp/select2@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.3.0.tgz#199777173ca3e43a5448f8b51b30c3aafe4a3333" - integrity sha512-6sEIYIakVI1DM3eOqOaxwdG8vkyVFBWvVZhoChPZkgIIQ0EH2JLerHv0pePJEl6mQSxOUrikPA8++B2y+sBnQA== +"@abp/select2@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.4.0.tgz#e744988abf81e2194a5850acc4ac1333e9578df8" + integrity sha512-tyAdGzX32joI5vw+MUJGR0iToWCGZrbQg/zKeoCib80Zx5cn+e6GXj6wZyPbsJgVm0jZ7zBweWt3x8HzW9N8Ww== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" select2 "^4.0.13" -"@abp/sweetalert2@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.3.0.tgz#515e0477574b54bc6650b9f5ee193bfc648c715a" - integrity sha512-bY5MXEiKja+vk71ujSn8rpzRC45Ge8zvcca1k1vBJs+oKyRCii//JBxa3H0lagZo5r/QwIqv5ihdyLE1xsuLSg== +"@abp/sweetalert2@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.4.0.tgz#65cb26c8c7c9c02158c9a487969eef033fe04a73" + integrity sha512-kyVMlfnI6MZojJlaRstdHj/CL3olOn4BszjQ155UQaDsGHUu0THmbhpfi4P0q3TCLLzcLM4pkuY3wJHPNl3ZOw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" sweetalert2 "^11.23.0" -"@abp/timeago@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.3.0.tgz#3214a6d000c79504c9fa7cb4e004a967fe46459b" - integrity sha512-Rgcex+jvM9Z9/E0I5Q3wlC2bY5550/+6xRX407k5ho4gxCccYo3c0Gkpiy5pJRFhTwtBgcNzYxArh6NCez+tDg== +"@abp/timeago@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.4.0.tgz#100fc0598e3a605346397f8a395bb96dc70f429d" + integrity sha512-G0ppO7Evai3DdhSg4ozxQpvIke0t3vyg63pNdfOtAPngmuam7WBM1WemBi4u29M84WcSfmk4KuAubXO0aCJgDg== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" timeago "^1.6.7" -"@abp/utils@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.3.0.tgz#d88c0d639d69d6210c3070892aac61d5e945c124" - integrity sha512-iNehxiqWJQl2wwE9EbsQANPv/ITMh0yUcPATUlG/McoLWdpPcqobx75NxXcHDqW8ZarecqSktjJDZPvaUaD+jA== +"@abp/utils@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.4.0.tgz#75175f798e58643c0dec1e85a502186c811e9226" + integrity sha512-BPc7KGDcbdFZ4GonAt3SOi/gYhG+iXI76TMlAzSKMBRhBU0vxFS8I/pb7U5zX0Gjh5M+aT0l2vA3l+POCHPBfw== dependencies: just-compare "^2.3.0" @@ -307,10 +307,10 @@ just-compare@^2.3.0: resolved "https://registry.yarnpkg.com/just-compare/-/just-compare-2.3.0.tgz#a2adcc1d1940536263275f5a1ef1298bcacfeda7" integrity sha512-6shoR7HDT+fzfL3gBahx1jZG3hWLrhPAf+l7nCwahDdT9XDtosB9kIF0ZrzUp5QY8dJWfQVr5rnsPqsbvflDzg== -lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lodash@^4.18.1: + version "4.18.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" + integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== luxon@^3.7.2: version "3.7.2" diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/package.json b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/package.json index d6105f02a8..432c74459b 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/package.json +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/package.json @@ -3,8 +3,8 @@ "name": "asp.net", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.3.0", - "@abp/prismjs": "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.4.0", + "@abp/prismjs": "~10.4.0" }, "devDependencies": {} } diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/yarn.lock b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/yarn.lock index 59dc5b16a6..7b7ba0a9a7 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/yarn.lock +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/yarn.lock @@ -2,203 +2,203 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.3.0.tgz#bf48e8fa571067d15bc19bdd3f13509acf58f9c7" - integrity sha512-t9pHqrXaCDYcsdrz0VPKT28ujnnU+EVxyVDogQSRRpKsDoLhNcOD8NI3swdAWBQZ46YSCDfeDR+gfwjC3yqpXQ== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~10.3.0" - -"@abp/aspnetcore.mvc.ui.theme.shared@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.3.0.tgz#bcf72b5dd1dea285c3fa7cb1b6ce27c29b985ff7" - integrity sha512-X5xmMSxnIzNJPexvKR9y6jyn0v5n2wscN1SECG/bMI1YdKugMlkfkSuyUmLn+RX9/YdcK0jOsdMhfuzs5mgAOA== - dependencies: - "@abp/aspnetcore.mvc.ui" "~10.3.0" - "@abp/bootstrap" "~10.3.0" - "@abp/bootstrap-datepicker" "~10.3.0" - "@abp/bootstrap-daterangepicker" "~10.3.0" - "@abp/datatables.net-bs5" "~10.3.0" - "@abp/font-awesome" "~10.3.0" - "@abp/jquery-validation-unobtrusive" "~10.3.0" - "@abp/lodash" "~10.3.0" - "@abp/luxon" "~10.3.0" - "@abp/malihu-custom-scrollbar-plugin" "~10.3.0" - "@abp/moment" "~10.3.0" - "@abp/select2" "~10.3.0" - "@abp/sweetalert2" "~10.3.0" - "@abp/timeago" "~10.3.0" - -"@abp/aspnetcore.mvc.ui@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.3.0.tgz#0d125efd172b6772c516eae82cfe35f144da73fc" - integrity sha512-kp0XdwrlNz+NeLdxSJ6xdUaB5PM7xdbiLsIyTUcwa7AUKBtcVU6yq6qttbk9VyO0KqDgLywSuEL+9PkFhthuPg== +"@abp/aspnetcore.mvc.ui.theme.basic@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.4.0.tgz#c0414583f1c1fca14b0133818a63434b4fbf7df0" + integrity sha512-sOg3URmFRO/18D734B56jdFuC4LflGXXpw4w1Rdd77BJxugynxcmwNOQbLYFvMfqj0eKqDOH3E4yEcmS2IT1BQ== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~10.4.0" + +"@abp/aspnetcore.mvc.ui.theme.shared@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.4.0.tgz#49002e55db00c98931d27357f7ea40eeab09e2e8" + integrity sha512-bNQ+WOnsfQhb98Z9YO0zZsW2K8ZEgOQ+7/86QaqoYTf+ZFvLJsICmNuHryNkCGRM1XJf9EaDZ3qSOwoOGQwS9A== + dependencies: + "@abp/aspnetcore.mvc.ui" "~10.4.0" + "@abp/bootstrap" "~10.4.0" + "@abp/bootstrap-datepicker" "~10.4.0" + "@abp/bootstrap-daterangepicker" "~10.4.0" + "@abp/datatables.net-bs5" "~10.4.0" + "@abp/font-awesome" "~10.4.0" + "@abp/jquery-validation-unobtrusive" "~10.4.0" + "@abp/lodash" "~10.4.0" + "@abp/luxon" "~10.4.0" + "@abp/malihu-custom-scrollbar-plugin" "~10.4.0" + "@abp/moment" "~10.4.0" + "@abp/select2" "~10.4.0" + "@abp/sweetalert2" "~10.4.0" + "@abp/timeago" "~10.4.0" + +"@abp/aspnetcore.mvc.ui@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.4.0.tgz#24e40fdc101c411f29a87adb77baa642299e979c" + integrity sha512-0qVFthS4f4fkCv//UY2GseO+NX+XYqlamZJJwOTmv1UXFW6LxAXxuEQqWYVfFKzwk2lbkXDUcgP8NhhQ9xp3Sw== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.3.0.tgz#032aaf0e474f971b9f90ca7fb83ff7ca4d7be933" - integrity sha512-7mRuRRIE4R0yw1cZuoIskgrqZPUa5/SJzse0K6u+/QDqvyLjjOsyE51WuqAXxlVfKpLp/fBvXZwnCDeCRU9iZQ== +"@abp/bootstrap-datepicker@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.4.0.tgz#a392fd5ce31c86e9fc07f7dedbd0c56a02af341e" + integrity sha512-kOpuCEO64zoJ7a9JASsCLFZIjEYgN0GG/oApS2L729pv6/oEhcupWpwrnvDUhsdGOezXwLIsgTzc6tiG3HxwGA== dependencies: bootstrap-datepicker "^1.10.1" -"@abp/bootstrap-daterangepicker@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.3.0.tgz#b2a9171595ee311c32f57460013870cde67c6b7f" - integrity sha512-ejLu3sWhfNDlsXeUXAQudZvPv0xM4McxIK65vuVEZiKIwFSnrLYil8jBi12EJJmAw5yYA/KC0EdyG82E+AWpmA== +"@abp/bootstrap-daterangepicker@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.4.0.tgz#95f68d3f488a12ede3c12352637b48782eeb031a" + integrity sha512-HX4HutkbPDQWBJNPDozpT1Um9mHcfV+RL+ukwEy0N4e7nD8VJyqLT/a+/FzQN7iUkwV+aNR2v5c0W+dgyqTaEA== dependencies: - "@abp/moment" "~10.3.0" + "@abp/moment" "~10.4.0" bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.3.0.tgz#23535c90613271b02fdad3eae1556b67844729e6" - integrity sha512-JnVOeJUyR78oo+QURvaNqa9xGZaVux0PRIpFpd4Qsqiiz9FvJrbCuSoBkkZoj/0eH23WoZ3bJxy0K9LDE19qvg== +"@abp/bootstrap@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.4.0.tgz#64b22bbdbf6b5bda279b21a7bae0652290b1221f" + integrity sha512-WQzXi5h1mej9twrAB6kD4aJtTNfSxpnuo1pxIOu/8GKGWoDayVY2NMQl+MCVel6dST+6EUTmuDRxc2q9qHRxLg== dependencies: - "@abp/core" "~10.3.0" - "@abp/popper.js" "~10.3.0" + "@abp/core" "~10.4.0" + "@abp/popper.js" "~10.4.0" bootstrap "^5.3.8" -"@abp/clipboard@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-10.3.0.tgz#7bfbfcae6a135065ee13afd1b7d3abc92176a203" - integrity sha512-Cah+jOzcG1KZh6PXWxo+BedU7gsqTuNlTDsCzBCk2NuV2/Blb5qumAwEP4GBKAEIY/XwcFQP/ubxuXMTNjr0jw== +"@abp/clipboard@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-10.4.0.tgz#bac5854af4f8bcaa987d39f234edd58b675a71c8" + integrity sha512-KTTzCDEe+wjpGLdud8MHlwnPr/NdzFDmfHtENgESdIa5KilGOwy2wv24lQ5cyJOB4ftoJzkBaHinKJxg/KU0pw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" clipboard "^2.0.11" -"@abp/core@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.3.0.tgz#9960f8179d1fdc42250d0bbba60e379590c8199c" - integrity sha512-DpL4qrsyCUolypUwxKVDDK8bPVhNbYKx6l2ytUjoOu73CwgPoCSGkvHWrmKIsEsmTfOC9J7+GDErh8jWR/5pVA== +"@abp/core@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.4.0.tgz#de0c09e26d1f20451da9430d939beebb5bb6e9dd" + integrity sha512-3yKLFqUtFo5rgBPQxWkNPsdq1bE9VKwm02yB14lu913EIYhP//akizY4uSK08BozocJNveCASQbgd66z4x6v/Q== dependencies: - "@abp/utils" "~10.3.0" + "@abp/utils" "~10.4.0" -"@abp/datatables.net-bs5@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.3.0.tgz#d8711c52f7c03df51d14d1a5f0e6b00f6d9551fc" - integrity sha512-I8rEU98kEhsNZjVdpPvOcG6F4xr1CgIRb0bTf5bfd8ufzxEUKB+xF9bguJIB5kYwjme8QbMlcJ0V/c+N4iewtQ== +"@abp/datatables.net-bs5@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.4.0.tgz#a24eef99399324e4b89a00a204d2c4c2de379da1" + integrity sha512-T4f7e6sR1qHcvygNUNfdPWsK61C3NQORV35Lzpl98eDwY8XLg2/RLAegpFI8GjESVy2+2PCKVqUoSYZtXaMFmw== dependencies: - "@abp/datatables.net" "~10.3.0" + "@abp/datatables.net" "~10.4.0" datatables.net-bs5 "^2.3.4" -"@abp/datatables.net@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.3.0.tgz#f98319d8e226da5aced4d1d94d3ababb11327074" - integrity sha512-imM25WmO1V0hqeG5iPeLvYMID2HX7SG9drz47qilr74MhpY2i2J7HlsvZDonNkjpxzhK4DHpGeQd6mE2QzG7Hw== +"@abp/datatables.net@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.4.0.tgz#ab48f6e3ccae8d0644f0c01011ff98cc6ca16181" + integrity sha512-esIUozaSEa29G7bdWbEZr22fRChaTuGfNNB8rgBHmVKwgkbHSG7qUTgsfGhNz0T2vfDsK3X37n55xmSdE9RElg== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" datatables.net "^2.3.4" -"@abp/font-awesome@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.3.0.tgz#43602605f5633132cc6fe21b6ce5ab19b86bdf27" - integrity sha512-RUOVHxDyT81hp01DhUuSshKvLM+NiTt9VP+O4/rSj+SOaSJAm3wX7eziCKrDi6H41FwhoZL75CifLhcZVyVO4A== +"@abp/font-awesome@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.4.0.tgz#327979191615fff571f0ea2e99d01bbcca5378ce" + integrity sha512-RBzDSi/hrjhLKc7h6Kf9CQqC+LCGvS7h8HrRVaIzWCZSeqTfIMFpixjCUrNgkqWI0bU4NDUKXZ4XCOv4nPVlBg== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" "@fortawesome/fontawesome-free" "^7.0.1" -"@abp/jquery-validation-unobtrusive@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.3.0.tgz#5bc14ed642d48f9eae54761ad39bd9f51beeb005" - integrity sha512-j/9fA7tAs1/t3P2U1kKUiDlHAImmseul6KzvV77sNpnms7g4+x6Fgsa+sMzicSRGOLz7lK5RHdBFyvfKxt7/nA== +"@abp/jquery-validation-unobtrusive@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.4.0.tgz#e0114347344931667f48e0f2de5ea4bd6f9d2a88" + integrity sha512-AJncXVB+eTSizq0fTAwLii9a0GeYFPiY+gw+Ki5pak8Pp9LQivphsW32TqQmPC3qZVuYtd4myUUraWuOTT1Adg== dependencies: - "@abp/jquery-validation" "~10.3.0" + "@abp/jquery-validation" "~10.4.0" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.3.0.tgz#e669a54d28f6c0b863a284322e527a5c65cb7dbf" - integrity sha512-Wo95ZJLS1ScKm7dm3zFlEFUKqXrqo3m9+82DX5mFJUVCHHuBPrhxvDjxNJ18z4gpoOUu08uqxX7LFbphtkRM3g== +"@abp/jquery-validation@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.4.0.tgz#bd1ef0217c2500363f25223dd7316ba919a505b5" + integrity sha512-W9QiGnOpaq6uDDwBy4UEqWrP1p2UegHQShW5Cev9vr6g7Lk97UsS1Agh9lnjbvMzFSAiLl3M9gHmyMD4aU3NPA== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" jquery-validation "^1.21.0" -"@abp/jquery@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.3.0.tgz#3b60bcdfe8f9a33be79db1bea2500837fe09c67d" - integrity sha512-TndX/8bJx5vGAAwIykx2CJUZIFFDkwpD+cUjE45WO2dSrY2M5vNfWiuw95OcLGngO8RaT/gTkFGzPuZqKn9ggg== +"@abp/jquery@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.4.0.tgz#7ed3f1808682717ccfa7c6ac6e8db8a8b648b1e1" + integrity sha512-kjFoVIhqlaIHlEs917sIeP1sHmLWXMn+DVKaCkVQUXC2KVq+yvQRC8AZiFQVxOwLvzd9dFFpGwGZgpWkDfvhvg== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" jquery "~3.7.1" -"@abp/lodash@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.3.0.tgz#bd091fc811d85d2f535802631af6165ac1c8a198" - integrity sha512-7JDU9UlbD+9odhFQjolvyJZ6EQKG5kiL2Pt9T1RMVchNh47iEuZwfi8c+CIxa3YxA3n6lAsZ+8Pmxq4yOku+RQ== +"@abp/lodash@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.4.0.tgz#b6fc25b6e2fd74420ca16a5a13cb8fcd39b89efd" + integrity sha512-RuiIGAqJtSBS+4VAk4cg7Zsfj07B3xkiewCaK5j0xUcyjsCJKL1UdXe2zVNL3oE5bXTNMU7yNQWJS4knv2mfwA== dependencies: - "@abp/core" "~10.3.0" - lodash "^4.17.21" + "@abp/core" "~10.4.0" + lodash "^4.18.1" -"@abp/luxon@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.3.0.tgz#acbd92289456a546607ed4506dc36f71e6da53c9" - integrity sha512-IIi2+odQzuFK4qV1KJ2HtlTewSPby2F9CJFNNtNRMzX/P30dQkAGWpNV5/FZzJwFIihR+zSei5wlu11WpPugsw== +"@abp/luxon@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.4.0.tgz#731d20877ec679fa518b7199128676631d030a90" + integrity sha512-/hF+FBugJMkp/H9WNZxlsOy3Rl4DTeR2hqmRPI0sPcdFOixCziYrwzyHg/2Kx8cI/zHa6GRRt1B4Jz4yjQTvdw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" luxon "^3.7.2" -"@abp/malihu-custom-scrollbar-plugin@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.3.0.tgz#209931d02a994ef448371d6203e9ec74c72b61e6" - integrity sha512-hJjwEExP3hKE1MNDG+pcvoHRFUihh3t/0wCPNaoePFd8+QnJuyQ9U0VA+vdtykTxm8uFNE2uaCgyHffoHmzeZA== +"@abp/malihu-custom-scrollbar-plugin@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.4.0.tgz#e1c207d9c66cce64fcb4fe13c091c4b86b00b6d7" + integrity sha512-LC+imsGQganhKWybvQytATjQMXfQwfCfi6RRWREP+pejKPuHpqHzPd+AjKwZpx500K2/ZTwT62FKJ1uDiJXSKA== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.3.0.tgz#5a5c4ece14d1bc4dd33134f85d1d085733d3c975" - integrity sha512-8DKw2jgC/28nLrB/CR9BiyUSt0pUmWF+bEgAsduln6VrSCHn0aRLqgs+wi/9Ol+1bl7pO2wbJOYg1qbnkDhrAQ== +"@abp/moment@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.4.0.tgz#6737898b90b579ce5e0d683657757725a4def09d" + integrity sha512-WUtzjD6+1JGpgS6Yv2Qo/h4jM77LkkasPrywsetmtj/6zyVQWrkk7tKHTCjhAJtdinctcROwIIcwLsELi0nDHg== dependencies: moment "^2.30.1" -"@abp/popper.js@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-10.3.0.tgz#cb2415e45a905854980f4e864c00c8fe1156a6f9" - integrity sha512-Yfafs50t7DKnShxAl9WPpihZxG5cLAt99rJmIyXBYUQIpkHkxDyUIA5b4gCoVCqKgjFDOxG7i6L+Ovd/2N8k7g== +"@abp/popper.js@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-10.4.0.tgz#9d8d09c4e18020b6a75ba30daf6786606f555e84" + integrity sha512-KR5wGZ/MfR1rOPcmFr5ekraYhOSRul9gqempir69hlqrFKUFqt46F9/wLWyaJ7vlsMU6o4gBo6/jE5tICIpzNw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" "@popperjs/core" "^2.11.8" -"@abp/prismjs@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-10.3.0.tgz#4ea33ece50cefcfc8a89c17b0d265b484b7205ee" - integrity sha512-WvkLyg+izkcX6sEOYy41B9lebiniYxMQZ++c+ysyOQKZzrYEEFOOLlnFS9jPqSKLv9cNRizhG1CCfiwoYCxufQ== +"@abp/prismjs@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-10.4.0.tgz#0061d71d18252bc1e66e763aab6d81a7b4fe51b7" + integrity sha512-kjV11XNt2uP01zlhrrScr4WA27lgZ03Cz3svA7/HHnfhFy76MHYo+Dyn7sS4Gp3UobMcf1QWHl4KQNCUnYNTOQ== dependencies: - "@abp/clipboard" "~10.3.0" - "@abp/core" "~10.3.0" + "@abp/clipboard" "~10.4.0" + "@abp/core" "~10.4.0" prismjs "^1.30.0" -"@abp/select2@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.3.0.tgz#199777173ca3e43a5448f8b51b30c3aafe4a3333" - integrity sha512-6sEIYIakVI1DM3eOqOaxwdG8vkyVFBWvVZhoChPZkgIIQ0EH2JLerHv0pePJEl6mQSxOUrikPA8++B2y+sBnQA== +"@abp/select2@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.4.0.tgz#e744988abf81e2194a5850acc4ac1333e9578df8" + integrity sha512-tyAdGzX32joI5vw+MUJGR0iToWCGZrbQg/zKeoCib80Zx5cn+e6GXj6wZyPbsJgVm0jZ7zBweWt3x8HzW9N8Ww== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" select2 "^4.0.13" -"@abp/sweetalert2@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.3.0.tgz#515e0477574b54bc6650b9f5ee193bfc648c715a" - integrity sha512-bY5MXEiKja+vk71ujSn8rpzRC45Ge8zvcca1k1vBJs+oKyRCii//JBxa3H0lagZo5r/QwIqv5ihdyLE1xsuLSg== +"@abp/sweetalert2@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.4.0.tgz#65cb26c8c7c9c02158c9a487969eef033fe04a73" + integrity sha512-kyVMlfnI6MZojJlaRstdHj/CL3olOn4BszjQ155UQaDsGHUu0THmbhpfi4P0q3TCLLzcLM4pkuY3wJHPNl3ZOw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" sweetalert2 "^11.23.0" -"@abp/timeago@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.3.0.tgz#3214a6d000c79504c9fa7cb4e004a967fe46459b" - integrity sha512-Rgcex+jvM9Z9/E0I5Q3wlC2bY5550/+6xRX407k5ho4gxCccYo3c0Gkpiy5pJRFhTwtBgcNzYxArh6NCez+tDg== +"@abp/timeago@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.4.0.tgz#100fc0598e3a605346397f8a395bb96dc70f429d" + integrity sha512-G0ppO7Evai3DdhSg4ozxQpvIke0t3vyg63pNdfOtAPngmuam7WBM1WemBi4u29M84WcSfmk4KuAubXO0aCJgDg== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" timeago "^1.6.7" -"@abp/utils@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.3.0.tgz#d88c0d639d69d6210c3070892aac61d5e945c124" - integrity sha512-iNehxiqWJQl2wwE9EbsQANPv/ITMh0yUcPATUlG/McoLWdpPcqobx75NxXcHDqW8ZarecqSktjJDZPvaUaD+jA== +"@abp/utils@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.4.0.tgz#75175f798e58643c0dec1e85a502186c811e9226" + integrity sha512-BPc7KGDcbdFZ4GonAt3SOi/gYhG+iXI76TMlAzSKMBRhBU0vxFS8I/pb7U5zX0Gjh5M+aT0l2vA3l+POCHPBfw== dependencies: just-compare "^2.3.0" @@ -306,10 +306,10 @@ just-compare@^2.3.0: resolved "https://registry.yarnpkg.com/just-compare/-/just-compare-2.3.0.tgz#a2adcc1d1940536263275f5a1ef1298bcacfeda7" integrity sha512-6shoR7HDT+fzfL3gBahx1jZG3hWLrhPAf+l7nCwahDdT9XDtosB9kIF0ZrzUp5QY8dJWfQVr5rnsPqsbvflDzg== -lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lodash@^4.18.1: + version "4.18.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" + integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== luxon@^3.7.2: version "3.7.2" diff --git a/modules/blogging/app/Volo.BloggingTestApp/package.json b/modules/blogging/app/Volo.BloggingTestApp/package.json index da9f44eeae..2ed7bc314a 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/package.json +++ b/modules/blogging/app/Volo.BloggingTestApp/package.json @@ -3,7 +3,7 @@ "name": "volo.blogtestapp", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.3.0", - "@abp/blogging": "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.4.0", + "@abp/blogging": "~10.4.0" } } diff --git a/modules/blogging/app/Volo.BloggingTestApp/yarn.lock b/modules/blogging/app/Volo.BloggingTestApp/yarn.lock index cd247c048d..e9417a19ba 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/yarn.lock +++ b/modules/blogging/app/Volo.BloggingTestApp/yarn.lock @@ -2,229 +2,229 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.3.0.tgz#bf48e8fa571067d15bc19bdd3f13509acf58f9c7" - integrity sha512-t9pHqrXaCDYcsdrz0VPKT28ujnnU+EVxyVDogQSRRpKsDoLhNcOD8NI3swdAWBQZ46YSCDfeDR+gfwjC3yqpXQ== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~10.3.0" - -"@abp/aspnetcore.mvc.ui.theme.shared@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.3.0.tgz#bcf72b5dd1dea285c3fa7cb1b6ce27c29b985ff7" - integrity sha512-X5xmMSxnIzNJPexvKR9y6jyn0v5n2wscN1SECG/bMI1YdKugMlkfkSuyUmLn+RX9/YdcK0jOsdMhfuzs5mgAOA== - dependencies: - "@abp/aspnetcore.mvc.ui" "~10.3.0" - "@abp/bootstrap" "~10.3.0" - "@abp/bootstrap-datepicker" "~10.3.0" - "@abp/bootstrap-daterangepicker" "~10.3.0" - "@abp/datatables.net-bs5" "~10.3.0" - "@abp/font-awesome" "~10.3.0" - "@abp/jquery-validation-unobtrusive" "~10.3.0" - "@abp/lodash" "~10.3.0" - "@abp/luxon" "~10.3.0" - "@abp/malihu-custom-scrollbar-plugin" "~10.3.0" - "@abp/moment" "~10.3.0" - "@abp/select2" "~10.3.0" - "@abp/sweetalert2" "~10.3.0" - "@abp/timeago" "~10.3.0" - -"@abp/aspnetcore.mvc.ui@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.3.0.tgz#0d125efd172b6772c516eae82cfe35f144da73fc" - integrity sha512-kp0XdwrlNz+NeLdxSJ6xdUaB5PM7xdbiLsIyTUcwa7AUKBtcVU6yq6qttbk9VyO0KqDgLywSuEL+9PkFhthuPg== +"@abp/aspnetcore.mvc.ui.theme.basic@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.4.0.tgz#c0414583f1c1fca14b0133818a63434b4fbf7df0" + integrity sha512-sOg3URmFRO/18D734B56jdFuC4LflGXXpw4w1Rdd77BJxugynxcmwNOQbLYFvMfqj0eKqDOH3E4yEcmS2IT1BQ== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~10.4.0" + +"@abp/aspnetcore.mvc.ui.theme.shared@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.4.0.tgz#49002e55db00c98931d27357f7ea40eeab09e2e8" + integrity sha512-bNQ+WOnsfQhb98Z9YO0zZsW2K8ZEgOQ+7/86QaqoYTf+ZFvLJsICmNuHryNkCGRM1XJf9EaDZ3qSOwoOGQwS9A== + dependencies: + "@abp/aspnetcore.mvc.ui" "~10.4.0" + "@abp/bootstrap" "~10.4.0" + "@abp/bootstrap-datepicker" "~10.4.0" + "@abp/bootstrap-daterangepicker" "~10.4.0" + "@abp/datatables.net-bs5" "~10.4.0" + "@abp/font-awesome" "~10.4.0" + "@abp/jquery-validation-unobtrusive" "~10.4.0" + "@abp/lodash" "~10.4.0" + "@abp/luxon" "~10.4.0" + "@abp/malihu-custom-scrollbar-plugin" "~10.4.0" + "@abp/moment" "~10.4.0" + "@abp/select2" "~10.4.0" + "@abp/sweetalert2" "~10.4.0" + "@abp/timeago" "~10.4.0" + +"@abp/aspnetcore.mvc.ui@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.4.0.tgz#24e40fdc101c411f29a87adb77baa642299e979c" + integrity sha512-0qVFthS4f4fkCv//UY2GseO+NX+XYqlamZJJwOTmv1UXFW6LxAXxuEQqWYVfFKzwk2lbkXDUcgP8NhhQ9xp3Sw== dependencies: ansi-colors "^4.1.3" -"@abp/blogging@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/blogging/-/blogging-10.3.0.tgz#cf2c43cf10a3c7f6678682216baa98d297bde3a6" - integrity sha512-A3em39jepJp9TCZVcXeQNP+UnCmgggTmbVDbs5agAA2DiaWA+92oTEcAGlooHw48fiGc+1oANC7WaWS5FdNMGA== +"@abp/blogging@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/blogging/-/blogging-10.4.0.tgz#ae468b0cdfdd011fcb709b627dc9cb7a3076506b" + integrity sha512-UHoEeo3lBTXA4TpkPkOSaNklsbB4MG6OC6oM2l9ApihMzJyKV3osczBr8COZ+awpRGCsEAjotOw8OMIVH4evcQ== dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~10.3.0" - "@abp/owl.carousel" "~10.3.0" - "@abp/prismjs" "~10.3.0" - "@abp/tui-editor" "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.shared" "~10.4.0" + "@abp/owl.carousel" "~10.4.0" + "@abp/prismjs" "~10.4.0" + "@abp/tui-editor" "~10.4.0" -"@abp/bootstrap-datepicker@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.3.0.tgz#032aaf0e474f971b9f90ca7fb83ff7ca4d7be933" - integrity sha512-7mRuRRIE4R0yw1cZuoIskgrqZPUa5/SJzse0K6u+/QDqvyLjjOsyE51WuqAXxlVfKpLp/fBvXZwnCDeCRU9iZQ== +"@abp/bootstrap-datepicker@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.4.0.tgz#a392fd5ce31c86e9fc07f7dedbd0c56a02af341e" + integrity sha512-kOpuCEO64zoJ7a9JASsCLFZIjEYgN0GG/oApS2L729pv6/oEhcupWpwrnvDUhsdGOezXwLIsgTzc6tiG3HxwGA== dependencies: bootstrap-datepicker "^1.10.1" -"@abp/bootstrap-daterangepicker@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.3.0.tgz#b2a9171595ee311c32f57460013870cde67c6b7f" - integrity sha512-ejLu3sWhfNDlsXeUXAQudZvPv0xM4McxIK65vuVEZiKIwFSnrLYil8jBi12EJJmAw5yYA/KC0EdyG82E+AWpmA== +"@abp/bootstrap-daterangepicker@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.4.0.tgz#95f68d3f488a12ede3c12352637b48782eeb031a" + integrity sha512-HX4HutkbPDQWBJNPDozpT1Um9mHcfV+RL+ukwEy0N4e7nD8VJyqLT/a+/FzQN7iUkwV+aNR2v5c0W+dgyqTaEA== dependencies: - "@abp/moment" "~10.3.0" + "@abp/moment" "~10.4.0" bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.3.0.tgz#23535c90613271b02fdad3eae1556b67844729e6" - integrity sha512-JnVOeJUyR78oo+QURvaNqa9xGZaVux0PRIpFpd4Qsqiiz9FvJrbCuSoBkkZoj/0eH23WoZ3bJxy0K9LDE19qvg== +"@abp/bootstrap@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.4.0.tgz#64b22bbdbf6b5bda279b21a7bae0652290b1221f" + integrity sha512-WQzXi5h1mej9twrAB6kD4aJtTNfSxpnuo1pxIOu/8GKGWoDayVY2NMQl+MCVel6dST+6EUTmuDRxc2q9qHRxLg== dependencies: - "@abp/core" "~10.3.0" - "@abp/popper.js" "~10.3.0" + "@abp/core" "~10.4.0" + "@abp/popper.js" "~10.4.0" bootstrap "^5.3.8" -"@abp/clipboard@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-10.3.0.tgz#7bfbfcae6a135065ee13afd1b7d3abc92176a203" - integrity sha512-Cah+jOzcG1KZh6PXWxo+BedU7gsqTuNlTDsCzBCk2NuV2/Blb5qumAwEP4GBKAEIY/XwcFQP/ubxuXMTNjr0jw== +"@abp/clipboard@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-10.4.0.tgz#bac5854af4f8bcaa987d39f234edd58b675a71c8" + integrity sha512-KTTzCDEe+wjpGLdud8MHlwnPr/NdzFDmfHtENgESdIa5KilGOwy2wv24lQ5cyJOB4ftoJzkBaHinKJxg/KU0pw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" clipboard "^2.0.11" -"@abp/core@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.3.0.tgz#9960f8179d1fdc42250d0bbba60e379590c8199c" - integrity sha512-DpL4qrsyCUolypUwxKVDDK8bPVhNbYKx6l2ytUjoOu73CwgPoCSGkvHWrmKIsEsmTfOC9J7+GDErh8jWR/5pVA== +"@abp/core@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.4.0.tgz#de0c09e26d1f20451da9430d939beebb5bb6e9dd" + integrity sha512-3yKLFqUtFo5rgBPQxWkNPsdq1bE9VKwm02yB14lu913EIYhP//akizY4uSK08BozocJNveCASQbgd66z4x6v/Q== dependencies: - "@abp/utils" "~10.3.0" + "@abp/utils" "~10.4.0" -"@abp/datatables.net-bs5@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.3.0.tgz#d8711c52f7c03df51d14d1a5f0e6b00f6d9551fc" - integrity sha512-I8rEU98kEhsNZjVdpPvOcG6F4xr1CgIRb0bTf5bfd8ufzxEUKB+xF9bguJIB5kYwjme8QbMlcJ0V/c+N4iewtQ== +"@abp/datatables.net-bs5@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.4.0.tgz#a24eef99399324e4b89a00a204d2c4c2de379da1" + integrity sha512-T4f7e6sR1qHcvygNUNfdPWsK61C3NQORV35Lzpl98eDwY8XLg2/RLAegpFI8GjESVy2+2PCKVqUoSYZtXaMFmw== dependencies: - "@abp/datatables.net" "~10.3.0" + "@abp/datatables.net" "~10.4.0" datatables.net-bs5 "^2.3.4" -"@abp/datatables.net@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.3.0.tgz#f98319d8e226da5aced4d1d94d3ababb11327074" - integrity sha512-imM25WmO1V0hqeG5iPeLvYMID2HX7SG9drz47qilr74MhpY2i2J7HlsvZDonNkjpxzhK4DHpGeQd6mE2QzG7Hw== +"@abp/datatables.net@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.4.0.tgz#ab48f6e3ccae8d0644f0c01011ff98cc6ca16181" + integrity sha512-esIUozaSEa29G7bdWbEZr22fRChaTuGfNNB8rgBHmVKwgkbHSG7qUTgsfGhNz0T2vfDsK3X37n55xmSdE9RElg== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" datatables.net "^2.3.4" -"@abp/font-awesome@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.3.0.tgz#43602605f5633132cc6fe21b6ce5ab19b86bdf27" - integrity sha512-RUOVHxDyT81hp01DhUuSshKvLM+NiTt9VP+O4/rSj+SOaSJAm3wX7eziCKrDi6H41FwhoZL75CifLhcZVyVO4A== +"@abp/font-awesome@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.4.0.tgz#327979191615fff571f0ea2e99d01bbcca5378ce" + integrity sha512-RBzDSi/hrjhLKc7h6Kf9CQqC+LCGvS7h8HrRVaIzWCZSeqTfIMFpixjCUrNgkqWI0bU4NDUKXZ4XCOv4nPVlBg== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" "@fortawesome/fontawesome-free" "^7.0.1" -"@abp/jquery-validation-unobtrusive@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.3.0.tgz#5bc14ed642d48f9eae54761ad39bd9f51beeb005" - integrity sha512-j/9fA7tAs1/t3P2U1kKUiDlHAImmseul6KzvV77sNpnms7g4+x6Fgsa+sMzicSRGOLz7lK5RHdBFyvfKxt7/nA== +"@abp/jquery-validation-unobtrusive@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.4.0.tgz#e0114347344931667f48e0f2de5ea4bd6f9d2a88" + integrity sha512-AJncXVB+eTSizq0fTAwLii9a0GeYFPiY+gw+Ki5pak8Pp9LQivphsW32TqQmPC3qZVuYtd4myUUraWuOTT1Adg== dependencies: - "@abp/jquery-validation" "~10.3.0" + "@abp/jquery-validation" "~10.4.0" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.3.0.tgz#e669a54d28f6c0b863a284322e527a5c65cb7dbf" - integrity sha512-Wo95ZJLS1ScKm7dm3zFlEFUKqXrqo3m9+82DX5mFJUVCHHuBPrhxvDjxNJ18z4gpoOUu08uqxX7LFbphtkRM3g== +"@abp/jquery-validation@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.4.0.tgz#bd1ef0217c2500363f25223dd7316ba919a505b5" + integrity sha512-W9QiGnOpaq6uDDwBy4UEqWrP1p2UegHQShW5Cev9vr6g7Lk97UsS1Agh9lnjbvMzFSAiLl3M9gHmyMD4aU3NPA== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" jquery-validation "^1.21.0" -"@abp/jquery@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.3.0.tgz#3b60bcdfe8f9a33be79db1bea2500837fe09c67d" - integrity sha512-TndX/8bJx5vGAAwIykx2CJUZIFFDkwpD+cUjE45WO2dSrY2M5vNfWiuw95OcLGngO8RaT/gTkFGzPuZqKn9ggg== +"@abp/jquery@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.4.0.tgz#7ed3f1808682717ccfa7c6ac6e8db8a8b648b1e1" + integrity sha512-kjFoVIhqlaIHlEs917sIeP1sHmLWXMn+DVKaCkVQUXC2KVq+yvQRC8AZiFQVxOwLvzd9dFFpGwGZgpWkDfvhvg== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" jquery "~3.7.1" -"@abp/lodash@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.3.0.tgz#bd091fc811d85d2f535802631af6165ac1c8a198" - integrity sha512-7JDU9UlbD+9odhFQjolvyJZ6EQKG5kiL2Pt9T1RMVchNh47iEuZwfi8c+CIxa3YxA3n6lAsZ+8Pmxq4yOku+RQ== +"@abp/lodash@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.4.0.tgz#b6fc25b6e2fd74420ca16a5a13cb8fcd39b89efd" + integrity sha512-RuiIGAqJtSBS+4VAk4cg7Zsfj07B3xkiewCaK5j0xUcyjsCJKL1UdXe2zVNL3oE5bXTNMU7yNQWJS4knv2mfwA== dependencies: - "@abp/core" "~10.3.0" - lodash "^4.17.21" + "@abp/core" "~10.4.0" + lodash "^4.18.1" -"@abp/luxon@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.3.0.tgz#acbd92289456a546607ed4506dc36f71e6da53c9" - integrity sha512-IIi2+odQzuFK4qV1KJ2HtlTewSPby2F9CJFNNtNRMzX/P30dQkAGWpNV5/FZzJwFIihR+zSei5wlu11WpPugsw== +"@abp/luxon@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.4.0.tgz#731d20877ec679fa518b7199128676631d030a90" + integrity sha512-/hF+FBugJMkp/H9WNZxlsOy3Rl4DTeR2hqmRPI0sPcdFOixCziYrwzyHg/2Kx8cI/zHa6GRRt1B4Jz4yjQTvdw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" luxon "^3.7.2" -"@abp/malihu-custom-scrollbar-plugin@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.3.0.tgz#209931d02a994ef448371d6203e9ec74c72b61e6" - integrity sha512-hJjwEExP3hKE1MNDG+pcvoHRFUihh3t/0wCPNaoePFd8+QnJuyQ9U0VA+vdtykTxm8uFNE2uaCgyHffoHmzeZA== +"@abp/malihu-custom-scrollbar-plugin@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.4.0.tgz#e1c207d9c66cce64fcb4fe13c091c4b86b00b6d7" + integrity sha512-LC+imsGQganhKWybvQytATjQMXfQwfCfi6RRWREP+pejKPuHpqHzPd+AjKwZpx500K2/ZTwT62FKJ1uDiJXSKA== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.3.0.tgz#5a5c4ece14d1bc4dd33134f85d1d085733d3c975" - integrity sha512-8DKw2jgC/28nLrB/CR9BiyUSt0pUmWF+bEgAsduln6VrSCHn0aRLqgs+wi/9Ol+1bl7pO2wbJOYg1qbnkDhrAQ== +"@abp/moment@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.4.0.tgz#6737898b90b579ce5e0d683657757725a4def09d" + integrity sha512-WUtzjD6+1JGpgS6Yv2Qo/h4jM77LkkasPrywsetmtj/6zyVQWrkk7tKHTCjhAJtdinctcROwIIcwLsELi0nDHg== dependencies: moment "^2.30.1" -"@abp/owl.carousel@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/owl.carousel/-/owl.carousel-10.3.0.tgz#840718a4b8ad7636bcb42d40169f42b863ac2537" - integrity sha512-r0Yfbf6MiKRa3fGrz7b/UBeRoPOBUPA39KV6JXIR5UDkn+Vu3BInacpB0HyksADzVcYyz06QvHGzlCOmikXzrg== +"@abp/owl.carousel@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/owl.carousel/-/owl.carousel-10.4.0.tgz#03cb3abec37b9e61196982d20f8b48ca9ff9818a" + integrity sha512-wSMMMVJ5LRSmmV4jXekrWJdI7MQ+37LWKu83dQRD1Bwegy2USuB2efBcwVB/juI2iaxP9b0jTrKmxTlFPkNbCg== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" owl.carousel "^2.3.4" -"@abp/popper.js@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-10.3.0.tgz#cb2415e45a905854980f4e864c00c8fe1156a6f9" - integrity sha512-Yfafs50t7DKnShxAl9WPpihZxG5cLAt99rJmIyXBYUQIpkHkxDyUIA5b4gCoVCqKgjFDOxG7i6L+Ovd/2N8k7g== +"@abp/popper.js@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-10.4.0.tgz#9d8d09c4e18020b6a75ba30daf6786606f555e84" + integrity sha512-KR5wGZ/MfR1rOPcmFr5ekraYhOSRul9gqempir69hlqrFKUFqt46F9/wLWyaJ7vlsMU6o4gBo6/jE5tICIpzNw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" "@popperjs/core" "^2.11.8" -"@abp/prismjs@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-10.3.0.tgz#4ea33ece50cefcfc8a89c17b0d265b484b7205ee" - integrity sha512-WvkLyg+izkcX6sEOYy41B9lebiniYxMQZ++c+ysyOQKZzrYEEFOOLlnFS9jPqSKLv9cNRizhG1CCfiwoYCxufQ== +"@abp/prismjs@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-10.4.0.tgz#0061d71d18252bc1e66e763aab6d81a7b4fe51b7" + integrity sha512-kjV11XNt2uP01zlhrrScr4WA27lgZ03Cz3svA7/HHnfhFy76MHYo+Dyn7sS4Gp3UobMcf1QWHl4KQNCUnYNTOQ== dependencies: - "@abp/clipboard" "~10.3.0" - "@abp/core" "~10.3.0" + "@abp/clipboard" "~10.4.0" + "@abp/core" "~10.4.0" prismjs "^1.30.0" -"@abp/select2@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.3.0.tgz#199777173ca3e43a5448f8b51b30c3aafe4a3333" - integrity sha512-6sEIYIakVI1DM3eOqOaxwdG8vkyVFBWvVZhoChPZkgIIQ0EH2JLerHv0pePJEl6mQSxOUrikPA8++B2y+sBnQA== +"@abp/select2@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.4.0.tgz#e744988abf81e2194a5850acc4ac1333e9578df8" + integrity sha512-tyAdGzX32joI5vw+MUJGR0iToWCGZrbQg/zKeoCib80Zx5cn+e6GXj6wZyPbsJgVm0jZ7zBweWt3x8HzW9N8Ww== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" select2 "^4.0.13" -"@abp/sweetalert2@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.3.0.tgz#515e0477574b54bc6650b9f5ee193bfc648c715a" - integrity sha512-bY5MXEiKja+vk71ujSn8rpzRC45Ge8zvcca1k1vBJs+oKyRCii//JBxa3H0lagZo5r/QwIqv5ihdyLE1xsuLSg== +"@abp/sweetalert2@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.4.0.tgz#65cb26c8c7c9c02158c9a487969eef033fe04a73" + integrity sha512-kyVMlfnI6MZojJlaRstdHj/CL3olOn4BszjQ155UQaDsGHUu0THmbhpfi4P0q3TCLLzcLM4pkuY3wJHPNl3ZOw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" sweetalert2 "^11.23.0" -"@abp/timeago@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.3.0.tgz#3214a6d000c79504c9fa7cb4e004a967fe46459b" - integrity sha512-Rgcex+jvM9Z9/E0I5Q3wlC2bY5550/+6xRX407k5ho4gxCccYo3c0Gkpiy5pJRFhTwtBgcNzYxArh6NCez+tDg== +"@abp/timeago@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.4.0.tgz#100fc0598e3a605346397f8a395bb96dc70f429d" + integrity sha512-G0ppO7Evai3DdhSg4ozxQpvIke0t3vyg63pNdfOtAPngmuam7WBM1WemBi4u29M84WcSfmk4KuAubXO0aCJgDg== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" timeago "^1.6.7" -"@abp/tui-editor@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/tui-editor/-/tui-editor-10.3.0.tgz#4fe3879111fe5ee312048841fca1cac43bb9bb62" - integrity sha512-6zGfaYSggalgpS9tKjESXT6w00KK7fKN71rG/hxRLNRpLGn5aapHdlAZRNEUgEFYo9czFdMW8POHpVA+sjcDMw== +"@abp/tui-editor@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/tui-editor/-/tui-editor-10.4.0.tgz#8041b62eb464f4a8adddc043904e2bc8475d45e3" + integrity sha512-SbbAs3V94UaF6xaMIaRNpDE9KSvOU45XZl24jCWjCAwkyTkN0bHlpnqs0UuJUtCnnQAzj4C+AvVy5ykrDKGtIg== dependencies: - "@abp/jquery" "~10.3.0" - "@abp/prismjs" "~10.3.0" + "@abp/jquery" "~10.4.0" + "@abp/prismjs" "~10.4.0" -"@abp/utils@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.3.0.tgz#d88c0d639d69d6210c3070892aac61d5e945c124" - integrity sha512-iNehxiqWJQl2wwE9EbsQANPv/ITMh0yUcPATUlG/McoLWdpPcqobx75NxXcHDqW8ZarecqSktjJDZPvaUaD+jA== +"@abp/utils@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.4.0.tgz#75175f798e58643c0dec1e85a502186c811e9226" + integrity sha512-BPc7KGDcbdFZ4GonAt3SOi/gYhG+iXI76TMlAzSKMBRhBU0vxFS8I/pb7U5zX0Gjh5M+aT0l2vA3l+POCHPBfw== dependencies: just-compare "^2.3.0" @@ -332,10 +332,10 @@ just-compare@^2.3.0: resolved "https://registry.yarnpkg.com/just-compare/-/just-compare-2.3.0.tgz#a2adcc1d1940536263275f5a1ef1298bcacfeda7" integrity sha512-6shoR7HDT+fzfL3gBahx1jZG3hWLrhPAf+l7nCwahDdT9XDtosB9kIF0ZrzUp5QY8dJWfQVr5rnsPqsbvflDzg== -lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lodash@^4.18.1: + version "4.18.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" + integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== luxon@^3.7.2: version "3.7.2" diff --git a/modules/client-simulation/demo/Volo.ClientSimulation.Demo/package.json b/modules/client-simulation/demo/Volo.ClientSimulation.Demo/package.json index 443413a399..667968540e 100644 --- a/modules/client-simulation/demo/Volo.ClientSimulation.Demo/package.json +++ b/modules/client-simulation/demo/Volo.ClientSimulation.Demo/package.json @@ -3,6 +3,6 @@ "name": "client-simulation-web", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.4.0" } } diff --git a/modules/client-simulation/demo/Volo.ClientSimulation.Demo/yarn.lock b/modules/client-simulation/demo/Volo.ClientSimulation.Demo/yarn.lock index 2e8f03c042..6cbad6797a 100644 --- a/modules/client-simulation/demo/Volo.ClientSimulation.Demo/yarn.lock +++ b/modules/client-simulation/demo/Volo.ClientSimulation.Demo/yarn.lock @@ -2,186 +2,186 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.3.0.tgz#bf48e8fa571067d15bc19bdd3f13509acf58f9c7" - integrity sha512-t9pHqrXaCDYcsdrz0VPKT28ujnnU+EVxyVDogQSRRpKsDoLhNcOD8NI3swdAWBQZ46YSCDfeDR+gfwjC3yqpXQ== +"@abp/aspnetcore.mvc.ui.theme.basic@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.4.0.tgz#c0414583f1c1fca14b0133818a63434b4fbf7df0" + integrity sha512-sOg3URmFRO/18D734B56jdFuC4LflGXXpw4w1Rdd77BJxugynxcmwNOQbLYFvMfqj0eKqDOH3E4yEcmS2IT1BQ== dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.shared" "~10.4.0" -"@abp/aspnetcore.mvc.ui.theme.shared@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.3.0.tgz#bcf72b5dd1dea285c3fa7cb1b6ce27c29b985ff7" - integrity sha512-X5xmMSxnIzNJPexvKR9y6jyn0v5n2wscN1SECG/bMI1YdKugMlkfkSuyUmLn+RX9/YdcK0jOsdMhfuzs5mgAOA== +"@abp/aspnetcore.mvc.ui.theme.shared@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.4.0.tgz#49002e55db00c98931d27357f7ea40eeab09e2e8" + integrity sha512-bNQ+WOnsfQhb98Z9YO0zZsW2K8ZEgOQ+7/86QaqoYTf+ZFvLJsICmNuHryNkCGRM1XJf9EaDZ3qSOwoOGQwS9A== dependencies: - "@abp/aspnetcore.mvc.ui" "~10.3.0" - "@abp/bootstrap" "~10.3.0" - "@abp/bootstrap-datepicker" "~10.3.0" - "@abp/bootstrap-daterangepicker" "~10.3.0" - "@abp/datatables.net-bs5" "~10.3.0" - "@abp/font-awesome" "~10.3.0" - "@abp/jquery-validation-unobtrusive" "~10.3.0" - "@abp/lodash" "~10.3.0" - "@abp/luxon" "~10.3.0" - "@abp/malihu-custom-scrollbar-plugin" "~10.3.0" - "@abp/moment" "~10.3.0" - "@abp/select2" "~10.3.0" - "@abp/sweetalert2" "~10.3.0" - "@abp/timeago" "~10.3.0" - -"@abp/aspnetcore.mvc.ui@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.3.0.tgz#0d125efd172b6772c516eae82cfe35f144da73fc" - integrity sha512-kp0XdwrlNz+NeLdxSJ6xdUaB5PM7xdbiLsIyTUcwa7AUKBtcVU6yq6qttbk9VyO0KqDgLywSuEL+9PkFhthuPg== + "@abp/aspnetcore.mvc.ui" "~10.4.0" + "@abp/bootstrap" "~10.4.0" + "@abp/bootstrap-datepicker" "~10.4.0" + "@abp/bootstrap-daterangepicker" "~10.4.0" + "@abp/datatables.net-bs5" "~10.4.0" + "@abp/font-awesome" "~10.4.0" + "@abp/jquery-validation-unobtrusive" "~10.4.0" + "@abp/lodash" "~10.4.0" + "@abp/luxon" "~10.4.0" + "@abp/malihu-custom-scrollbar-plugin" "~10.4.0" + "@abp/moment" "~10.4.0" + "@abp/select2" "~10.4.0" + "@abp/sweetalert2" "~10.4.0" + "@abp/timeago" "~10.4.0" + +"@abp/aspnetcore.mvc.ui@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.4.0.tgz#24e40fdc101c411f29a87adb77baa642299e979c" + integrity sha512-0qVFthS4f4fkCv//UY2GseO+NX+XYqlamZJJwOTmv1UXFW6LxAXxuEQqWYVfFKzwk2lbkXDUcgP8NhhQ9xp3Sw== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.3.0.tgz#032aaf0e474f971b9f90ca7fb83ff7ca4d7be933" - integrity sha512-7mRuRRIE4R0yw1cZuoIskgrqZPUa5/SJzse0K6u+/QDqvyLjjOsyE51WuqAXxlVfKpLp/fBvXZwnCDeCRU9iZQ== +"@abp/bootstrap-datepicker@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.4.0.tgz#a392fd5ce31c86e9fc07f7dedbd0c56a02af341e" + integrity sha512-kOpuCEO64zoJ7a9JASsCLFZIjEYgN0GG/oApS2L729pv6/oEhcupWpwrnvDUhsdGOezXwLIsgTzc6tiG3HxwGA== dependencies: bootstrap-datepicker "^1.10.1" -"@abp/bootstrap-daterangepicker@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.3.0.tgz#b2a9171595ee311c32f57460013870cde67c6b7f" - integrity sha512-ejLu3sWhfNDlsXeUXAQudZvPv0xM4McxIK65vuVEZiKIwFSnrLYil8jBi12EJJmAw5yYA/KC0EdyG82E+AWpmA== +"@abp/bootstrap-daterangepicker@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.4.0.tgz#95f68d3f488a12ede3c12352637b48782eeb031a" + integrity sha512-HX4HutkbPDQWBJNPDozpT1Um9mHcfV+RL+ukwEy0N4e7nD8VJyqLT/a+/FzQN7iUkwV+aNR2v5c0W+dgyqTaEA== dependencies: - "@abp/moment" "~10.3.0" + "@abp/moment" "~10.4.0" bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.3.0.tgz#23535c90613271b02fdad3eae1556b67844729e6" - integrity sha512-JnVOeJUyR78oo+QURvaNqa9xGZaVux0PRIpFpd4Qsqiiz9FvJrbCuSoBkkZoj/0eH23WoZ3bJxy0K9LDE19qvg== +"@abp/bootstrap@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.4.0.tgz#64b22bbdbf6b5bda279b21a7bae0652290b1221f" + integrity sha512-WQzXi5h1mej9twrAB6kD4aJtTNfSxpnuo1pxIOu/8GKGWoDayVY2NMQl+MCVel6dST+6EUTmuDRxc2q9qHRxLg== dependencies: - "@abp/core" "~10.3.0" - "@abp/popper.js" "~10.3.0" + "@abp/core" "~10.4.0" + "@abp/popper.js" "~10.4.0" bootstrap "^5.3.8" -"@abp/core@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.3.0.tgz#9960f8179d1fdc42250d0bbba60e379590c8199c" - integrity sha512-DpL4qrsyCUolypUwxKVDDK8bPVhNbYKx6l2ytUjoOu73CwgPoCSGkvHWrmKIsEsmTfOC9J7+GDErh8jWR/5pVA== +"@abp/core@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.4.0.tgz#de0c09e26d1f20451da9430d939beebb5bb6e9dd" + integrity sha512-3yKLFqUtFo5rgBPQxWkNPsdq1bE9VKwm02yB14lu913EIYhP//akizY4uSK08BozocJNveCASQbgd66z4x6v/Q== dependencies: - "@abp/utils" "~10.3.0" + "@abp/utils" "~10.4.0" -"@abp/datatables.net-bs5@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.3.0.tgz#d8711c52f7c03df51d14d1a5f0e6b00f6d9551fc" - integrity sha512-I8rEU98kEhsNZjVdpPvOcG6F4xr1CgIRb0bTf5bfd8ufzxEUKB+xF9bguJIB5kYwjme8QbMlcJ0V/c+N4iewtQ== +"@abp/datatables.net-bs5@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.4.0.tgz#a24eef99399324e4b89a00a204d2c4c2de379da1" + integrity sha512-T4f7e6sR1qHcvygNUNfdPWsK61C3NQORV35Lzpl98eDwY8XLg2/RLAegpFI8GjESVy2+2PCKVqUoSYZtXaMFmw== dependencies: - "@abp/datatables.net" "~10.3.0" + "@abp/datatables.net" "~10.4.0" datatables.net-bs5 "^2.3.4" -"@abp/datatables.net@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.3.0.tgz#f98319d8e226da5aced4d1d94d3ababb11327074" - integrity sha512-imM25WmO1V0hqeG5iPeLvYMID2HX7SG9drz47qilr74MhpY2i2J7HlsvZDonNkjpxzhK4DHpGeQd6mE2QzG7Hw== +"@abp/datatables.net@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.4.0.tgz#ab48f6e3ccae8d0644f0c01011ff98cc6ca16181" + integrity sha512-esIUozaSEa29G7bdWbEZr22fRChaTuGfNNB8rgBHmVKwgkbHSG7qUTgsfGhNz0T2vfDsK3X37n55xmSdE9RElg== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" datatables.net "^2.3.4" -"@abp/font-awesome@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.3.0.tgz#43602605f5633132cc6fe21b6ce5ab19b86bdf27" - integrity sha512-RUOVHxDyT81hp01DhUuSshKvLM+NiTt9VP+O4/rSj+SOaSJAm3wX7eziCKrDi6H41FwhoZL75CifLhcZVyVO4A== +"@abp/font-awesome@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.4.0.tgz#327979191615fff571f0ea2e99d01bbcca5378ce" + integrity sha512-RBzDSi/hrjhLKc7h6Kf9CQqC+LCGvS7h8HrRVaIzWCZSeqTfIMFpixjCUrNgkqWI0bU4NDUKXZ4XCOv4nPVlBg== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" "@fortawesome/fontawesome-free" "^7.0.1" -"@abp/jquery-validation-unobtrusive@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.3.0.tgz#5bc14ed642d48f9eae54761ad39bd9f51beeb005" - integrity sha512-j/9fA7tAs1/t3P2U1kKUiDlHAImmseul6KzvV77sNpnms7g4+x6Fgsa+sMzicSRGOLz7lK5RHdBFyvfKxt7/nA== +"@abp/jquery-validation-unobtrusive@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.4.0.tgz#e0114347344931667f48e0f2de5ea4bd6f9d2a88" + integrity sha512-AJncXVB+eTSizq0fTAwLii9a0GeYFPiY+gw+Ki5pak8Pp9LQivphsW32TqQmPC3qZVuYtd4myUUraWuOTT1Adg== dependencies: - "@abp/jquery-validation" "~10.3.0" + "@abp/jquery-validation" "~10.4.0" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.3.0.tgz#e669a54d28f6c0b863a284322e527a5c65cb7dbf" - integrity sha512-Wo95ZJLS1ScKm7dm3zFlEFUKqXrqo3m9+82DX5mFJUVCHHuBPrhxvDjxNJ18z4gpoOUu08uqxX7LFbphtkRM3g== +"@abp/jquery-validation@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.4.0.tgz#bd1ef0217c2500363f25223dd7316ba919a505b5" + integrity sha512-W9QiGnOpaq6uDDwBy4UEqWrP1p2UegHQShW5Cev9vr6g7Lk97UsS1Agh9lnjbvMzFSAiLl3M9gHmyMD4aU3NPA== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" jquery-validation "^1.21.0" -"@abp/jquery@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.3.0.tgz#3b60bcdfe8f9a33be79db1bea2500837fe09c67d" - integrity sha512-TndX/8bJx5vGAAwIykx2CJUZIFFDkwpD+cUjE45WO2dSrY2M5vNfWiuw95OcLGngO8RaT/gTkFGzPuZqKn9ggg== +"@abp/jquery@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.4.0.tgz#7ed3f1808682717ccfa7c6ac6e8db8a8b648b1e1" + integrity sha512-kjFoVIhqlaIHlEs917sIeP1sHmLWXMn+DVKaCkVQUXC2KVq+yvQRC8AZiFQVxOwLvzd9dFFpGwGZgpWkDfvhvg== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" jquery "~3.7.1" -"@abp/lodash@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.3.0.tgz#bd091fc811d85d2f535802631af6165ac1c8a198" - integrity sha512-7JDU9UlbD+9odhFQjolvyJZ6EQKG5kiL2Pt9T1RMVchNh47iEuZwfi8c+CIxa3YxA3n6lAsZ+8Pmxq4yOku+RQ== +"@abp/lodash@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.4.0.tgz#b6fc25b6e2fd74420ca16a5a13cb8fcd39b89efd" + integrity sha512-RuiIGAqJtSBS+4VAk4cg7Zsfj07B3xkiewCaK5j0xUcyjsCJKL1UdXe2zVNL3oE5bXTNMU7yNQWJS4knv2mfwA== dependencies: - "@abp/core" "~10.3.0" - lodash "^4.17.21" + "@abp/core" "~10.4.0" + lodash "^4.18.1" -"@abp/luxon@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.3.0.tgz#acbd92289456a546607ed4506dc36f71e6da53c9" - integrity sha512-IIi2+odQzuFK4qV1KJ2HtlTewSPby2F9CJFNNtNRMzX/P30dQkAGWpNV5/FZzJwFIihR+zSei5wlu11WpPugsw== +"@abp/luxon@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.4.0.tgz#731d20877ec679fa518b7199128676631d030a90" + integrity sha512-/hF+FBugJMkp/H9WNZxlsOy3Rl4DTeR2hqmRPI0sPcdFOixCziYrwzyHg/2Kx8cI/zHa6GRRt1B4Jz4yjQTvdw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" luxon "^3.7.2" -"@abp/malihu-custom-scrollbar-plugin@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.3.0.tgz#209931d02a994ef448371d6203e9ec74c72b61e6" - integrity sha512-hJjwEExP3hKE1MNDG+pcvoHRFUihh3t/0wCPNaoePFd8+QnJuyQ9U0VA+vdtykTxm8uFNE2uaCgyHffoHmzeZA== +"@abp/malihu-custom-scrollbar-plugin@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.4.0.tgz#e1c207d9c66cce64fcb4fe13c091c4b86b00b6d7" + integrity sha512-LC+imsGQganhKWybvQytATjQMXfQwfCfi6RRWREP+pejKPuHpqHzPd+AjKwZpx500K2/ZTwT62FKJ1uDiJXSKA== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.3.0.tgz#5a5c4ece14d1bc4dd33134f85d1d085733d3c975" - integrity sha512-8DKw2jgC/28nLrB/CR9BiyUSt0pUmWF+bEgAsduln6VrSCHn0aRLqgs+wi/9Ol+1bl7pO2wbJOYg1qbnkDhrAQ== +"@abp/moment@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.4.0.tgz#6737898b90b579ce5e0d683657757725a4def09d" + integrity sha512-WUtzjD6+1JGpgS6Yv2Qo/h4jM77LkkasPrywsetmtj/6zyVQWrkk7tKHTCjhAJtdinctcROwIIcwLsELi0nDHg== dependencies: moment "^2.30.1" -"@abp/popper.js@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-10.3.0.tgz#cb2415e45a905854980f4e864c00c8fe1156a6f9" - integrity sha512-Yfafs50t7DKnShxAl9WPpihZxG5cLAt99rJmIyXBYUQIpkHkxDyUIA5b4gCoVCqKgjFDOxG7i6L+Ovd/2N8k7g== +"@abp/popper.js@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-10.4.0.tgz#9d8d09c4e18020b6a75ba30daf6786606f555e84" + integrity sha512-KR5wGZ/MfR1rOPcmFr5ekraYhOSRul9gqempir69hlqrFKUFqt46F9/wLWyaJ7vlsMU6o4gBo6/jE5tICIpzNw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" "@popperjs/core" "^2.11.8" -"@abp/select2@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.3.0.tgz#199777173ca3e43a5448f8b51b30c3aafe4a3333" - integrity sha512-6sEIYIakVI1DM3eOqOaxwdG8vkyVFBWvVZhoChPZkgIIQ0EH2JLerHv0pePJEl6mQSxOUrikPA8++B2y+sBnQA== +"@abp/select2@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.4.0.tgz#e744988abf81e2194a5850acc4ac1333e9578df8" + integrity sha512-tyAdGzX32joI5vw+MUJGR0iToWCGZrbQg/zKeoCib80Zx5cn+e6GXj6wZyPbsJgVm0jZ7zBweWt3x8HzW9N8Ww== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" select2 "^4.0.13" -"@abp/sweetalert2@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.3.0.tgz#515e0477574b54bc6650b9f5ee193bfc648c715a" - integrity sha512-bY5MXEiKja+vk71ujSn8rpzRC45Ge8zvcca1k1vBJs+oKyRCii//JBxa3H0lagZo5r/QwIqv5ihdyLE1xsuLSg== +"@abp/sweetalert2@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.4.0.tgz#65cb26c8c7c9c02158c9a487969eef033fe04a73" + integrity sha512-kyVMlfnI6MZojJlaRstdHj/CL3olOn4BszjQ155UQaDsGHUu0THmbhpfi4P0q3TCLLzcLM4pkuY3wJHPNl3ZOw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" sweetalert2 "^11.23.0" -"@abp/timeago@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.3.0.tgz#3214a6d000c79504c9fa7cb4e004a967fe46459b" - integrity sha512-Rgcex+jvM9Z9/E0I5Q3wlC2bY5550/+6xRX407k5ho4gxCccYo3c0Gkpiy5pJRFhTwtBgcNzYxArh6NCez+tDg== +"@abp/timeago@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.4.0.tgz#100fc0598e3a605346397f8a395bb96dc70f429d" + integrity sha512-G0ppO7Evai3DdhSg4ozxQpvIke0t3vyg63pNdfOtAPngmuam7WBM1WemBi4u29M84WcSfmk4KuAubXO0aCJgDg== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" timeago "^1.6.7" -"@abp/utils@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.3.0.tgz#d88c0d639d69d6210c3070892aac61d5e945c124" - integrity sha512-iNehxiqWJQl2wwE9EbsQANPv/ITMh0yUcPATUlG/McoLWdpPcqobx75NxXcHDqW8ZarecqSktjJDZPvaUaD+jA== +"@abp/utils@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.4.0.tgz#75175f798e58643c0dec1e85a502186c811e9226" + integrity sha512-BPc7KGDcbdFZ4GonAt3SOi/gYhG+iXI76TMlAzSKMBRhBU0vxFS8I/pb7U5zX0Gjh5M+aT0l2vA3l+POCHPBfw== dependencies: just-compare "^2.3.0" @@ -268,10 +268,10 @@ just-compare@^2.3.0: resolved "https://registry.yarnpkg.com/just-compare/-/just-compare-2.3.0.tgz#a2adcc1d1940536263275f5a1ef1298bcacfeda7" integrity sha512-6shoR7HDT+fzfL3gBahx1jZG3hWLrhPAf+l7nCwahDdT9XDtosB9kIF0ZrzUp5QY8dJWfQVr5rnsPqsbvflDzg== -lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lodash@^4.18.1: + version "4.18.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" + integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== luxon@^3.7.2: version "3.7.2" diff --git a/modules/cms-kit/.abpstudio/state.json b/modules/cms-kit/.abpstudio/state.json deleted file mode 100644 index b0ef88d2f2..0000000000 --- a/modules/cms-kit/.abpstudio/state.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "selectedKubernetesProfile": null, - "solutionRunner": { - "selectedProfile": null, - "targetFrameworks": [], - "applicationsStartingWithoutBuild": [], - "applicationsWithoutAutoRefreshBrowserOnRestart": [], - "applicationBatchStartStates": [], - "folderBatchStartStates": [] - } -} \ No newline at end of file diff --git a/modules/cms-kit/angular/package.json b/modules/cms-kit/angular/package.json index 76ed594cef..d752aab145 100644 --- a/modules/cms-kit/angular/package.json +++ b/modules/cms-kit/angular/package.json @@ -15,11 +15,11 @@ }, "private": true, "dependencies": { - "@abp/ng.account": "~10.3.0", - "@abp/ng.identity": "~10.3.0", - "@abp/ng.setting-management": "~10.3.0", - "@abp/ng.tenant-management": "~10.3.0", - "@abp/ng.theme.basic": "~10.3.0", + "@abp/ng.account": "~10.4.0", + "@abp/ng.identity": "~10.4.0", + "@abp/ng.setting-management": "~10.4.0", + "@abp/ng.tenant-management": "~10.4.0", + "@abp/ng.theme.basic": "~10.4.0", "@angular/animations": "~10.0.0", "@angular/common": "~10.0.0", "@angular/compiler": "~10.0.0", diff --git a/modules/cms-kit/angular/projects/cms-kit/package.json b/modules/cms-kit/angular/projects/cms-kit/package.json index 752961a86e..d1c09fb92e 100644 --- a/modules/cms-kit/angular/projects/cms-kit/package.json +++ b/modules/cms-kit/angular/projects/cms-kit/package.json @@ -4,8 +4,8 @@ "peerDependencies": { "@angular/common": "^9.1.11", "@angular/core": "^9.1.11", - "@abp/ng.core": ">=10.3.0", - "@abp/ng.theme.shared": ">=10.3.0" + "@abp/ng.core": ">=10.4.0", + "@abp/ng.theme.shared": ">=10.4.0" }, "dependencies": { "tslib": "^2.0.0" diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/package.json b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/package.json index 821ff58fe3..9ab2f0e3a0 100644 --- a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/package.json +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/package.json @@ -3,6 +3,6 @@ "name": "my-app-identityserver", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.4.0" } } diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/yarn.lock b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/yarn.lock index 2e8f03c042..6cbad6797a 100644 --- a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/yarn.lock +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/yarn.lock @@ -2,186 +2,186 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.3.0.tgz#bf48e8fa571067d15bc19bdd3f13509acf58f9c7" - integrity sha512-t9pHqrXaCDYcsdrz0VPKT28ujnnU+EVxyVDogQSRRpKsDoLhNcOD8NI3swdAWBQZ46YSCDfeDR+gfwjC3yqpXQ== +"@abp/aspnetcore.mvc.ui.theme.basic@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.4.0.tgz#c0414583f1c1fca14b0133818a63434b4fbf7df0" + integrity sha512-sOg3URmFRO/18D734B56jdFuC4LflGXXpw4w1Rdd77BJxugynxcmwNOQbLYFvMfqj0eKqDOH3E4yEcmS2IT1BQ== dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.shared" "~10.4.0" -"@abp/aspnetcore.mvc.ui.theme.shared@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.3.0.tgz#bcf72b5dd1dea285c3fa7cb1b6ce27c29b985ff7" - integrity sha512-X5xmMSxnIzNJPexvKR9y6jyn0v5n2wscN1SECG/bMI1YdKugMlkfkSuyUmLn+RX9/YdcK0jOsdMhfuzs5mgAOA== +"@abp/aspnetcore.mvc.ui.theme.shared@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.4.0.tgz#49002e55db00c98931d27357f7ea40eeab09e2e8" + integrity sha512-bNQ+WOnsfQhb98Z9YO0zZsW2K8ZEgOQ+7/86QaqoYTf+ZFvLJsICmNuHryNkCGRM1XJf9EaDZ3qSOwoOGQwS9A== dependencies: - "@abp/aspnetcore.mvc.ui" "~10.3.0" - "@abp/bootstrap" "~10.3.0" - "@abp/bootstrap-datepicker" "~10.3.0" - "@abp/bootstrap-daterangepicker" "~10.3.0" - "@abp/datatables.net-bs5" "~10.3.0" - "@abp/font-awesome" "~10.3.0" - "@abp/jquery-validation-unobtrusive" "~10.3.0" - "@abp/lodash" "~10.3.0" - "@abp/luxon" "~10.3.0" - "@abp/malihu-custom-scrollbar-plugin" "~10.3.0" - "@abp/moment" "~10.3.0" - "@abp/select2" "~10.3.0" - "@abp/sweetalert2" "~10.3.0" - "@abp/timeago" "~10.3.0" - -"@abp/aspnetcore.mvc.ui@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.3.0.tgz#0d125efd172b6772c516eae82cfe35f144da73fc" - integrity sha512-kp0XdwrlNz+NeLdxSJ6xdUaB5PM7xdbiLsIyTUcwa7AUKBtcVU6yq6qttbk9VyO0KqDgLywSuEL+9PkFhthuPg== + "@abp/aspnetcore.mvc.ui" "~10.4.0" + "@abp/bootstrap" "~10.4.0" + "@abp/bootstrap-datepicker" "~10.4.0" + "@abp/bootstrap-daterangepicker" "~10.4.0" + "@abp/datatables.net-bs5" "~10.4.0" + "@abp/font-awesome" "~10.4.0" + "@abp/jquery-validation-unobtrusive" "~10.4.0" + "@abp/lodash" "~10.4.0" + "@abp/luxon" "~10.4.0" + "@abp/malihu-custom-scrollbar-plugin" "~10.4.0" + "@abp/moment" "~10.4.0" + "@abp/select2" "~10.4.0" + "@abp/sweetalert2" "~10.4.0" + "@abp/timeago" "~10.4.0" + +"@abp/aspnetcore.mvc.ui@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.4.0.tgz#24e40fdc101c411f29a87adb77baa642299e979c" + integrity sha512-0qVFthS4f4fkCv//UY2GseO+NX+XYqlamZJJwOTmv1UXFW6LxAXxuEQqWYVfFKzwk2lbkXDUcgP8NhhQ9xp3Sw== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.3.0.tgz#032aaf0e474f971b9f90ca7fb83ff7ca4d7be933" - integrity sha512-7mRuRRIE4R0yw1cZuoIskgrqZPUa5/SJzse0K6u+/QDqvyLjjOsyE51WuqAXxlVfKpLp/fBvXZwnCDeCRU9iZQ== +"@abp/bootstrap-datepicker@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.4.0.tgz#a392fd5ce31c86e9fc07f7dedbd0c56a02af341e" + integrity sha512-kOpuCEO64zoJ7a9JASsCLFZIjEYgN0GG/oApS2L729pv6/oEhcupWpwrnvDUhsdGOezXwLIsgTzc6tiG3HxwGA== dependencies: bootstrap-datepicker "^1.10.1" -"@abp/bootstrap-daterangepicker@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.3.0.tgz#b2a9171595ee311c32f57460013870cde67c6b7f" - integrity sha512-ejLu3sWhfNDlsXeUXAQudZvPv0xM4McxIK65vuVEZiKIwFSnrLYil8jBi12EJJmAw5yYA/KC0EdyG82E+AWpmA== +"@abp/bootstrap-daterangepicker@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.4.0.tgz#95f68d3f488a12ede3c12352637b48782eeb031a" + integrity sha512-HX4HutkbPDQWBJNPDozpT1Um9mHcfV+RL+ukwEy0N4e7nD8VJyqLT/a+/FzQN7iUkwV+aNR2v5c0W+dgyqTaEA== dependencies: - "@abp/moment" "~10.3.0" + "@abp/moment" "~10.4.0" bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.3.0.tgz#23535c90613271b02fdad3eae1556b67844729e6" - integrity sha512-JnVOeJUyR78oo+QURvaNqa9xGZaVux0PRIpFpd4Qsqiiz9FvJrbCuSoBkkZoj/0eH23WoZ3bJxy0K9LDE19qvg== +"@abp/bootstrap@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.4.0.tgz#64b22bbdbf6b5bda279b21a7bae0652290b1221f" + integrity sha512-WQzXi5h1mej9twrAB6kD4aJtTNfSxpnuo1pxIOu/8GKGWoDayVY2NMQl+MCVel6dST+6EUTmuDRxc2q9qHRxLg== dependencies: - "@abp/core" "~10.3.0" - "@abp/popper.js" "~10.3.0" + "@abp/core" "~10.4.0" + "@abp/popper.js" "~10.4.0" bootstrap "^5.3.8" -"@abp/core@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.3.0.tgz#9960f8179d1fdc42250d0bbba60e379590c8199c" - integrity sha512-DpL4qrsyCUolypUwxKVDDK8bPVhNbYKx6l2ytUjoOu73CwgPoCSGkvHWrmKIsEsmTfOC9J7+GDErh8jWR/5pVA== +"@abp/core@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.4.0.tgz#de0c09e26d1f20451da9430d939beebb5bb6e9dd" + integrity sha512-3yKLFqUtFo5rgBPQxWkNPsdq1bE9VKwm02yB14lu913EIYhP//akizY4uSK08BozocJNveCASQbgd66z4x6v/Q== dependencies: - "@abp/utils" "~10.3.0" + "@abp/utils" "~10.4.0" -"@abp/datatables.net-bs5@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.3.0.tgz#d8711c52f7c03df51d14d1a5f0e6b00f6d9551fc" - integrity sha512-I8rEU98kEhsNZjVdpPvOcG6F4xr1CgIRb0bTf5bfd8ufzxEUKB+xF9bguJIB5kYwjme8QbMlcJ0V/c+N4iewtQ== +"@abp/datatables.net-bs5@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.4.0.tgz#a24eef99399324e4b89a00a204d2c4c2de379da1" + integrity sha512-T4f7e6sR1qHcvygNUNfdPWsK61C3NQORV35Lzpl98eDwY8XLg2/RLAegpFI8GjESVy2+2PCKVqUoSYZtXaMFmw== dependencies: - "@abp/datatables.net" "~10.3.0" + "@abp/datatables.net" "~10.4.0" datatables.net-bs5 "^2.3.4" -"@abp/datatables.net@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.3.0.tgz#f98319d8e226da5aced4d1d94d3ababb11327074" - integrity sha512-imM25WmO1V0hqeG5iPeLvYMID2HX7SG9drz47qilr74MhpY2i2J7HlsvZDonNkjpxzhK4DHpGeQd6mE2QzG7Hw== +"@abp/datatables.net@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.4.0.tgz#ab48f6e3ccae8d0644f0c01011ff98cc6ca16181" + integrity sha512-esIUozaSEa29G7bdWbEZr22fRChaTuGfNNB8rgBHmVKwgkbHSG7qUTgsfGhNz0T2vfDsK3X37n55xmSdE9RElg== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" datatables.net "^2.3.4" -"@abp/font-awesome@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.3.0.tgz#43602605f5633132cc6fe21b6ce5ab19b86bdf27" - integrity sha512-RUOVHxDyT81hp01DhUuSshKvLM+NiTt9VP+O4/rSj+SOaSJAm3wX7eziCKrDi6H41FwhoZL75CifLhcZVyVO4A== +"@abp/font-awesome@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.4.0.tgz#327979191615fff571f0ea2e99d01bbcca5378ce" + integrity sha512-RBzDSi/hrjhLKc7h6Kf9CQqC+LCGvS7h8HrRVaIzWCZSeqTfIMFpixjCUrNgkqWI0bU4NDUKXZ4XCOv4nPVlBg== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" "@fortawesome/fontawesome-free" "^7.0.1" -"@abp/jquery-validation-unobtrusive@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.3.0.tgz#5bc14ed642d48f9eae54761ad39bd9f51beeb005" - integrity sha512-j/9fA7tAs1/t3P2U1kKUiDlHAImmseul6KzvV77sNpnms7g4+x6Fgsa+sMzicSRGOLz7lK5RHdBFyvfKxt7/nA== +"@abp/jquery-validation-unobtrusive@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.4.0.tgz#e0114347344931667f48e0f2de5ea4bd6f9d2a88" + integrity sha512-AJncXVB+eTSizq0fTAwLii9a0GeYFPiY+gw+Ki5pak8Pp9LQivphsW32TqQmPC3qZVuYtd4myUUraWuOTT1Adg== dependencies: - "@abp/jquery-validation" "~10.3.0" + "@abp/jquery-validation" "~10.4.0" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.3.0.tgz#e669a54d28f6c0b863a284322e527a5c65cb7dbf" - integrity sha512-Wo95ZJLS1ScKm7dm3zFlEFUKqXrqo3m9+82DX5mFJUVCHHuBPrhxvDjxNJ18z4gpoOUu08uqxX7LFbphtkRM3g== +"@abp/jquery-validation@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.4.0.tgz#bd1ef0217c2500363f25223dd7316ba919a505b5" + integrity sha512-W9QiGnOpaq6uDDwBy4UEqWrP1p2UegHQShW5Cev9vr6g7Lk97UsS1Agh9lnjbvMzFSAiLl3M9gHmyMD4aU3NPA== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" jquery-validation "^1.21.0" -"@abp/jquery@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.3.0.tgz#3b60bcdfe8f9a33be79db1bea2500837fe09c67d" - integrity sha512-TndX/8bJx5vGAAwIykx2CJUZIFFDkwpD+cUjE45WO2dSrY2M5vNfWiuw95OcLGngO8RaT/gTkFGzPuZqKn9ggg== +"@abp/jquery@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.4.0.tgz#7ed3f1808682717ccfa7c6ac6e8db8a8b648b1e1" + integrity sha512-kjFoVIhqlaIHlEs917sIeP1sHmLWXMn+DVKaCkVQUXC2KVq+yvQRC8AZiFQVxOwLvzd9dFFpGwGZgpWkDfvhvg== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" jquery "~3.7.1" -"@abp/lodash@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.3.0.tgz#bd091fc811d85d2f535802631af6165ac1c8a198" - integrity sha512-7JDU9UlbD+9odhFQjolvyJZ6EQKG5kiL2Pt9T1RMVchNh47iEuZwfi8c+CIxa3YxA3n6lAsZ+8Pmxq4yOku+RQ== +"@abp/lodash@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.4.0.tgz#b6fc25b6e2fd74420ca16a5a13cb8fcd39b89efd" + integrity sha512-RuiIGAqJtSBS+4VAk4cg7Zsfj07B3xkiewCaK5j0xUcyjsCJKL1UdXe2zVNL3oE5bXTNMU7yNQWJS4knv2mfwA== dependencies: - "@abp/core" "~10.3.0" - lodash "^4.17.21" + "@abp/core" "~10.4.0" + lodash "^4.18.1" -"@abp/luxon@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.3.0.tgz#acbd92289456a546607ed4506dc36f71e6da53c9" - integrity sha512-IIi2+odQzuFK4qV1KJ2HtlTewSPby2F9CJFNNtNRMzX/P30dQkAGWpNV5/FZzJwFIihR+zSei5wlu11WpPugsw== +"@abp/luxon@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.4.0.tgz#731d20877ec679fa518b7199128676631d030a90" + integrity sha512-/hF+FBugJMkp/H9WNZxlsOy3Rl4DTeR2hqmRPI0sPcdFOixCziYrwzyHg/2Kx8cI/zHa6GRRt1B4Jz4yjQTvdw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" luxon "^3.7.2" -"@abp/malihu-custom-scrollbar-plugin@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.3.0.tgz#209931d02a994ef448371d6203e9ec74c72b61e6" - integrity sha512-hJjwEExP3hKE1MNDG+pcvoHRFUihh3t/0wCPNaoePFd8+QnJuyQ9U0VA+vdtykTxm8uFNE2uaCgyHffoHmzeZA== +"@abp/malihu-custom-scrollbar-plugin@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.4.0.tgz#e1c207d9c66cce64fcb4fe13c091c4b86b00b6d7" + integrity sha512-LC+imsGQganhKWybvQytATjQMXfQwfCfi6RRWREP+pejKPuHpqHzPd+AjKwZpx500K2/ZTwT62FKJ1uDiJXSKA== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.3.0.tgz#5a5c4ece14d1bc4dd33134f85d1d085733d3c975" - integrity sha512-8DKw2jgC/28nLrB/CR9BiyUSt0pUmWF+bEgAsduln6VrSCHn0aRLqgs+wi/9Ol+1bl7pO2wbJOYg1qbnkDhrAQ== +"@abp/moment@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.4.0.tgz#6737898b90b579ce5e0d683657757725a4def09d" + integrity sha512-WUtzjD6+1JGpgS6Yv2Qo/h4jM77LkkasPrywsetmtj/6zyVQWrkk7tKHTCjhAJtdinctcROwIIcwLsELi0nDHg== dependencies: moment "^2.30.1" -"@abp/popper.js@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-10.3.0.tgz#cb2415e45a905854980f4e864c00c8fe1156a6f9" - integrity sha512-Yfafs50t7DKnShxAl9WPpihZxG5cLAt99rJmIyXBYUQIpkHkxDyUIA5b4gCoVCqKgjFDOxG7i6L+Ovd/2N8k7g== +"@abp/popper.js@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-10.4.0.tgz#9d8d09c4e18020b6a75ba30daf6786606f555e84" + integrity sha512-KR5wGZ/MfR1rOPcmFr5ekraYhOSRul9gqempir69hlqrFKUFqt46F9/wLWyaJ7vlsMU6o4gBo6/jE5tICIpzNw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" "@popperjs/core" "^2.11.8" -"@abp/select2@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.3.0.tgz#199777173ca3e43a5448f8b51b30c3aafe4a3333" - integrity sha512-6sEIYIakVI1DM3eOqOaxwdG8vkyVFBWvVZhoChPZkgIIQ0EH2JLerHv0pePJEl6mQSxOUrikPA8++B2y+sBnQA== +"@abp/select2@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.4.0.tgz#e744988abf81e2194a5850acc4ac1333e9578df8" + integrity sha512-tyAdGzX32joI5vw+MUJGR0iToWCGZrbQg/zKeoCib80Zx5cn+e6GXj6wZyPbsJgVm0jZ7zBweWt3x8HzW9N8Ww== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" select2 "^4.0.13" -"@abp/sweetalert2@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.3.0.tgz#515e0477574b54bc6650b9f5ee193bfc648c715a" - integrity sha512-bY5MXEiKja+vk71ujSn8rpzRC45Ge8zvcca1k1vBJs+oKyRCii//JBxa3H0lagZo5r/QwIqv5ihdyLE1xsuLSg== +"@abp/sweetalert2@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.4.0.tgz#65cb26c8c7c9c02158c9a487969eef033fe04a73" + integrity sha512-kyVMlfnI6MZojJlaRstdHj/CL3olOn4BszjQ155UQaDsGHUu0THmbhpfi4P0q3TCLLzcLM4pkuY3wJHPNl3ZOw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" sweetalert2 "^11.23.0" -"@abp/timeago@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.3.0.tgz#3214a6d000c79504c9fa7cb4e004a967fe46459b" - integrity sha512-Rgcex+jvM9Z9/E0I5Q3wlC2bY5550/+6xRX407k5ho4gxCccYo3c0Gkpiy5pJRFhTwtBgcNzYxArh6NCez+tDg== +"@abp/timeago@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.4.0.tgz#100fc0598e3a605346397f8a395bb96dc70f429d" + integrity sha512-G0ppO7Evai3DdhSg4ozxQpvIke0t3vyg63pNdfOtAPngmuam7WBM1WemBi4u29M84WcSfmk4KuAubXO0aCJgDg== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" timeago "^1.6.7" -"@abp/utils@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.3.0.tgz#d88c0d639d69d6210c3070892aac61d5e945c124" - integrity sha512-iNehxiqWJQl2wwE9EbsQANPv/ITMh0yUcPATUlG/McoLWdpPcqobx75NxXcHDqW8ZarecqSktjJDZPvaUaD+jA== +"@abp/utils@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.4.0.tgz#75175f798e58643c0dec1e85a502186c811e9226" + integrity sha512-BPc7KGDcbdFZ4GonAt3SOi/gYhG+iXI76TMlAzSKMBRhBU0vxFS8I/pb7U5zX0Gjh5M+aT0l2vA3l+POCHPBfw== dependencies: just-compare "^2.3.0" @@ -268,10 +268,10 @@ just-compare@^2.3.0: resolved "https://registry.yarnpkg.com/just-compare/-/just-compare-2.3.0.tgz#a2adcc1d1940536263275f5a1ef1298bcacfeda7" integrity sha512-6shoR7HDT+fzfL3gBahx1jZG3hWLrhPAf+l7nCwahDdT9XDtosB9kIF0ZrzUp5QY8dJWfQVr5rnsPqsbvflDzg== -lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lodash@^4.18.1: + version "4.18.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" + integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== luxon@^3.7.2: version "3.7.2" diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Host/package.json b/modules/cms-kit/host/Volo.CmsKit.Web.Host/package.json index abb49011d8..0b8b07c6be 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Host/package.json +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Host/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.4.0" } } diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Host/yarn.lock b/modules/cms-kit/host/Volo.CmsKit.Web.Host/yarn.lock index 2e8f03c042..6cbad6797a 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Host/yarn.lock +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Host/yarn.lock @@ -2,186 +2,186 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.3.0.tgz#bf48e8fa571067d15bc19bdd3f13509acf58f9c7" - integrity sha512-t9pHqrXaCDYcsdrz0VPKT28ujnnU+EVxyVDogQSRRpKsDoLhNcOD8NI3swdAWBQZ46YSCDfeDR+gfwjC3yqpXQ== +"@abp/aspnetcore.mvc.ui.theme.basic@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.4.0.tgz#c0414583f1c1fca14b0133818a63434b4fbf7df0" + integrity sha512-sOg3URmFRO/18D734B56jdFuC4LflGXXpw4w1Rdd77BJxugynxcmwNOQbLYFvMfqj0eKqDOH3E4yEcmS2IT1BQ== dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.shared" "~10.4.0" -"@abp/aspnetcore.mvc.ui.theme.shared@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.3.0.tgz#bcf72b5dd1dea285c3fa7cb1b6ce27c29b985ff7" - integrity sha512-X5xmMSxnIzNJPexvKR9y6jyn0v5n2wscN1SECG/bMI1YdKugMlkfkSuyUmLn+RX9/YdcK0jOsdMhfuzs5mgAOA== +"@abp/aspnetcore.mvc.ui.theme.shared@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.4.0.tgz#49002e55db00c98931d27357f7ea40eeab09e2e8" + integrity sha512-bNQ+WOnsfQhb98Z9YO0zZsW2K8ZEgOQ+7/86QaqoYTf+ZFvLJsICmNuHryNkCGRM1XJf9EaDZ3qSOwoOGQwS9A== dependencies: - "@abp/aspnetcore.mvc.ui" "~10.3.0" - "@abp/bootstrap" "~10.3.0" - "@abp/bootstrap-datepicker" "~10.3.0" - "@abp/bootstrap-daterangepicker" "~10.3.0" - "@abp/datatables.net-bs5" "~10.3.0" - "@abp/font-awesome" "~10.3.0" - "@abp/jquery-validation-unobtrusive" "~10.3.0" - "@abp/lodash" "~10.3.0" - "@abp/luxon" "~10.3.0" - "@abp/malihu-custom-scrollbar-plugin" "~10.3.0" - "@abp/moment" "~10.3.0" - "@abp/select2" "~10.3.0" - "@abp/sweetalert2" "~10.3.0" - "@abp/timeago" "~10.3.0" - -"@abp/aspnetcore.mvc.ui@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.3.0.tgz#0d125efd172b6772c516eae82cfe35f144da73fc" - integrity sha512-kp0XdwrlNz+NeLdxSJ6xdUaB5PM7xdbiLsIyTUcwa7AUKBtcVU6yq6qttbk9VyO0KqDgLywSuEL+9PkFhthuPg== + "@abp/aspnetcore.mvc.ui" "~10.4.0" + "@abp/bootstrap" "~10.4.0" + "@abp/bootstrap-datepicker" "~10.4.0" + "@abp/bootstrap-daterangepicker" "~10.4.0" + "@abp/datatables.net-bs5" "~10.4.0" + "@abp/font-awesome" "~10.4.0" + "@abp/jquery-validation-unobtrusive" "~10.4.0" + "@abp/lodash" "~10.4.0" + "@abp/luxon" "~10.4.0" + "@abp/malihu-custom-scrollbar-plugin" "~10.4.0" + "@abp/moment" "~10.4.0" + "@abp/select2" "~10.4.0" + "@abp/sweetalert2" "~10.4.0" + "@abp/timeago" "~10.4.0" + +"@abp/aspnetcore.mvc.ui@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.4.0.tgz#24e40fdc101c411f29a87adb77baa642299e979c" + integrity sha512-0qVFthS4f4fkCv//UY2GseO+NX+XYqlamZJJwOTmv1UXFW6LxAXxuEQqWYVfFKzwk2lbkXDUcgP8NhhQ9xp3Sw== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.3.0.tgz#032aaf0e474f971b9f90ca7fb83ff7ca4d7be933" - integrity sha512-7mRuRRIE4R0yw1cZuoIskgrqZPUa5/SJzse0K6u+/QDqvyLjjOsyE51WuqAXxlVfKpLp/fBvXZwnCDeCRU9iZQ== +"@abp/bootstrap-datepicker@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.4.0.tgz#a392fd5ce31c86e9fc07f7dedbd0c56a02af341e" + integrity sha512-kOpuCEO64zoJ7a9JASsCLFZIjEYgN0GG/oApS2L729pv6/oEhcupWpwrnvDUhsdGOezXwLIsgTzc6tiG3HxwGA== dependencies: bootstrap-datepicker "^1.10.1" -"@abp/bootstrap-daterangepicker@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.3.0.tgz#b2a9171595ee311c32f57460013870cde67c6b7f" - integrity sha512-ejLu3sWhfNDlsXeUXAQudZvPv0xM4McxIK65vuVEZiKIwFSnrLYil8jBi12EJJmAw5yYA/KC0EdyG82E+AWpmA== +"@abp/bootstrap-daterangepicker@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.4.0.tgz#95f68d3f488a12ede3c12352637b48782eeb031a" + integrity sha512-HX4HutkbPDQWBJNPDozpT1Um9mHcfV+RL+ukwEy0N4e7nD8VJyqLT/a+/FzQN7iUkwV+aNR2v5c0W+dgyqTaEA== dependencies: - "@abp/moment" "~10.3.0" + "@abp/moment" "~10.4.0" bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.3.0.tgz#23535c90613271b02fdad3eae1556b67844729e6" - integrity sha512-JnVOeJUyR78oo+QURvaNqa9xGZaVux0PRIpFpd4Qsqiiz9FvJrbCuSoBkkZoj/0eH23WoZ3bJxy0K9LDE19qvg== +"@abp/bootstrap@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.4.0.tgz#64b22bbdbf6b5bda279b21a7bae0652290b1221f" + integrity sha512-WQzXi5h1mej9twrAB6kD4aJtTNfSxpnuo1pxIOu/8GKGWoDayVY2NMQl+MCVel6dST+6EUTmuDRxc2q9qHRxLg== dependencies: - "@abp/core" "~10.3.0" - "@abp/popper.js" "~10.3.0" + "@abp/core" "~10.4.0" + "@abp/popper.js" "~10.4.0" bootstrap "^5.3.8" -"@abp/core@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.3.0.tgz#9960f8179d1fdc42250d0bbba60e379590c8199c" - integrity sha512-DpL4qrsyCUolypUwxKVDDK8bPVhNbYKx6l2ytUjoOu73CwgPoCSGkvHWrmKIsEsmTfOC9J7+GDErh8jWR/5pVA== +"@abp/core@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.4.0.tgz#de0c09e26d1f20451da9430d939beebb5bb6e9dd" + integrity sha512-3yKLFqUtFo5rgBPQxWkNPsdq1bE9VKwm02yB14lu913EIYhP//akizY4uSK08BozocJNveCASQbgd66z4x6v/Q== dependencies: - "@abp/utils" "~10.3.0" + "@abp/utils" "~10.4.0" -"@abp/datatables.net-bs5@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.3.0.tgz#d8711c52f7c03df51d14d1a5f0e6b00f6d9551fc" - integrity sha512-I8rEU98kEhsNZjVdpPvOcG6F4xr1CgIRb0bTf5bfd8ufzxEUKB+xF9bguJIB5kYwjme8QbMlcJ0V/c+N4iewtQ== +"@abp/datatables.net-bs5@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.4.0.tgz#a24eef99399324e4b89a00a204d2c4c2de379da1" + integrity sha512-T4f7e6sR1qHcvygNUNfdPWsK61C3NQORV35Lzpl98eDwY8XLg2/RLAegpFI8GjESVy2+2PCKVqUoSYZtXaMFmw== dependencies: - "@abp/datatables.net" "~10.3.0" + "@abp/datatables.net" "~10.4.0" datatables.net-bs5 "^2.3.4" -"@abp/datatables.net@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.3.0.tgz#f98319d8e226da5aced4d1d94d3ababb11327074" - integrity sha512-imM25WmO1V0hqeG5iPeLvYMID2HX7SG9drz47qilr74MhpY2i2J7HlsvZDonNkjpxzhK4DHpGeQd6mE2QzG7Hw== +"@abp/datatables.net@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.4.0.tgz#ab48f6e3ccae8d0644f0c01011ff98cc6ca16181" + integrity sha512-esIUozaSEa29G7bdWbEZr22fRChaTuGfNNB8rgBHmVKwgkbHSG7qUTgsfGhNz0T2vfDsK3X37n55xmSdE9RElg== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" datatables.net "^2.3.4" -"@abp/font-awesome@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.3.0.tgz#43602605f5633132cc6fe21b6ce5ab19b86bdf27" - integrity sha512-RUOVHxDyT81hp01DhUuSshKvLM+NiTt9VP+O4/rSj+SOaSJAm3wX7eziCKrDi6H41FwhoZL75CifLhcZVyVO4A== +"@abp/font-awesome@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.4.0.tgz#327979191615fff571f0ea2e99d01bbcca5378ce" + integrity sha512-RBzDSi/hrjhLKc7h6Kf9CQqC+LCGvS7h8HrRVaIzWCZSeqTfIMFpixjCUrNgkqWI0bU4NDUKXZ4XCOv4nPVlBg== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" "@fortawesome/fontawesome-free" "^7.0.1" -"@abp/jquery-validation-unobtrusive@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.3.0.tgz#5bc14ed642d48f9eae54761ad39bd9f51beeb005" - integrity sha512-j/9fA7tAs1/t3P2U1kKUiDlHAImmseul6KzvV77sNpnms7g4+x6Fgsa+sMzicSRGOLz7lK5RHdBFyvfKxt7/nA== +"@abp/jquery-validation-unobtrusive@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.4.0.tgz#e0114347344931667f48e0f2de5ea4bd6f9d2a88" + integrity sha512-AJncXVB+eTSizq0fTAwLii9a0GeYFPiY+gw+Ki5pak8Pp9LQivphsW32TqQmPC3qZVuYtd4myUUraWuOTT1Adg== dependencies: - "@abp/jquery-validation" "~10.3.0" + "@abp/jquery-validation" "~10.4.0" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.3.0.tgz#e669a54d28f6c0b863a284322e527a5c65cb7dbf" - integrity sha512-Wo95ZJLS1ScKm7dm3zFlEFUKqXrqo3m9+82DX5mFJUVCHHuBPrhxvDjxNJ18z4gpoOUu08uqxX7LFbphtkRM3g== +"@abp/jquery-validation@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.4.0.tgz#bd1ef0217c2500363f25223dd7316ba919a505b5" + integrity sha512-W9QiGnOpaq6uDDwBy4UEqWrP1p2UegHQShW5Cev9vr6g7Lk97UsS1Agh9lnjbvMzFSAiLl3M9gHmyMD4aU3NPA== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" jquery-validation "^1.21.0" -"@abp/jquery@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.3.0.tgz#3b60bcdfe8f9a33be79db1bea2500837fe09c67d" - integrity sha512-TndX/8bJx5vGAAwIykx2CJUZIFFDkwpD+cUjE45WO2dSrY2M5vNfWiuw95OcLGngO8RaT/gTkFGzPuZqKn9ggg== +"@abp/jquery@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.4.0.tgz#7ed3f1808682717ccfa7c6ac6e8db8a8b648b1e1" + integrity sha512-kjFoVIhqlaIHlEs917sIeP1sHmLWXMn+DVKaCkVQUXC2KVq+yvQRC8AZiFQVxOwLvzd9dFFpGwGZgpWkDfvhvg== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" jquery "~3.7.1" -"@abp/lodash@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.3.0.tgz#bd091fc811d85d2f535802631af6165ac1c8a198" - integrity sha512-7JDU9UlbD+9odhFQjolvyJZ6EQKG5kiL2Pt9T1RMVchNh47iEuZwfi8c+CIxa3YxA3n6lAsZ+8Pmxq4yOku+RQ== +"@abp/lodash@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.4.0.tgz#b6fc25b6e2fd74420ca16a5a13cb8fcd39b89efd" + integrity sha512-RuiIGAqJtSBS+4VAk4cg7Zsfj07B3xkiewCaK5j0xUcyjsCJKL1UdXe2zVNL3oE5bXTNMU7yNQWJS4knv2mfwA== dependencies: - "@abp/core" "~10.3.0" - lodash "^4.17.21" + "@abp/core" "~10.4.0" + lodash "^4.18.1" -"@abp/luxon@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.3.0.tgz#acbd92289456a546607ed4506dc36f71e6da53c9" - integrity sha512-IIi2+odQzuFK4qV1KJ2HtlTewSPby2F9CJFNNtNRMzX/P30dQkAGWpNV5/FZzJwFIihR+zSei5wlu11WpPugsw== +"@abp/luxon@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.4.0.tgz#731d20877ec679fa518b7199128676631d030a90" + integrity sha512-/hF+FBugJMkp/H9WNZxlsOy3Rl4DTeR2hqmRPI0sPcdFOixCziYrwzyHg/2Kx8cI/zHa6GRRt1B4Jz4yjQTvdw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" luxon "^3.7.2" -"@abp/malihu-custom-scrollbar-plugin@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.3.0.tgz#209931d02a994ef448371d6203e9ec74c72b61e6" - integrity sha512-hJjwEExP3hKE1MNDG+pcvoHRFUihh3t/0wCPNaoePFd8+QnJuyQ9U0VA+vdtykTxm8uFNE2uaCgyHffoHmzeZA== +"@abp/malihu-custom-scrollbar-plugin@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.4.0.tgz#e1c207d9c66cce64fcb4fe13c091c4b86b00b6d7" + integrity sha512-LC+imsGQganhKWybvQytATjQMXfQwfCfi6RRWREP+pejKPuHpqHzPd+AjKwZpx500K2/ZTwT62FKJ1uDiJXSKA== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.3.0.tgz#5a5c4ece14d1bc4dd33134f85d1d085733d3c975" - integrity sha512-8DKw2jgC/28nLrB/CR9BiyUSt0pUmWF+bEgAsduln6VrSCHn0aRLqgs+wi/9Ol+1bl7pO2wbJOYg1qbnkDhrAQ== +"@abp/moment@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.4.0.tgz#6737898b90b579ce5e0d683657757725a4def09d" + integrity sha512-WUtzjD6+1JGpgS6Yv2Qo/h4jM77LkkasPrywsetmtj/6zyVQWrkk7tKHTCjhAJtdinctcROwIIcwLsELi0nDHg== dependencies: moment "^2.30.1" -"@abp/popper.js@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-10.3.0.tgz#cb2415e45a905854980f4e864c00c8fe1156a6f9" - integrity sha512-Yfafs50t7DKnShxAl9WPpihZxG5cLAt99rJmIyXBYUQIpkHkxDyUIA5b4gCoVCqKgjFDOxG7i6L+Ovd/2N8k7g== +"@abp/popper.js@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-10.4.0.tgz#9d8d09c4e18020b6a75ba30daf6786606f555e84" + integrity sha512-KR5wGZ/MfR1rOPcmFr5ekraYhOSRul9gqempir69hlqrFKUFqt46F9/wLWyaJ7vlsMU6o4gBo6/jE5tICIpzNw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" "@popperjs/core" "^2.11.8" -"@abp/select2@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.3.0.tgz#199777173ca3e43a5448f8b51b30c3aafe4a3333" - integrity sha512-6sEIYIakVI1DM3eOqOaxwdG8vkyVFBWvVZhoChPZkgIIQ0EH2JLerHv0pePJEl6mQSxOUrikPA8++B2y+sBnQA== +"@abp/select2@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.4.0.tgz#e744988abf81e2194a5850acc4ac1333e9578df8" + integrity sha512-tyAdGzX32joI5vw+MUJGR0iToWCGZrbQg/zKeoCib80Zx5cn+e6GXj6wZyPbsJgVm0jZ7zBweWt3x8HzW9N8Ww== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" select2 "^4.0.13" -"@abp/sweetalert2@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.3.0.tgz#515e0477574b54bc6650b9f5ee193bfc648c715a" - integrity sha512-bY5MXEiKja+vk71ujSn8rpzRC45Ge8zvcca1k1vBJs+oKyRCii//JBxa3H0lagZo5r/QwIqv5ihdyLE1xsuLSg== +"@abp/sweetalert2@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.4.0.tgz#65cb26c8c7c9c02158c9a487969eef033fe04a73" + integrity sha512-kyVMlfnI6MZojJlaRstdHj/CL3olOn4BszjQ155UQaDsGHUu0THmbhpfi4P0q3TCLLzcLM4pkuY3wJHPNl3ZOw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" sweetalert2 "^11.23.0" -"@abp/timeago@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.3.0.tgz#3214a6d000c79504c9fa7cb4e004a967fe46459b" - integrity sha512-Rgcex+jvM9Z9/E0I5Q3wlC2bY5550/+6xRX407k5ho4gxCccYo3c0Gkpiy5pJRFhTwtBgcNzYxArh6NCez+tDg== +"@abp/timeago@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.4.0.tgz#100fc0598e3a605346397f8a395bb96dc70f429d" + integrity sha512-G0ppO7Evai3DdhSg4ozxQpvIke0t3vyg63pNdfOtAPngmuam7WBM1WemBi4u29M84WcSfmk4KuAubXO0aCJgDg== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" timeago "^1.6.7" -"@abp/utils@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.3.0.tgz#d88c0d639d69d6210c3070892aac61d5e945c124" - integrity sha512-iNehxiqWJQl2wwE9EbsQANPv/ITMh0yUcPATUlG/McoLWdpPcqobx75NxXcHDqW8ZarecqSktjJDZPvaUaD+jA== +"@abp/utils@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.4.0.tgz#75175f798e58643c0dec1e85a502186c811e9226" + integrity sha512-BPc7KGDcbdFZ4GonAt3SOi/gYhG+iXI76TMlAzSKMBRhBU0vxFS8I/pb7U5zX0Gjh5M+aT0l2vA3l+POCHPBfw== dependencies: just-compare "^2.3.0" @@ -268,10 +268,10 @@ just-compare@^2.3.0: resolved "https://registry.yarnpkg.com/just-compare/-/just-compare-2.3.0.tgz#a2adcc1d1940536263275f5a1ef1298bcacfeda7" integrity sha512-6shoR7HDT+fzfL3gBahx1jZG3hWLrhPAf+l7nCwahDdT9XDtosB9kIF0ZrzUp5QY8dJWfQVr5rnsPqsbvflDzg== -lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lodash@^4.18.1: + version "4.18.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" + integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== luxon@^3.7.2: version "3.7.2" diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/package.json b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/package.json index 8b258a31f0..ba4f6cad1d 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/package.json +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/package.json @@ -3,7 +3,7 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.3.0", - "@abp/cms-kit": "10.3.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.4.0", + "@abp/cms-kit": "10.4.0" } } diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/yarn.lock b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/yarn.lock index fc0d3f8801..22a1070b60 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/yarn.lock +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/yarn.lock @@ -2,294 +2,294 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.3.0.tgz#bf48e8fa571067d15bc19bdd3f13509acf58f9c7" - integrity sha512-t9pHqrXaCDYcsdrz0VPKT28ujnnU+EVxyVDogQSRRpKsDoLhNcOD8NI3swdAWBQZ46YSCDfeDR+gfwjC3yqpXQ== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~10.3.0" - -"@abp/aspnetcore.mvc.ui.theme.shared@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.3.0.tgz#bcf72b5dd1dea285c3fa7cb1b6ce27c29b985ff7" - integrity sha512-X5xmMSxnIzNJPexvKR9y6jyn0v5n2wscN1SECG/bMI1YdKugMlkfkSuyUmLn+RX9/YdcK0jOsdMhfuzs5mgAOA== - dependencies: - "@abp/aspnetcore.mvc.ui" "~10.3.0" - "@abp/bootstrap" "~10.3.0" - "@abp/bootstrap-datepicker" "~10.3.0" - "@abp/bootstrap-daterangepicker" "~10.3.0" - "@abp/datatables.net-bs5" "~10.3.0" - "@abp/font-awesome" "~10.3.0" - "@abp/jquery-validation-unobtrusive" "~10.3.0" - "@abp/lodash" "~10.3.0" - "@abp/luxon" "~10.3.0" - "@abp/malihu-custom-scrollbar-plugin" "~10.3.0" - "@abp/moment" "~10.3.0" - "@abp/select2" "~10.3.0" - "@abp/sweetalert2" "~10.3.0" - "@abp/timeago" "~10.3.0" - -"@abp/aspnetcore.mvc.ui@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.3.0.tgz#0d125efd172b6772c516eae82cfe35f144da73fc" - integrity sha512-kp0XdwrlNz+NeLdxSJ6xdUaB5PM7xdbiLsIyTUcwa7AUKBtcVU6yq6qttbk9VyO0KqDgLywSuEL+9PkFhthuPg== +"@abp/aspnetcore.mvc.ui.theme.basic@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.4.0.tgz#c0414583f1c1fca14b0133818a63434b4fbf7df0" + integrity sha512-sOg3URmFRO/18D734B56jdFuC4LflGXXpw4w1Rdd77BJxugynxcmwNOQbLYFvMfqj0eKqDOH3E4yEcmS2IT1BQ== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~10.4.0" + +"@abp/aspnetcore.mvc.ui.theme.shared@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.4.0.tgz#49002e55db00c98931d27357f7ea40eeab09e2e8" + integrity sha512-bNQ+WOnsfQhb98Z9YO0zZsW2K8ZEgOQ+7/86QaqoYTf+ZFvLJsICmNuHryNkCGRM1XJf9EaDZ3qSOwoOGQwS9A== + dependencies: + "@abp/aspnetcore.mvc.ui" "~10.4.0" + "@abp/bootstrap" "~10.4.0" + "@abp/bootstrap-datepicker" "~10.4.0" + "@abp/bootstrap-daterangepicker" "~10.4.0" + "@abp/datatables.net-bs5" "~10.4.0" + "@abp/font-awesome" "~10.4.0" + "@abp/jquery-validation-unobtrusive" "~10.4.0" + "@abp/lodash" "~10.4.0" + "@abp/luxon" "~10.4.0" + "@abp/malihu-custom-scrollbar-plugin" "~10.4.0" + "@abp/moment" "~10.4.0" + "@abp/select2" "~10.4.0" + "@abp/sweetalert2" "~10.4.0" + "@abp/timeago" "~10.4.0" + +"@abp/aspnetcore.mvc.ui@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.4.0.tgz#24e40fdc101c411f29a87adb77baa642299e979c" + integrity sha512-0qVFthS4f4fkCv//UY2GseO+NX+XYqlamZJJwOTmv1UXFW6LxAXxuEQqWYVfFKzwk2lbkXDUcgP8NhhQ9xp3Sw== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.3.0.tgz#032aaf0e474f971b9f90ca7fb83ff7ca4d7be933" - integrity sha512-7mRuRRIE4R0yw1cZuoIskgrqZPUa5/SJzse0K6u+/QDqvyLjjOsyE51WuqAXxlVfKpLp/fBvXZwnCDeCRU9iZQ== +"@abp/bootstrap-datepicker@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.4.0.tgz#a392fd5ce31c86e9fc07f7dedbd0c56a02af341e" + integrity sha512-kOpuCEO64zoJ7a9JASsCLFZIjEYgN0GG/oApS2L729pv6/oEhcupWpwrnvDUhsdGOezXwLIsgTzc6tiG3HxwGA== dependencies: bootstrap-datepicker "^1.10.1" -"@abp/bootstrap-daterangepicker@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.3.0.tgz#b2a9171595ee311c32f57460013870cde67c6b7f" - integrity sha512-ejLu3sWhfNDlsXeUXAQudZvPv0xM4McxIK65vuVEZiKIwFSnrLYil8jBi12EJJmAw5yYA/KC0EdyG82E+AWpmA== +"@abp/bootstrap-daterangepicker@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.4.0.tgz#95f68d3f488a12ede3c12352637b48782eeb031a" + integrity sha512-HX4HutkbPDQWBJNPDozpT1Um9mHcfV+RL+ukwEy0N4e7nD8VJyqLT/a+/FzQN7iUkwV+aNR2v5c0W+dgyqTaEA== dependencies: - "@abp/moment" "~10.3.0" + "@abp/moment" "~10.4.0" bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.3.0.tgz#23535c90613271b02fdad3eae1556b67844729e6" - integrity sha512-JnVOeJUyR78oo+QURvaNqa9xGZaVux0PRIpFpd4Qsqiiz9FvJrbCuSoBkkZoj/0eH23WoZ3bJxy0K9LDE19qvg== +"@abp/bootstrap@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.4.0.tgz#64b22bbdbf6b5bda279b21a7bae0652290b1221f" + integrity sha512-WQzXi5h1mej9twrAB6kD4aJtTNfSxpnuo1pxIOu/8GKGWoDayVY2NMQl+MCVel6dST+6EUTmuDRxc2q9qHRxLg== dependencies: - "@abp/core" "~10.3.0" - "@abp/popper.js" "~10.3.0" + "@abp/core" "~10.4.0" + "@abp/popper.js" "~10.4.0" bootstrap "^5.3.8" -"@abp/clipboard@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-10.3.0.tgz#7bfbfcae6a135065ee13afd1b7d3abc92176a203" - integrity sha512-Cah+jOzcG1KZh6PXWxo+BedU7gsqTuNlTDsCzBCk2NuV2/Blb5qumAwEP4GBKAEIY/XwcFQP/ubxuXMTNjr0jw== +"@abp/clipboard@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-10.4.0.tgz#bac5854af4f8bcaa987d39f234edd58b675a71c8" + integrity sha512-KTTzCDEe+wjpGLdud8MHlwnPr/NdzFDmfHtENgESdIa5KilGOwy2wv24lQ5cyJOB4ftoJzkBaHinKJxg/KU0pw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" clipboard "^2.0.11" -"@abp/cms-kit.admin@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/cms-kit.admin/-/cms-kit.admin-10.3.0.tgz#3ac7c8e37f7de1345fbb7634ae0d797d28b6751b" - integrity sha512-zvFpdfvBJHg+XC7xM/h9YBe8gPgRrb6AJx+S9eT1+MNgyE+Lm3ghxAa6SBIg5MgPxrVpLO7N8HHrzu5Kx2ksUw== +"@abp/cms-kit.admin@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/cms-kit.admin/-/cms-kit.admin-10.4.0.tgz#f3d2272d8ab6adb60b7b23807b1d48a174411377" + integrity sha512-klxpGOF4aiJevsZT+7zM/TpWfoEAXlBBWSrFWNtxMNMmYisOFEX2AWCDVl2lQvoEiwTFNR+D9zhuKYFdWKQXFQ== dependencies: - "@abp/codemirror" "~10.3.0" - "@abp/jstree" "~10.3.0" - "@abp/markdown-it" "~10.3.0" - "@abp/slugify" "~10.3.0" - "@abp/tui-editor" "~10.3.0" - "@abp/uppy" "~10.3.0" + "@abp/codemirror" "~10.4.0" + "@abp/jstree" "~10.4.0" + "@abp/markdown-it" "~10.4.0" + "@abp/slugify" "~10.4.0" + "@abp/tui-editor" "~10.4.0" + "@abp/uppy" "~10.4.0" -"@abp/cms-kit.public@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/cms-kit.public/-/cms-kit.public-10.3.0.tgz#2331751380b3f224d28481c4472f31fbfc1e8e1a" - integrity sha512-t7xHhYZqEneV4BwMk7StfQXB1xy0ohoCBDduYwRPGkAvgvWifFNwZ9ZuFwBwItPPeg7llzYPLxLyZg265tNDeQ== +"@abp/cms-kit.public@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/cms-kit.public/-/cms-kit.public-10.4.0.tgz#a7f0c5b45f7cd4c6189311f4f293fa4ee09d4e2c" + integrity sha512-LbQC0Q05fCuTa9ZXIcNYGp3b9vbdlFif/tL8LReNWVQQ2FgzjdtOoCajz9v4lfbqbjsQeH+fMir+gCQDQggYOQ== dependencies: - "@abp/highlight.js" "~10.3.0" - "@abp/star-rating-svg" "~10.3.0" + "@abp/highlight.js" "~10.4.0" + "@abp/star-rating-svg" "~10.4.0" -"@abp/cms-kit@10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/cms-kit/-/cms-kit-10.3.0.tgz#a67d33eda79a55d6f327d41e320a3bf88360a3ab" - integrity sha512-/nMSj95DkxnI6SG11I0ltdDMaMKNKQ6Fp5/aDNNbFyBXbbOpPzKulhHJIighynXk/B9N01mztL1B5MSL5ViSQg== +"@abp/cms-kit@10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/cms-kit/-/cms-kit-10.4.0.tgz#03773131bf5e74d410f4657d68179328059a04c4" + integrity sha512-7J93H3pWbsD0fAoEJP+ELkYMlcj+bC442ypB5sLrPDOBZ2cLB4Nji4m9D/17KMIb87vu7gNQuUnk+OhjpBrmFg== dependencies: - "@abp/cms-kit.admin" "~10.3.0" - "@abp/cms-kit.public" "~10.3.0" + "@abp/cms-kit.admin" "~10.4.0" + "@abp/cms-kit.public" "~10.4.0" -"@abp/codemirror@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/codemirror/-/codemirror-10.3.0.tgz#57345c0d68850a4b1d0c62faf2c8cde221bada17" - integrity sha512-vo53MxaQE3l/la5kTXQLh0K4wgCPmfvAO0g552xEKzGEpAtD9Cl3EvmYmdJGxXlfFnAz4ic6/YZ6SJ+LCprFvw== +"@abp/codemirror@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/codemirror/-/codemirror-10.4.0.tgz#1e5f9a37dbaaa6484f67ce57a187263b8b95b860" + integrity sha512-6C0AOVon9S89b+8SuBy3fuKq/CQ4rvgpJk+8o90veGm4s0BDZkyGbee/vaJItX7c3ZzAYpWdcO22ybUuJVHpJA== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" codemirror "^5.65.1" -"@abp/core@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.3.0.tgz#9960f8179d1fdc42250d0bbba60e379590c8199c" - integrity sha512-DpL4qrsyCUolypUwxKVDDK8bPVhNbYKx6l2ytUjoOu73CwgPoCSGkvHWrmKIsEsmTfOC9J7+GDErh8jWR/5pVA== +"@abp/core@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.4.0.tgz#de0c09e26d1f20451da9430d939beebb5bb6e9dd" + integrity sha512-3yKLFqUtFo5rgBPQxWkNPsdq1bE9VKwm02yB14lu913EIYhP//akizY4uSK08BozocJNveCASQbgd66z4x6v/Q== dependencies: - "@abp/utils" "~10.3.0" + "@abp/utils" "~10.4.0" -"@abp/datatables.net-bs5@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.3.0.tgz#d8711c52f7c03df51d14d1a5f0e6b00f6d9551fc" - integrity sha512-I8rEU98kEhsNZjVdpPvOcG6F4xr1CgIRb0bTf5bfd8ufzxEUKB+xF9bguJIB5kYwjme8QbMlcJ0V/c+N4iewtQ== +"@abp/datatables.net-bs5@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.4.0.tgz#a24eef99399324e4b89a00a204d2c4c2de379da1" + integrity sha512-T4f7e6sR1qHcvygNUNfdPWsK61C3NQORV35Lzpl98eDwY8XLg2/RLAegpFI8GjESVy2+2PCKVqUoSYZtXaMFmw== dependencies: - "@abp/datatables.net" "~10.3.0" + "@abp/datatables.net" "~10.4.0" datatables.net-bs5 "^2.3.4" -"@abp/datatables.net@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.3.0.tgz#f98319d8e226da5aced4d1d94d3ababb11327074" - integrity sha512-imM25WmO1V0hqeG5iPeLvYMID2HX7SG9drz47qilr74MhpY2i2J7HlsvZDonNkjpxzhK4DHpGeQd6mE2QzG7Hw== +"@abp/datatables.net@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.4.0.tgz#ab48f6e3ccae8d0644f0c01011ff98cc6ca16181" + integrity sha512-esIUozaSEa29G7bdWbEZr22fRChaTuGfNNB8rgBHmVKwgkbHSG7qUTgsfGhNz0T2vfDsK3X37n55xmSdE9RElg== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" datatables.net "^2.3.4" -"@abp/font-awesome@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.3.0.tgz#43602605f5633132cc6fe21b6ce5ab19b86bdf27" - integrity sha512-RUOVHxDyT81hp01DhUuSshKvLM+NiTt9VP+O4/rSj+SOaSJAm3wX7eziCKrDi6H41FwhoZL75CifLhcZVyVO4A== +"@abp/font-awesome@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.4.0.tgz#327979191615fff571f0ea2e99d01bbcca5378ce" + integrity sha512-RBzDSi/hrjhLKc7h6Kf9CQqC+LCGvS7h8HrRVaIzWCZSeqTfIMFpixjCUrNgkqWI0bU4NDUKXZ4XCOv4nPVlBg== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" "@fortawesome/fontawesome-free" "^7.0.1" -"@abp/highlight.js@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/highlight.js/-/highlight.js-10.3.0.tgz#1b04355e84eba15c395cdfc7055c8d610296206b" - integrity sha512-cqApQta9gEs03jk/s+yQsm1mnHGNDZPHnkGYgxYKI41Nl9meV6+5Dkz0b4gnQoUKXvFlmuOuf2DOjot7a2yB7g== +"@abp/highlight.js@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/highlight.js/-/highlight.js-10.4.0.tgz#b1b53329743f3eeb203ec35ebcf9c6e79c9fbe93" + integrity sha512-L0mL/AErFsYdJbCLUu22lm/fn+Eelc0H57OQrUAzVy+22XXALiFtyq/cci3sjOcxfpf2CW/v+z0JKCouuJd7Ag== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" "@highlightjs/cdn-assets" "~11.11.1" -"@abp/jquery-validation-unobtrusive@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.3.0.tgz#5bc14ed642d48f9eae54761ad39bd9f51beeb005" - integrity sha512-j/9fA7tAs1/t3P2U1kKUiDlHAImmseul6KzvV77sNpnms7g4+x6Fgsa+sMzicSRGOLz7lK5RHdBFyvfKxt7/nA== +"@abp/jquery-validation-unobtrusive@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.4.0.tgz#e0114347344931667f48e0f2de5ea4bd6f9d2a88" + integrity sha512-AJncXVB+eTSizq0fTAwLii9a0GeYFPiY+gw+Ki5pak8Pp9LQivphsW32TqQmPC3qZVuYtd4myUUraWuOTT1Adg== dependencies: - "@abp/jquery-validation" "~10.3.0" + "@abp/jquery-validation" "~10.4.0" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.3.0.tgz#e669a54d28f6c0b863a284322e527a5c65cb7dbf" - integrity sha512-Wo95ZJLS1ScKm7dm3zFlEFUKqXrqo3m9+82DX5mFJUVCHHuBPrhxvDjxNJ18z4gpoOUu08uqxX7LFbphtkRM3g== +"@abp/jquery-validation@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.4.0.tgz#bd1ef0217c2500363f25223dd7316ba919a505b5" + integrity sha512-W9QiGnOpaq6uDDwBy4UEqWrP1p2UegHQShW5Cev9vr6g7Lk97UsS1Agh9lnjbvMzFSAiLl3M9gHmyMD4aU3NPA== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" jquery-validation "^1.21.0" -"@abp/jquery@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.3.0.tgz#3b60bcdfe8f9a33be79db1bea2500837fe09c67d" - integrity sha512-TndX/8bJx5vGAAwIykx2CJUZIFFDkwpD+cUjE45WO2dSrY2M5vNfWiuw95OcLGngO8RaT/gTkFGzPuZqKn9ggg== +"@abp/jquery@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.4.0.tgz#7ed3f1808682717ccfa7c6ac6e8db8a8b648b1e1" + integrity sha512-kjFoVIhqlaIHlEs917sIeP1sHmLWXMn+DVKaCkVQUXC2KVq+yvQRC8AZiFQVxOwLvzd9dFFpGwGZgpWkDfvhvg== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" jquery "~3.7.1" -"@abp/jstree@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jstree/-/jstree-10.3.0.tgz#ad6d3a7fe6ec2d0a55ad1fa028a61ec0e7e1157d" - integrity sha512-MR7MIbjDSHq/MUk8FDjIKrram2aWYuuC9OKZLCZ8KH2rsfx9Y0wjYTKcoouwMldLQ/71O7/pY/RRVFCxp5ROvQ== +"@abp/jstree@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jstree/-/jstree-10.4.0.tgz#99f49383dd016bfa66e0512603a4a02b3534b36a" + integrity sha512-GXcK+JZGLBwGWs3GiFK/sLQt4VJIlweXR6MymE9YvG2X9i9Q8GrhfivINhbWAttSvbBTGca0RiKG2SxiZMAyhg== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" jstree "^3.3.17" -"@abp/lodash@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.3.0.tgz#bd091fc811d85d2f535802631af6165ac1c8a198" - integrity sha512-7JDU9UlbD+9odhFQjolvyJZ6EQKG5kiL2Pt9T1RMVchNh47iEuZwfi8c+CIxa3YxA3n6lAsZ+8Pmxq4yOku+RQ== +"@abp/lodash@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.4.0.tgz#b6fc25b6e2fd74420ca16a5a13cb8fcd39b89efd" + integrity sha512-RuiIGAqJtSBS+4VAk4cg7Zsfj07B3xkiewCaK5j0xUcyjsCJKL1UdXe2zVNL3oE5bXTNMU7yNQWJS4knv2mfwA== dependencies: - "@abp/core" "~10.3.0" - lodash "^4.17.21" + "@abp/core" "~10.4.0" + lodash "^4.18.1" -"@abp/luxon@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.3.0.tgz#acbd92289456a546607ed4506dc36f71e6da53c9" - integrity sha512-IIi2+odQzuFK4qV1KJ2HtlTewSPby2F9CJFNNtNRMzX/P30dQkAGWpNV5/FZzJwFIihR+zSei5wlu11WpPugsw== +"@abp/luxon@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.4.0.tgz#731d20877ec679fa518b7199128676631d030a90" + integrity sha512-/hF+FBugJMkp/H9WNZxlsOy3Rl4DTeR2hqmRPI0sPcdFOixCziYrwzyHg/2Kx8cI/zHa6GRRt1B4Jz4yjQTvdw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" luxon "^3.7.2" -"@abp/malihu-custom-scrollbar-plugin@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.3.0.tgz#209931d02a994ef448371d6203e9ec74c72b61e6" - integrity sha512-hJjwEExP3hKE1MNDG+pcvoHRFUihh3t/0wCPNaoePFd8+QnJuyQ9U0VA+vdtykTxm8uFNE2uaCgyHffoHmzeZA== +"@abp/malihu-custom-scrollbar-plugin@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.4.0.tgz#e1c207d9c66cce64fcb4fe13c091c4b86b00b6d7" + integrity sha512-LC+imsGQganhKWybvQytATjQMXfQwfCfi6RRWREP+pejKPuHpqHzPd+AjKwZpx500K2/ZTwT62FKJ1uDiJXSKA== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/markdown-it@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/markdown-it/-/markdown-it-10.3.0.tgz#fbc5fff1e9dd1c31c8b96ebd6b481326809ddff0" - integrity sha512-IN01vs2axD+zrygwF5IyYmmFV+GqV7O4NK02ThqX97q0AZ5ZwqOccwcY/kkBkp+qAWoTMcPb6eOQZrTG631qUg== +"@abp/markdown-it@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/markdown-it/-/markdown-it-10.4.0.tgz#bb0bf97f13462454fc84782a09b50f637a49f200" + integrity sha512-Tb1hiZiq024WeveNJBS49HL6W+uNAGEn3NHV335wSlN9e+x4LeyYd79wj+PAfRaX9yWCXjupqrafpDZ6Nj0C7g== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" markdown-it "^14.1.0" -"@abp/moment@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.3.0.tgz#5a5c4ece14d1bc4dd33134f85d1d085733d3c975" - integrity sha512-8DKw2jgC/28nLrB/CR9BiyUSt0pUmWF+bEgAsduln6VrSCHn0aRLqgs+wi/9Ol+1bl7pO2wbJOYg1qbnkDhrAQ== +"@abp/moment@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.4.0.tgz#6737898b90b579ce5e0d683657757725a4def09d" + integrity sha512-WUtzjD6+1JGpgS6Yv2Qo/h4jM77LkkasPrywsetmtj/6zyVQWrkk7tKHTCjhAJtdinctcROwIIcwLsELi0nDHg== dependencies: moment "^2.30.1" -"@abp/popper.js@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-10.3.0.tgz#cb2415e45a905854980f4e864c00c8fe1156a6f9" - integrity sha512-Yfafs50t7DKnShxAl9WPpihZxG5cLAt99rJmIyXBYUQIpkHkxDyUIA5b4gCoVCqKgjFDOxG7i6L+Ovd/2N8k7g== +"@abp/popper.js@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-10.4.0.tgz#9d8d09c4e18020b6a75ba30daf6786606f555e84" + integrity sha512-KR5wGZ/MfR1rOPcmFr5ekraYhOSRul9gqempir69hlqrFKUFqt46F9/wLWyaJ7vlsMU6o4gBo6/jE5tICIpzNw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" "@popperjs/core" "^2.11.8" -"@abp/prismjs@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-10.3.0.tgz#4ea33ece50cefcfc8a89c17b0d265b484b7205ee" - integrity sha512-WvkLyg+izkcX6sEOYy41B9lebiniYxMQZ++c+ysyOQKZzrYEEFOOLlnFS9jPqSKLv9cNRizhG1CCfiwoYCxufQ== +"@abp/prismjs@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-10.4.0.tgz#0061d71d18252bc1e66e763aab6d81a7b4fe51b7" + integrity sha512-kjV11XNt2uP01zlhrrScr4WA27lgZ03Cz3svA7/HHnfhFy76MHYo+Dyn7sS4Gp3UobMcf1QWHl4KQNCUnYNTOQ== dependencies: - "@abp/clipboard" "~10.3.0" - "@abp/core" "~10.3.0" + "@abp/clipboard" "~10.4.0" + "@abp/core" "~10.4.0" prismjs "^1.30.0" -"@abp/select2@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.3.0.tgz#199777173ca3e43a5448f8b51b30c3aafe4a3333" - integrity sha512-6sEIYIakVI1DM3eOqOaxwdG8vkyVFBWvVZhoChPZkgIIQ0EH2JLerHv0pePJEl6mQSxOUrikPA8++B2y+sBnQA== +"@abp/select2@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.4.0.tgz#e744988abf81e2194a5850acc4ac1333e9578df8" + integrity sha512-tyAdGzX32joI5vw+MUJGR0iToWCGZrbQg/zKeoCib80Zx5cn+e6GXj6wZyPbsJgVm0jZ7zBweWt3x8HzW9N8Ww== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" select2 "^4.0.13" -"@abp/slugify@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/slugify/-/slugify-10.3.0.tgz#629f96a44b07d77439d9056531b43677d496caf3" - integrity sha512-+0WQAjKXLflXOobVkA41xAsvXVuMKZeoNSxGn97VDRLwmAPwb+moHV96fWF0iwju0kUZXNvbWFqbZfV07LitfQ== +"@abp/slugify@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/slugify/-/slugify-10.4.0.tgz#eb70ee28499f4406b738478cf4a26319ea4b9097" + integrity sha512-bJXo86hWHQiF9QlYHu869AcDV1sy1p7nlqWxjA3kMkZ5OvP+/7qkwuGfRzkhVhQ9MK6hcMOKwasZ6xw9UPERFQ== dependencies: slugify "^1.6.6" -"@abp/star-rating-svg@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/star-rating-svg/-/star-rating-svg-10.3.0.tgz#195d8175df2c82384752439d0677e27480c3f49d" - integrity sha512-7xXLgz2jxdkBceelG+uvnD9aKwwTV2FIDVCmpRF3AF+0c7jaJvtyM9vlx6xdTFQSSzDnSGG10k/jeYP7O2AjUg== +"@abp/star-rating-svg@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/star-rating-svg/-/star-rating-svg-10.4.0.tgz#9aa9226494be5d0d4cd99237d6bd75cedf41a0c6" + integrity sha512-cvu+9ARD9dq7qWHsmb+7Ml9ITyzuUzDHBvBxt5xE4WV0e3jszJRhOJ4TROlh15Z8ymZtStMVb2m1F2JGx1i3xA== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" star-rating-svg "^3.5.0" -"@abp/sweetalert2@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.3.0.tgz#515e0477574b54bc6650b9f5ee193bfc648c715a" - integrity sha512-bY5MXEiKja+vk71ujSn8rpzRC45Ge8zvcca1k1vBJs+oKyRCii//JBxa3H0lagZo5r/QwIqv5ihdyLE1xsuLSg== +"@abp/sweetalert2@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.4.0.tgz#65cb26c8c7c9c02158c9a487969eef033fe04a73" + integrity sha512-kyVMlfnI6MZojJlaRstdHj/CL3olOn4BszjQ155UQaDsGHUu0THmbhpfi4P0q3TCLLzcLM4pkuY3wJHPNl3ZOw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" sweetalert2 "^11.23.0" -"@abp/timeago@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.3.0.tgz#3214a6d000c79504c9fa7cb4e004a967fe46459b" - integrity sha512-Rgcex+jvM9Z9/E0I5Q3wlC2bY5550/+6xRX407k5ho4gxCccYo3c0Gkpiy5pJRFhTwtBgcNzYxArh6NCez+tDg== +"@abp/timeago@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.4.0.tgz#100fc0598e3a605346397f8a395bb96dc70f429d" + integrity sha512-G0ppO7Evai3DdhSg4ozxQpvIke0t3vyg63pNdfOtAPngmuam7WBM1WemBi4u29M84WcSfmk4KuAubXO0aCJgDg== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" timeago "^1.6.7" -"@abp/tui-editor@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/tui-editor/-/tui-editor-10.3.0.tgz#4fe3879111fe5ee312048841fca1cac43bb9bb62" - integrity sha512-6zGfaYSggalgpS9tKjESXT6w00KK7fKN71rG/hxRLNRpLGn5aapHdlAZRNEUgEFYo9czFdMW8POHpVA+sjcDMw== +"@abp/tui-editor@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/tui-editor/-/tui-editor-10.4.0.tgz#8041b62eb464f4a8adddc043904e2bc8475d45e3" + integrity sha512-SbbAs3V94UaF6xaMIaRNpDE9KSvOU45XZl24jCWjCAwkyTkN0bHlpnqs0UuJUtCnnQAzj4C+AvVy5ykrDKGtIg== dependencies: - "@abp/jquery" "~10.3.0" - "@abp/prismjs" "~10.3.0" + "@abp/jquery" "~10.4.0" + "@abp/prismjs" "~10.4.0" -"@abp/uppy@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/uppy/-/uppy-10.3.0.tgz#3b333f4f9618cc24dba989c6e2a9cb8c9bad0b2b" - integrity sha512-5RGZv2pk6xIFOGbIQdMvLBtpencqYICCtL0wuSP58zU97FQuQdbV3Du/2ZZvqCyxinJG+SwUBog4t9PAI1qM8w== +"@abp/uppy@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/uppy/-/uppy-10.4.0.tgz#876650cda8f5187cf9e4a0f4731632fed9be1356" + integrity sha512-E+86xlPFZemx56K6nK1gbDC1pcYHMEWw03QOfZwtwP5UGU5JvyyW6CpV3M8PdfBRo4sx+lRqOW2I/X8MIsbTig== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" uppy "^5.1.2" -"@abp/utils@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.3.0.tgz#d88c0d639d69d6210c3070892aac61d5e945c124" - integrity sha512-iNehxiqWJQl2wwE9EbsQANPv/ITMh0yUcPATUlG/McoLWdpPcqobx75NxXcHDqW8ZarecqSktjJDZPvaUaD+jA== +"@abp/utils@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.4.0.tgz#75175f798e58643c0dec1e85a502186c811e9226" + integrity sha512-BPc7KGDcbdFZ4GonAt3SOi/gYhG+iXI76TMlAzSKMBRhBU0vxFS8I/pb7U5zX0Gjh5M+aT0l2vA3l+POCHPBfw== dependencies: just-compare "^2.3.0" @@ -2275,6 +2275,11 @@ lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lodash@^4.18.1: + version "4.18.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" + integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== + lowercase-keys@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2" diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/ClientProxies/cms-kit-admin-generate-proxy.json b/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/ClientProxies/cms-kit-admin-generate-proxy.json index dc870f6878..8b8d9a19a5 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/ClientProxies/cms-kit-admin-generate-proxy.json +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi.Client/ClientProxies/cms-kit-admin-generate-proxy.json @@ -406,7 +406,7 @@ "uniqueName": "MoveAllBlogPostsAsyncByBlogIdAndAssignToBlogId", "name": "MoveAllBlogPostsAsync", "httpMethod": "PUT", - "url": "api/cms-kit-admin/blogs/{id}/move-all-blog-posts", + "url": "api/cms-kit-admin/blogs/{blogId}/move-all-blog-posts", "supportedVersions": [], "parametersOnMethod": [ { @@ -436,7 +436,7 @@ "isOptional": false, "defaultValue": null, "constraintTypes": null, - "bindingSourceId": "ModelBinding", + "bindingSourceId": "Path", "descriptorName": "" }, { @@ -450,18 +450,6 @@ "constraintTypes": null, "bindingSourceId": "Query", "descriptorName": "" - }, - { - "nameOnMethod": "id", - "name": "id", - "jsonName": null, - "type": null, - "typeSimple": null, - "isOptional": false, - "defaultValue": null, - "constraintTypes": [], - "bindingSourceId": "Path", - "descriptorName": "" } ], "returnValue": { diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Blogs/BlogAdminController.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Blogs/BlogAdminController.cs index f90215ed38..9d56b60231 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Blogs/BlogAdminController.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.HttpApi/Volo/CmsKit/Admin/Blogs/BlogAdminController.cs @@ -71,7 +71,7 @@ public class BlogAdminController : CmsKitAdminController, IBlogAdminAppService } [HttpPut] - [Route("{id}/move-all-blog-posts")] + [Route("{blogId}/move-all-blog-posts")] [Authorize(CmsKitAdminPermissions.Blogs.Delete)] public Task MoveAllBlogPostsAsync(Guid blogId, [FromQuery]Guid? assignToBlogId) { diff --git a/modules/docs/.abpstudio/state.json b/modules/docs/.abpstudio/state.json deleted file mode 100644 index fefde3c294..0000000000 --- a/modules/docs/.abpstudio/state.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "selectedKubernetesProfile": null, - "solutionRunner": { - "selectedProfile": "Default", - "targetFrameworks": [], - "applicationsStartingWithoutBuild": [], - "applicationBatchStartStates": [ - { - "profile": "Default", - "applicationOrFolder": "VoloDocs.Web", - "value": 0 - } - ], - "folderBatchStartStates": [] - } -} \ No newline at end of file diff --git a/modules/docs/app/VoloDocs.Web/package.json b/modules/docs/app/VoloDocs.Web/package.json index 0cbb4fa917..eb3423aca9 100644 --- a/modules/docs/app/VoloDocs.Web/package.json +++ b/modules/docs/app/VoloDocs.Web/package.json @@ -3,7 +3,7 @@ "name": "volo.docstestapp", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.3.0", - "@abp/docs": "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.4.0", + "@abp/docs": "~10.4.0" } } diff --git a/modules/docs/app/VoloDocs.Web/yarn.lock b/modules/docs/app/VoloDocs.Web/yarn.lock index 55561a2a30..2c8b945a40 100644 --- a/modules/docs/app/VoloDocs.Web/yarn.lock +++ b/modules/docs/app/VoloDocs.Web/yarn.lock @@ -2,222 +2,222 @@ # yarn lockfile v1 -"@abp/anchor-js@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/anchor-js/-/anchor-js-10.3.0.tgz#3c1c68fc41d770ac90ebf45d9aaa77b964dfb722" - integrity sha512-O4l8Kse3JOoSXKMyfzbQvErCrs3McCFevzYGakUb1cI/wgDfcMITO88oytSZl9R352haaKCpa7bC7EXHvMNgfw== +"@abp/anchor-js@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/anchor-js/-/anchor-js-10.4.0.tgz#32a6e61e7d2ca6cd5b5b1fc53a684bd52d1fc710" + integrity sha512-/ZmG6hbKFRY1w18jNdhfEX9614d/OAH++BTOGuQ/1ZQUZqxu+FTLLlDMczce3UmL4d5devYocu5f/x2JnKROZw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" anchor-js "^5.0.0" -"@abp/aspnetcore.mvc.ui.theme.basic@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.3.0.tgz#bf48e8fa571067d15bc19bdd3f13509acf58f9c7" - integrity sha512-t9pHqrXaCDYcsdrz0VPKT28ujnnU+EVxyVDogQSRRpKsDoLhNcOD8NI3swdAWBQZ46YSCDfeDR+gfwjC3yqpXQ== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~10.3.0" - -"@abp/aspnetcore.mvc.ui.theme.shared@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.3.0.tgz#bcf72b5dd1dea285c3fa7cb1b6ce27c29b985ff7" - integrity sha512-X5xmMSxnIzNJPexvKR9y6jyn0v5n2wscN1SECG/bMI1YdKugMlkfkSuyUmLn+RX9/YdcK0jOsdMhfuzs5mgAOA== - dependencies: - "@abp/aspnetcore.mvc.ui" "~10.3.0" - "@abp/bootstrap" "~10.3.0" - "@abp/bootstrap-datepicker" "~10.3.0" - "@abp/bootstrap-daterangepicker" "~10.3.0" - "@abp/datatables.net-bs5" "~10.3.0" - "@abp/font-awesome" "~10.3.0" - "@abp/jquery-validation-unobtrusive" "~10.3.0" - "@abp/lodash" "~10.3.0" - "@abp/luxon" "~10.3.0" - "@abp/malihu-custom-scrollbar-plugin" "~10.3.0" - "@abp/moment" "~10.3.0" - "@abp/select2" "~10.3.0" - "@abp/sweetalert2" "~10.3.0" - "@abp/timeago" "~10.3.0" - -"@abp/aspnetcore.mvc.ui@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.3.0.tgz#0d125efd172b6772c516eae82cfe35f144da73fc" - integrity sha512-kp0XdwrlNz+NeLdxSJ6xdUaB5PM7xdbiLsIyTUcwa7AUKBtcVU6yq6qttbk9VyO0KqDgLywSuEL+9PkFhthuPg== +"@abp/aspnetcore.mvc.ui.theme.basic@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.4.0.tgz#c0414583f1c1fca14b0133818a63434b4fbf7df0" + integrity sha512-sOg3URmFRO/18D734B56jdFuC4LflGXXpw4w1Rdd77BJxugynxcmwNOQbLYFvMfqj0eKqDOH3E4yEcmS2IT1BQ== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~10.4.0" + +"@abp/aspnetcore.mvc.ui.theme.shared@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.4.0.tgz#49002e55db00c98931d27357f7ea40eeab09e2e8" + integrity sha512-bNQ+WOnsfQhb98Z9YO0zZsW2K8ZEgOQ+7/86QaqoYTf+ZFvLJsICmNuHryNkCGRM1XJf9EaDZ3qSOwoOGQwS9A== + dependencies: + "@abp/aspnetcore.mvc.ui" "~10.4.0" + "@abp/bootstrap" "~10.4.0" + "@abp/bootstrap-datepicker" "~10.4.0" + "@abp/bootstrap-daterangepicker" "~10.4.0" + "@abp/datatables.net-bs5" "~10.4.0" + "@abp/font-awesome" "~10.4.0" + "@abp/jquery-validation-unobtrusive" "~10.4.0" + "@abp/lodash" "~10.4.0" + "@abp/luxon" "~10.4.0" + "@abp/malihu-custom-scrollbar-plugin" "~10.4.0" + "@abp/moment" "~10.4.0" + "@abp/select2" "~10.4.0" + "@abp/sweetalert2" "~10.4.0" + "@abp/timeago" "~10.4.0" + +"@abp/aspnetcore.mvc.ui@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.4.0.tgz#24e40fdc101c411f29a87adb77baa642299e979c" + integrity sha512-0qVFthS4f4fkCv//UY2GseO+NX+XYqlamZJJwOTmv1UXFW6LxAXxuEQqWYVfFKzwk2lbkXDUcgP8NhhQ9xp3Sw== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.3.0.tgz#032aaf0e474f971b9f90ca7fb83ff7ca4d7be933" - integrity sha512-7mRuRRIE4R0yw1cZuoIskgrqZPUa5/SJzse0K6u+/QDqvyLjjOsyE51WuqAXxlVfKpLp/fBvXZwnCDeCRU9iZQ== +"@abp/bootstrap-datepicker@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.4.0.tgz#a392fd5ce31c86e9fc07f7dedbd0c56a02af341e" + integrity sha512-kOpuCEO64zoJ7a9JASsCLFZIjEYgN0GG/oApS2L729pv6/oEhcupWpwrnvDUhsdGOezXwLIsgTzc6tiG3HxwGA== dependencies: bootstrap-datepicker "^1.10.1" -"@abp/bootstrap-daterangepicker@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.3.0.tgz#b2a9171595ee311c32f57460013870cde67c6b7f" - integrity sha512-ejLu3sWhfNDlsXeUXAQudZvPv0xM4McxIK65vuVEZiKIwFSnrLYil8jBi12EJJmAw5yYA/KC0EdyG82E+AWpmA== +"@abp/bootstrap-daterangepicker@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.4.0.tgz#95f68d3f488a12ede3c12352637b48782eeb031a" + integrity sha512-HX4HutkbPDQWBJNPDozpT1Um9mHcfV+RL+ukwEy0N4e7nD8VJyqLT/a+/FzQN7iUkwV+aNR2v5c0W+dgyqTaEA== dependencies: - "@abp/moment" "~10.3.0" + "@abp/moment" "~10.4.0" bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.3.0.tgz#23535c90613271b02fdad3eae1556b67844729e6" - integrity sha512-JnVOeJUyR78oo+QURvaNqa9xGZaVux0PRIpFpd4Qsqiiz9FvJrbCuSoBkkZoj/0eH23WoZ3bJxy0K9LDE19qvg== +"@abp/bootstrap@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.4.0.tgz#64b22bbdbf6b5bda279b21a7bae0652290b1221f" + integrity sha512-WQzXi5h1mej9twrAB6kD4aJtTNfSxpnuo1pxIOu/8GKGWoDayVY2NMQl+MCVel6dST+6EUTmuDRxc2q9qHRxLg== dependencies: - "@abp/core" "~10.3.0" - "@abp/popper.js" "~10.3.0" + "@abp/core" "~10.4.0" + "@abp/popper.js" "~10.4.0" bootstrap "^5.3.8" -"@abp/clipboard@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-10.3.0.tgz#7bfbfcae6a135065ee13afd1b7d3abc92176a203" - integrity sha512-Cah+jOzcG1KZh6PXWxo+BedU7gsqTuNlTDsCzBCk2NuV2/Blb5qumAwEP4GBKAEIY/XwcFQP/ubxuXMTNjr0jw== +"@abp/clipboard@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-10.4.0.tgz#bac5854af4f8bcaa987d39f234edd58b675a71c8" + integrity sha512-KTTzCDEe+wjpGLdud8MHlwnPr/NdzFDmfHtENgESdIa5KilGOwy2wv24lQ5cyJOB4ftoJzkBaHinKJxg/KU0pw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" clipboard "^2.0.11" -"@abp/core@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.3.0.tgz#9960f8179d1fdc42250d0bbba60e379590c8199c" - integrity sha512-DpL4qrsyCUolypUwxKVDDK8bPVhNbYKx6l2ytUjoOu73CwgPoCSGkvHWrmKIsEsmTfOC9J7+GDErh8jWR/5pVA== +"@abp/core@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.4.0.tgz#de0c09e26d1f20451da9430d939beebb5bb6e9dd" + integrity sha512-3yKLFqUtFo5rgBPQxWkNPsdq1bE9VKwm02yB14lu913EIYhP//akizY4uSK08BozocJNveCASQbgd66z4x6v/Q== dependencies: - "@abp/utils" "~10.3.0" + "@abp/utils" "~10.4.0" -"@abp/datatables.net-bs5@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.3.0.tgz#d8711c52f7c03df51d14d1a5f0e6b00f6d9551fc" - integrity sha512-I8rEU98kEhsNZjVdpPvOcG6F4xr1CgIRb0bTf5bfd8ufzxEUKB+xF9bguJIB5kYwjme8QbMlcJ0V/c+N4iewtQ== +"@abp/datatables.net-bs5@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.4.0.tgz#a24eef99399324e4b89a00a204d2c4c2de379da1" + integrity sha512-T4f7e6sR1qHcvygNUNfdPWsK61C3NQORV35Lzpl98eDwY8XLg2/RLAegpFI8GjESVy2+2PCKVqUoSYZtXaMFmw== dependencies: - "@abp/datatables.net" "~10.3.0" + "@abp/datatables.net" "~10.4.0" datatables.net-bs5 "^2.3.4" -"@abp/datatables.net@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.3.0.tgz#f98319d8e226da5aced4d1d94d3ababb11327074" - integrity sha512-imM25WmO1V0hqeG5iPeLvYMID2HX7SG9drz47qilr74MhpY2i2J7HlsvZDonNkjpxzhK4DHpGeQd6mE2QzG7Hw== +"@abp/datatables.net@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.4.0.tgz#ab48f6e3ccae8d0644f0c01011ff98cc6ca16181" + integrity sha512-esIUozaSEa29G7bdWbEZr22fRChaTuGfNNB8rgBHmVKwgkbHSG7qUTgsfGhNz0T2vfDsK3X37n55xmSdE9RElg== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" datatables.net "^2.3.4" -"@abp/docs@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/docs/-/docs-10.3.0.tgz#9eb45987868543440a3cbc3ff518a581f304860c" - integrity sha512-tjCXLBJKi011FjvnpvilYBuZtmQU1n9h52Vtr4QyWjy26oUIoluc2XJwnth+C6L5l3a3zfmOnSnZmtaphAsp1g== +"@abp/docs@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/docs/-/docs-10.4.0.tgz#b5ad9177888b2421cd5bfc417d2ae2260ded2a63" + integrity sha512-JMdqns4h/Bm9yOu2aXz6LmxGvTnWRTlZ3gFUKV3KiVy0FZfX09DhGh1zZbpr2lBxkSjhjBzcLYptLlUjIL6bzA== dependencies: - "@abp/anchor-js" "~10.3.0" - "@abp/clipboard" "~10.3.0" - "@abp/malihu-custom-scrollbar-plugin" "~10.3.0" - "@abp/popper.js" "~10.3.0" - "@abp/prismjs" "~10.3.0" + "@abp/anchor-js" "~10.4.0" + "@abp/clipboard" "~10.4.0" + "@abp/malihu-custom-scrollbar-plugin" "~10.4.0" + "@abp/popper.js" "~10.4.0" + "@abp/prismjs" "~10.4.0" -"@abp/font-awesome@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.3.0.tgz#43602605f5633132cc6fe21b6ce5ab19b86bdf27" - integrity sha512-RUOVHxDyT81hp01DhUuSshKvLM+NiTt9VP+O4/rSj+SOaSJAm3wX7eziCKrDi6H41FwhoZL75CifLhcZVyVO4A== +"@abp/font-awesome@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.4.0.tgz#327979191615fff571f0ea2e99d01bbcca5378ce" + integrity sha512-RBzDSi/hrjhLKc7h6Kf9CQqC+LCGvS7h8HrRVaIzWCZSeqTfIMFpixjCUrNgkqWI0bU4NDUKXZ4XCOv4nPVlBg== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" "@fortawesome/fontawesome-free" "^7.0.1" -"@abp/jquery-validation-unobtrusive@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.3.0.tgz#5bc14ed642d48f9eae54761ad39bd9f51beeb005" - integrity sha512-j/9fA7tAs1/t3P2U1kKUiDlHAImmseul6KzvV77sNpnms7g4+x6Fgsa+sMzicSRGOLz7lK5RHdBFyvfKxt7/nA== +"@abp/jquery-validation-unobtrusive@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.4.0.tgz#e0114347344931667f48e0f2de5ea4bd6f9d2a88" + integrity sha512-AJncXVB+eTSizq0fTAwLii9a0GeYFPiY+gw+Ki5pak8Pp9LQivphsW32TqQmPC3qZVuYtd4myUUraWuOTT1Adg== dependencies: - "@abp/jquery-validation" "~10.3.0" + "@abp/jquery-validation" "~10.4.0" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.3.0.tgz#e669a54d28f6c0b863a284322e527a5c65cb7dbf" - integrity sha512-Wo95ZJLS1ScKm7dm3zFlEFUKqXrqo3m9+82DX5mFJUVCHHuBPrhxvDjxNJ18z4gpoOUu08uqxX7LFbphtkRM3g== +"@abp/jquery-validation@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.4.0.tgz#bd1ef0217c2500363f25223dd7316ba919a505b5" + integrity sha512-W9QiGnOpaq6uDDwBy4UEqWrP1p2UegHQShW5Cev9vr6g7Lk97UsS1Agh9lnjbvMzFSAiLl3M9gHmyMD4aU3NPA== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" jquery-validation "^1.21.0" -"@abp/jquery@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.3.0.tgz#3b60bcdfe8f9a33be79db1bea2500837fe09c67d" - integrity sha512-TndX/8bJx5vGAAwIykx2CJUZIFFDkwpD+cUjE45WO2dSrY2M5vNfWiuw95OcLGngO8RaT/gTkFGzPuZqKn9ggg== +"@abp/jquery@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.4.0.tgz#7ed3f1808682717ccfa7c6ac6e8db8a8b648b1e1" + integrity sha512-kjFoVIhqlaIHlEs917sIeP1sHmLWXMn+DVKaCkVQUXC2KVq+yvQRC8AZiFQVxOwLvzd9dFFpGwGZgpWkDfvhvg== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" jquery "~3.7.1" -"@abp/lodash@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.3.0.tgz#bd091fc811d85d2f535802631af6165ac1c8a198" - integrity sha512-7JDU9UlbD+9odhFQjolvyJZ6EQKG5kiL2Pt9T1RMVchNh47iEuZwfi8c+CIxa3YxA3n6lAsZ+8Pmxq4yOku+RQ== +"@abp/lodash@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.4.0.tgz#b6fc25b6e2fd74420ca16a5a13cb8fcd39b89efd" + integrity sha512-RuiIGAqJtSBS+4VAk4cg7Zsfj07B3xkiewCaK5j0xUcyjsCJKL1UdXe2zVNL3oE5bXTNMU7yNQWJS4knv2mfwA== dependencies: - "@abp/core" "~10.3.0" - lodash "^4.17.21" + "@abp/core" "~10.4.0" + lodash "^4.18.1" -"@abp/luxon@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.3.0.tgz#acbd92289456a546607ed4506dc36f71e6da53c9" - integrity sha512-IIi2+odQzuFK4qV1KJ2HtlTewSPby2F9CJFNNtNRMzX/P30dQkAGWpNV5/FZzJwFIihR+zSei5wlu11WpPugsw== +"@abp/luxon@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.4.0.tgz#731d20877ec679fa518b7199128676631d030a90" + integrity sha512-/hF+FBugJMkp/H9WNZxlsOy3Rl4DTeR2hqmRPI0sPcdFOixCziYrwzyHg/2Kx8cI/zHa6GRRt1B4Jz4yjQTvdw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" luxon "^3.7.2" -"@abp/malihu-custom-scrollbar-plugin@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.3.0.tgz#209931d02a994ef448371d6203e9ec74c72b61e6" - integrity sha512-hJjwEExP3hKE1MNDG+pcvoHRFUihh3t/0wCPNaoePFd8+QnJuyQ9U0VA+vdtykTxm8uFNE2uaCgyHffoHmzeZA== +"@abp/malihu-custom-scrollbar-plugin@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.4.0.tgz#e1c207d9c66cce64fcb4fe13c091c4b86b00b6d7" + integrity sha512-LC+imsGQganhKWybvQytATjQMXfQwfCfi6RRWREP+pejKPuHpqHzPd+AjKwZpx500K2/ZTwT62FKJ1uDiJXSKA== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.3.0.tgz#5a5c4ece14d1bc4dd33134f85d1d085733d3c975" - integrity sha512-8DKw2jgC/28nLrB/CR9BiyUSt0pUmWF+bEgAsduln6VrSCHn0aRLqgs+wi/9Ol+1bl7pO2wbJOYg1qbnkDhrAQ== +"@abp/moment@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.4.0.tgz#6737898b90b579ce5e0d683657757725a4def09d" + integrity sha512-WUtzjD6+1JGpgS6Yv2Qo/h4jM77LkkasPrywsetmtj/6zyVQWrkk7tKHTCjhAJtdinctcROwIIcwLsELi0nDHg== dependencies: moment "^2.30.1" -"@abp/popper.js@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-10.3.0.tgz#cb2415e45a905854980f4e864c00c8fe1156a6f9" - integrity sha512-Yfafs50t7DKnShxAl9WPpihZxG5cLAt99rJmIyXBYUQIpkHkxDyUIA5b4gCoVCqKgjFDOxG7i6L+Ovd/2N8k7g== +"@abp/popper.js@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-10.4.0.tgz#9d8d09c4e18020b6a75ba30daf6786606f555e84" + integrity sha512-KR5wGZ/MfR1rOPcmFr5ekraYhOSRul9gqempir69hlqrFKUFqt46F9/wLWyaJ7vlsMU6o4gBo6/jE5tICIpzNw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" "@popperjs/core" "^2.11.8" -"@abp/prismjs@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-10.3.0.tgz#4ea33ece50cefcfc8a89c17b0d265b484b7205ee" - integrity sha512-WvkLyg+izkcX6sEOYy41B9lebiniYxMQZ++c+ysyOQKZzrYEEFOOLlnFS9jPqSKLv9cNRizhG1CCfiwoYCxufQ== +"@abp/prismjs@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-10.4.0.tgz#0061d71d18252bc1e66e763aab6d81a7b4fe51b7" + integrity sha512-kjV11XNt2uP01zlhrrScr4WA27lgZ03Cz3svA7/HHnfhFy76MHYo+Dyn7sS4Gp3UobMcf1QWHl4KQNCUnYNTOQ== dependencies: - "@abp/clipboard" "~10.3.0" - "@abp/core" "~10.3.0" + "@abp/clipboard" "~10.4.0" + "@abp/core" "~10.4.0" prismjs "^1.30.0" -"@abp/select2@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.3.0.tgz#199777173ca3e43a5448f8b51b30c3aafe4a3333" - integrity sha512-6sEIYIakVI1DM3eOqOaxwdG8vkyVFBWvVZhoChPZkgIIQ0EH2JLerHv0pePJEl6mQSxOUrikPA8++B2y+sBnQA== +"@abp/select2@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.4.0.tgz#e744988abf81e2194a5850acc4ac1333e9578df8" + integrity sha512-tyAdGzX32joI5vw+MUJGR0iToWCGZrbQg/zKeoCib80Zx5cn+e6GXj6wZyPbsJgVm0jZ7zBweWt3x8HzW9N8Ww== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" select2 "^4.0.13" -"@abp/sweetalert2@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.3.0.tgz#515e0477574b54bc6650b9f5ee193bfc648c715a" - integrity sha512-bY5MXEiKja+vk71ujSn8rpzRC45Ge8zvcca1k1vBJs+oKyRCii//JBxa3H0lagZo5r/QwIqv5ihdyLE1xsuLSg== +"@abp/sweetalert2@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.4.0.tgz#65cb26c8c7c9c02158c9a487969eef033fe04a73" + integrity sha512-kyVMlfnI6MZojJlaRstdHj/CL3olOn4BszjQ155UQaDsGHUu0THmbhpfi4P0q3TCLLzcLM4pkuY3wJHPNl3ZOw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" sweetalert2 "^11.23.0" -"@abp/timeago@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.3.0.tgz#3214a6d000c79504c9fa7cb4e004a967fe46459b" - integrity sha512-Rgcex+jvM9Z9/E0I5Q3wlC2bY5550/+6xRX407k5ho4gxCccYo3c0Gkpiy5pJRFhTwtBgcNzYxArh6NCez+tDg== +"@abp/timeago@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.4.0.tgz#100fc0598e3a605346397f8a395bb96dc70f429d" + integrity sha512-G0ppO7Evai3DdhSg4ozxQpvIke0t3vyg63pNdfOtAPngmuam7WBM1WemBi4u29M84WcSfmk4KuAubXO0aCJgDg== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" timeago "^1.6.7" -"@abp/utils@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.3.0.tgz#d88c0d639d69d6210c3070892aac61d5e945c124" - integrity sha512-iNehxiqWJQl2wwE9EbsQANPv/ITMh0yUcPATUlG/McoLWdpPcqobx75NxXcHDqW8ZarecqSktjJDZPvaUaD+jA== +"@abp/utils@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.4.0.tgz#75175f798e58643c0dec1e85a502186c811e9226" + integrity sha512-BPc7KGDcbdFZ4GonAt3SOi/gYhG+iXI76TMlAzSKMBRhBU0vxFS8I/pb7U5zX0Gjh5M+aT0l2vA3l+POCHPBfw== dependencies: just-compare "^2.3.0" @@ -330,10 +330,10 @@ just-compare@^2.3.0: resolved "https://registry.yarnpkg.com/just-compare/-/just-compare-2.3.0.tgz#a2adcc1d1940536263275f5a1ef1298bcacfeda7" integrity sha512-6shoR7HDT+fzfL3gBahx1jZG3hWLrhPAf+l7nCwahDdT9XDtosB9kIF0ZrzUp5QY8dJWfQVr5rnsPqsbvflDzg== -lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lodash@^4.18.1: + version "4.18.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" + integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== luxon@^3.7.2: version "3.7.2" diff --git a/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentParameterDto.cs b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentParameterDto.cs index ea387b2794..9bd3c0aefd 100644 --- a/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentParameterDto.cs +++ b/modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentParameterDto.cs @@ -9,5 +9,7 @@ namespace Volo.Docs.Documents public string DisplayName { get; set; } public Dictionary Values { get; set; } + + public Dictionary> DependsOn { get; set; } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml index 521ba70ffd..a68298a431 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml @@ -474,7 +474,12 @@
- @if (Model.DocumentPreferences != null && Model.DocumentPreferences.Parameters != null && Model.DocumentPreferences.Parameters.Any()) + @{ + var visibleParameters = Model.DocumentPreferences?.Parameters? + .Where(p => Model.IsParameterVisible(p)) + .ToList() ?? new List(); + } + @if (visibleParameters.Count > 0) {
@@ -488,10 +493,11 @@ @{ const int maxCellCount = 3; - var count = Model.DocumentPreferences.Parameters.Count; + var count = visibleParameters.Count; var rowCount = count / maxCellCount + (count % maxCellCount > 0 ? 1 : 0); var cellSize = 12 / (count > maxCellCount ? maxCellCount : count); - var latestCellSize = 12 / count - (rowCount - 1) * maxCellCount; + var lastRowCellCount = count - (rowCount - 1) * maxCellCount; + var latestCellSize = 12 / lastRowCellCount; string BuildParameterDivClass(int index) { @@ -503,7 +509,7 @@ for (var i = 0; i < count; ++i) { - var parameter = Model.DocumentPreferences.Parameters[i]; + var parameter = visibleParameters[i];
diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs index 35e3c05f15..6eb3c02a8c 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs @@ -90,6 +90,29 @@ namespace Volo.Docs.Pages.Documents.Project public DocumentRenderParameters UserPreferences { get; set; } = new DocumentRenderParameters(); + private HashSet? _renderedParameterNamesCache; + private HashSet RenderedParameterNames => + _renderedParameterNamesCache ??= (DocumentPreferences?.Parameters?.Select(p => p.Name).ToHashSet() ?? new HashSet()); + + public virtual bool IsParameterVisible(DocumentParameterDto parameter) + { + return IsParameterVisibleGiven(parameter, UserPreferences); + } + + protected virtual bool IsParameterVisibleGiven(DocumentParameterDto parameter, IReadOnlyDictionary selectedValues) + { + if (parameter.DependsOn == null || parameter.DependsOn.Count == 0) + { + return true; + } + + return parameter.DependsOn.All(rule => + rule.Value == null || !RenderedParameterNames.Contains(rule.Key) + || (rule.Value.Count > 0 + && selectedValues.TryGetValue(rule.Key, out var current) + && rule.Value.Contains(current))); + } + public List AlternativeOptionLinkQueries { get; set; } = new List(); public bool FullSearchEnabled { get; set; } @@ -827,7 +850,8 @@ namespace Volo.Docs.Pages.Documents.Project { Name = parameter.Name, DisplayName = parameter.DisplayName, - Values = new Dictionary() + Values = new Dictionary(), + DependsOn = parameter.DependsOn }; foreach (var value in parameter.Values) @@ -847,13 +871,13 @@ namespace Volo.Docs.Pages.Documents.Project { if (!DocumentPreferences?.Parameters?.Any() ?? true) { - return; + return; } - AlternativeOptionLinkQueries = CollectAlternativeOptionLinksRecursively(); + AlternativeOptionLinkQueries = CollectAlternativeOptionLinksRecursively(0, new Dictionary()); } - private List CollectAlternativeOptionLinksRecursively(int index = 0) + private List CollectAlternativeOptionLinksRecursively(int index, Dictionary selected) { if (index >= DocumentPreferences.Parameters.Count) { @@ -861,13 +885,20 @@ namespace Volo.Docs.Pages.Documents.Project } var option = DocumentPreferences.Parameters[index]; + + if (!IsParameterVisibleGiven(option, selected)) + { + return CollectAlternativeOptionLinksRecursively(index + 1, selected); + } + var queries = new List(); foreach (var key in option.Values.Keys) { - var linkQuery = new StringBuilder($"{option.Name}={key}"); + var linkQuery = $"{option.Name}={key}"; - var restOfQueries = CollectAlternativeOptionLinksRecursively(index + 1); + var nextSelected = new Dictionary(selected) { [option.Name] = key }; + var restOfQueries = CollectAlternativeOptionLinksRecursively(index + 1, nextSelected); if (restOfQueries.Any()) { @@ -878,7 +909,7 @@ namespace Volo.Docs.Pages.Documents.Project } else { - queries.Add($"{linkQuery}"); + queries.Add(linkQuery); } } diff --git a/modules/feature-management/Volo.Abp.FeatureManagement.slnx b/modules/feature-management/Volo.Abp.FeatureManagement.slnx index f246fb78b0..8b504d625b 100644 --- a/modules/feature-management/Volo.Abp.FeatureManagement.slnx +++ b/modules/feature-management/Volo.Abp.FeatureManagement.slnx @@ -2,6 +2,9 @@ + + + diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.Server/AbpFeatureManagementBlazorMudBlazorServerModule.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.Server/AbpFeatureManagementBlazorMudBlazorServerModule.cs new file mode 100644 index 0000000000..68bd8d2a84 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.Server/AbpFeatureManagementBlazorMudBlazorServerModule.cs @@ -0,0 +1,12 @@ +using Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor; +using Volo.Abp.Modularity; + +namespace Volo.Abp.FeatureManagement.Blazor.MudBlazor.Server; + +[DependsOn( + typeof(AbpFeatureManagementBlazorMudBlazorModule), + typeof(AbpAspNetCoreComponentsServerThemingMudBlazorModule) +)] +public class AbpFeatureManagementBlazorMudBlazorServerModule : AbpModule +{ +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.Server/FodyWeavers.xml b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.Server/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.Server/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.Server/FodyWeavers.xsd b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.Server/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.Server/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.Server/Volo.Abp.FeatureManagement.Blazor.MudBlazor.Server.csproj b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.Server/Volo.Abp.FeatureManagement.Blazor.MudBlazor.Server.csproj new file mode 100644 index 0000000000..43bddde3b2 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.Server/Volo.Abp.FeatureManagement.Blazor.MudBlazor.Server.csproj @@ -0,0 +1,15 @@ + + + + + + + net10.0 + + + + + + + + diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.WebAssembly/AbpFeatureManagementBlazorMudBlazorWebAssemblyModule.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.WebAssembly/AbpFeatureManagementBlazorMudBlazorWebAssemblyModule.cs new file mode 100644 index 0000000000..81ff2be458 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.WebAssembly/AbpFeatureManagementBlazorMudBlazorWebAssemblyModule.cs @@ -0,0 +1,13 @@ +using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor; +using Volo.Abp.Modularity; + +namespace Volo.Abp.FeatureManagement.Blazor.MudBlazor.WebAssembly; + +[DependsOn( + typeof(AbpFeatureManagementBlazorMudBlazorModule), + typeof(AbpAspNetCoreComponentsWebAssemblyThemingMudBlazorModule), + typeof(AbpFeatureManagementHttpApiClientModule) +)] +public class AbpFeatureManagementBlazorMudBlazorWebAssemblyModule : AbpModule +{ +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xml b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xsd b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.WebAssembly/Volo.Abp.FeatureManagement.Blazor.MudBlazor.WebAssembly.csproj b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.WebAssembly/Volo.Abp.FeatureManagement.Blazor.MudBlazor.WebAssembly.csproj new file mode 100644 index 0000000000..4e5f72aa1a --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.WebAssembly/Volo.Abp.FeatureManagement.Blazor.MudBlazor.WebAssembly.csproj @@ -0,0 +1,16 @@ + + + + + + + net10.0 + + + + + + + + + diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/AbpFeatureManagementBlazorMudBlazorModule.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/AbpFeatureManagementBlazorMudBlazorModule.cs new file mode 100644 index 0000000000..08d86950d1 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/AbpFeatureManagementBlazorMudBlazorModule.cs @@ -0,0 +1,34 @@ +using Localization.Resources.AbpUi; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor; +using Volo.Abp.FeatureManagement.Blazor.MudBlazor.Settings; +using Volo.Abp.FeatureManagement.Localization; +using Volo.Abp.Features; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.SettingManagement.Blazor.MudBlazor; + +namespace Volo.Abp.FeatureManagement.Blazor.MudBlazor; + +[DependsOn( + typeof(AbpAspNetCoreComponentsWebThemingMudBlazorModule), + typeof(AbpFeatureManagementApplicationContractsModule), + typeof(AbpFeaturesModule), + typeof(AbpSettingManagementBlazorMudBlazorModule) +)] +public class AbpFeatureManagementBlazorMudBlazorModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Contributors.Add(new FeatureSettingManagementComponentContributor()); + }); + + Configure(options => + { + options.Resources + .Get() + .AddBaseTypes(typeof(AbpUiResource)); + }); + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/AbpFeatureManagementComponentBase.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/AbpFeatureManagementComponentBase.cs new file mode 100644 index 0000000000..68ed6fc8fc --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/AbpFeatureManagementComponentBase.cs @@ -0,0 +1,12 @@ +using Volo.Abp.AspNetCore.Components; +using Volo.Abp.FeatureManagement.Localization; + +namespace Volo.Abp.FeatureManagement.Blazor.MudBlazor; + +public abstract class AbpFeatureManagementComponentBase : AbpComponentBase +{ + protected AbpFeatureManagementComponentBase() + { + LocalizationResource = typeof(AbpFeatureManagementResource); + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureManagementModal.razor b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureManagementModal.razor new file mode 100644 index 0000000000..57831e2598 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureManagementModal.razor @@ -0,0 +1,83 @@ +@using global::MudBlazor +@using JetBrains.Annotations +@using Volo.Abp.Validation.StringValues +@using Microsoft.Extensions.Localization +@using Volo.Abp.FeatureManagement.Localization +@inherits AbpFeatureManagementComponentBase + + + + @L["Features"]@ProviderKeyDisplayName + + + @if (Groups == null || !Groups.Any()) + { + @L["NoFeatureFoundMessage"] + } + else + { + + @foreach (var group in Groups) + { + + + @group.DisplayName + @foreach (var feature in group.Features) + { + var disabled = IsDisabled(feature); +
+ @if (feature.ValueType is FreeTextStringValueType) + { + + @if (feature.Description != null) + { + @feature.Description + } + } + + @if (feature.ValueType is SelectionStringValueType) + { + var items = ((SelectionStringValueType)feature.ValueType).ItemSource.Items; + var selectedValue = SelectionStringValues[feature.Name]; + + @foreach (var item in items) + { + + @CreateStringLocalizer(item.DisplayText.ResourceName).GetString(item.DisplayText.Name) + + } + + @if (feature.Description != null) + { + @feature.Description + } + } + + @if (feature.ValueType is ToggleStringValueType) + { + + @if (feature.Description != null) + { + @feature.Description + } + } +
+ } +
+
+ } +
+ } +
+ + @L["Cancel"] + @L["ResetToDefault"] + @L["Save"] + +
diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureManagementModal.razor.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureManagementModal.razor.cs new file mode 100644 index 0000000000..cfbee7f16f --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureManagementModal.razor.cs @@ -0,0 +1,263 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Options; +using MudBlazor; +using Volo.Abp.AspNetCore.Components.Messages; +using Volo.Abp.AspNetCore.Components.Web.Configuration; +using Volo.Abp.FeatureManagement.Blazor.MudBlazor; +using Volo.Abp.Features; +using Volo.Abp.Localization; +using Volo.Abp.Validation.StringValues; + +namespace Volo.Abp.FeatureManagement.Blazor.MudBlazor.Components; + +public partial class FeatureManagementModal +{ + [Inject] protected IFeatureAppService FeatureAppService { get; set; } = default!; + + [Inject] protected IUiMessageService UiMessageService { get; set; } = default!; + + [Inject] protected IStringLocalizerFactory HtmlLocalizerFactory { get; set; } = default!; + + [Inject] protected IOptions LocalizationOptions { get; set; } = default!; + + [Inject] protected ICurrentApplicationConfigurationCacheResetService CurrentApplicationConfigurationCacheResetService { get; set; } = default!; + + [Inject] protected ISnackbar Snackbar { get; set; } = default!; + + [Inject] protected IDialogService DialogService { get; set; } = default!; + + protected bool _isVisible; + protected int _activeTabIndex = 0; + + protected string? ProviderName; + protected string? ProviderKey; + protected string? ProviderKeyDisplayName; + + protected List? Groups { get; set; } + + protected Dictionary ToggleValues = new(); + + protected Dictionary SelectionStringValues = new(); + + public virtual async Task OpenAsync(string providerName, string? providerKey = null, string? providerKeyDisplayName = null) + { + try + { + ProviderName = providerName; + ProviderKey = providerKey; + + ToggleValues = new Dictionary(); + SelectionStringValues = new Dictionary(); + + if (!providerKeyDisplayName.IsNullOrWhiteSpace()) + { + ProviderKeyDisplayName = $" - {providerKeyDisplayName}"; + } + else + { + ProviderKeyDisplayName = string.Empty; + } + + var result = await FeatureAppService.GetAsync(ProviderName, ProviderKey); + + Groups = result?.Groups ?? new List(); + + if (Groups.Any()) + { + _activeTabIndex = 0; + } + + foreach (var featureGroupDto in Groups) + { + foreach (var featureDto in featureGroupDto.Features) + { + if (featureDto.ValueType is ToggleStringValueType) + { + ToggleValues.Add(featureDto.Name, bool.Parse(featureDto.Value)); + } + + if (featureDto.ValueType is SelectionStringValueType) + { + SelectionStringValues.Add(featureDto.Name, featureDto.Value); + } + } + } + + _isVisible = true; + await InvokeAsync(StateHasChanged); + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + public virtual Task CloseModal() + { + _isVisible = false; + return InvokeAsync(StateHasChanged); + } + + protected virtual async Task SaveAsync() + { + try + { + if (Groups == null) + { + return; + } + + var features = new UpdateFeaturesDto + { + Features = Groups.SelectMany(g => g.Features).Select(f => new UpdateFeatureDto + { + Name = f.Name, + Value = f.ValueType is ToggleStringValueType ? ToggleValues[f.Name].ToString() : + f.ValueType is SelectionStringValueType ? SelectionStringValues[f.Name] : f.Value + }).ToList() + }; + + await FeatureAppService.UpdateAsync(ProviderName!, ProviderKey, features); + + await CurrentApplicationConfigurationCacheResetService.ResetAsync(); + + _isVisible = false; + await InvokeAsync(StateHasChanged); + Snackbar.Add(L["SavedSuccessfully"], Severity.Success); + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + public virtual async Task DeleteAsync(string providerName, string? providerKey = null) + { + try + { + var confirmed = await DialogService.ShowMessageBox( + L["AreYouSure"], + L["AreYouSureToResetToDefault"], + yesText: L["Yes"], + cancelText: L["Cancel"]); + + if (confirmed != true) + { + return; + } + + await FeatureAppService.DeleteAsync(ProviderName!, ProviderKey); + Snackbar.Add(L["ResetedToDefault"], Severity.Success); + + await CloseModal(); + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + protected virtual string GetNormalizedGroupName(string name) + { + return "FeatureGroup_" + name.Replace(".", "_"); + } + + protected virtual string GetFeatureStyles(FeatureDto feature) + { + return $"margin-inline-start: {feature.Depth * 20}px"; + } + + protected virtual bool IsDisabled(FeatureDto feature) + { + return feature.Value != null && + feature.Provider.Name != null && + feature.Provider.Name != ProviderName && + feature.Provider.Name != DefaultValueFeatureValueProvider.ProviderName; + } + + public virtual string GetShownName(FeatureDto featureDto) + { + return !IsDisabled(featureDto) + ? featureDto.DisplayName + : $"{featureDto.DisplayName} ({featureDto.Provider.Name})"; + } + + protected virtual async Task OnFeatureValueChangedAsync(string? value, FeatureDto feature) + { + if (feature?.ValueType?.Validator.IsValid(value) == true) + { + feature.Value = value; + } + else + { + await UiMessageService.Warn(L["Volo.Abp.FeatureManagement:InvalidFeatureValue", feature.DisplayName]); + } + } + + protected virtual Task OnSelectedValueChangedAsync(bool value, FeatureDto feature) + { + ToggleValues[feature.Name] = value; + + if (value) + { + CheckParents(feature.ParentName); + } + else + { + UncheckChildren(feature.Name); + } + + return Task.CompletedTask; + } + + protected virtual void CheckParents(string? parentName) + { + if (parentName.IsNullOrWhiteSpace() || Groups == null) + { + return; + } + + foreach (var featureGroupDto in Groups) + { + foreach (var featureDto in featureGroupDto.Features) + { + if (featureDto.Name == parentName && ToggleValues.ContainsKey(featureDto.Name)) + { + ToggleValues[featureDto.Name] = true; + CheckParents(featureDto.ParentName); + } + } + } + } + + protected virtual void UncheckChildren(string featureName) + { + if (Groups == null) + { + return; + } + + foreach (var featureGroupDto in Groups) + { + foreach (var featureDto in featureGroupDto.Features) + { + if (featureDto.ParentName == featureName && ToggleValues.ContainsKey(featureDto.Name)) + { + ToggleValues[featureDto.Name] = false; + UncheckChildren(featureDto.Name); + } + } + } + } + + protected virtual IStringLocalizer CreateStringLocalizer(string resourceName) + { + return HtmlLocalizerFactory.CreateByResourceNameOrNull(resourceName) ?? + HtmlLocalizerFactory.CreateDefaultOrNull(); + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingManagementComponent.razor b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingManagementComponent.razor new file mode 100644 index 0000000000..b1ebc29502 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingManagementComponent.razor @@ -0,0 +1,15 @@ +@using global::MudBlazor +@inherits Volo.Abp.AspNetCore.Components.AbpComponentBase + +@if (Settings != null && Settings.HasManageHostFeaturesPermission) +{ + @L["ManageHostFeaturesText"] + + @L["ManageHostFeatures"] + + + +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingManagementComponent.razor.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingManagementComponent.razor.cs new file mode 100644 index 0000000000..8c04c6868d --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingManagementComponent.razor.cs @@ -0,0 +1,41 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Components; +using Volo.Abp.AspNetCore.Components; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.FeatureManagement.Blazor.MudBlazor.Components; +using Volo.Abp.FeatureManagement.Localization; +using Volo.Abp.Features; + +namespace Volo.Abp.FeatureManagement.Blazor.MudBlazor.Components.FeatureSettingGroup; + +public partial class FeatureSettingManagementComponent : AbpComponentBase +{ + [Inject] + protected PermissionChecker PermissionChecker { get; set; } = default!; + + protected FeatureManagementModal? FeatureManagementModal; + + protected FeatureSettingViewModel? Settings; + + public FeatureSettingManagementComponent() + { + LocalizationResource = typeof(AbpFeatureManagementResource); + } + + protected async override Task OnInitializedAsync() + { + Settings = new FeatureSettingViewModel + { + HasManageHostFeaturesPermission = await AuthorizationService.IsGrantedAsync(FeatureManagementPermissions.ManageHostFeatures) + }; + } + + protected virtual async Task OnManageHostFeaturesClicked() + { + if (FeatureManagementModal != null) + { + await FeatureManagementModal.OpenAsync(TenantFeatureValueProvider.ProviderName); + } + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingViewModel.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingViewModel.cs new file mode 100644 index 0000000000..b933647b73 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Components/FeatureSettingGroup/FeatureSettingViewModel.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.FeatureManagement.Blazor.MudBlazor.Components.FeatureSettingGroup; + +public class FeatureSettingViewModel +{ + public bool HasManageHostFeaturesPermission { get; set; } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/FodyWeavers.xml b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/FodyWeavers.xsd b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Settings/FeatureSettingManagementComponentContributor.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Settings/FeatureSettingManagementComponentContributor.cs new file mode 100644 index 0000000000..50bec2f38c --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Settings/FeatureSettingManagementComponentContributor.cs @@ -0,0 +1,41 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using Volo.Abp.FeatureManagement.Blazor.MudBlazor.Components.FeatureSettingGroup; +using Volo.Abp.FeatureManagement.Localization; +using Volo.Abp.SettingManagement.Blazor.MudBlazor; + +namespace Volo.Abp.FeatureManagement.Blazor.MudBlazor.Settings; + +public class FeatureSettingManagementComponentContributor: ISettingComponentContributor +{ + public virtual async Task ConfigureAsync(SettingComponentCreationContext context) + { + if (!await CheckPermissionsInternalAsync(context)) + { + return; + } + + var l = context.ServiceProvider.GetRequiredService>(); + context.Groups.Add( + new SettingComponentGroup( + "Volo.Abp.Feature", + l["Permission:FeatureManagement"], + typeof(FeatureSettingManagementComponent) + ) + ); + } + + public virtual async Task CheckPermissionsAsync(SettingComponentCreationContext context) + { + return await CheckPermissionsInternalAsync(context); + } + + protected virtual async Task CheckPermissionsInternalAsync(SettingComponentCreationContext context) + { + var authorizationService = context.ServiceProvider.GetRequiredService(); + + return await authorizationService.IsGrantedAsync(FeatureManagementPermissions.ManageHostFeatures); + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Volo.Abp.FeatureManagement.Blazor.MudBlazor.csproj b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Volo.Abp.FeatureManagement.Blazor.MudBlazor.csproj new file mode 100644 index 0000000000..3a568524e3 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/Volo.Abp.FeatureManagement.Blazor.MudBlazor.csproj @@ -0,0 +1,20 @@ + + + + + + + net10.0 + + + + + + + + + + + + + diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/_Imports.razor b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/_Imports.razor new file mode 100644 index 0000000000..4e91324cd8 --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor/_Imports.razor @@ -0,0 +1,4 @@ +@using Microsoft.AspNetCore.Components.Web +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor +@using MudBlazor +@using Volo.Abp.MudBlazorUI diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/AbpFeatureManagementWebModule.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/AbpFeatureManagementWebModule.cs index 1c8c2a2572..bd4d26bb8c 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/AbpFeatureManagementWebModule.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/AbpFeatureManagementWebModule.cs @@ -1,10 +1,12 @@ -using Microsoft.AspNetCore.Mvc.RazorPages; +using Localization.Resources.AbpUi; +using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.FeatureManagement.Localization; using Volo.Abp.FeatureManagement.Settings; using Volo.Abp.Http.ProxyScripting.Generators.JQuery; +using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.SettingManagement.Web; using Volo.Abp.SettingManagement.Web.Pages.SettingManagement; @@ -38,6 +40,13 @@ public class AbpFeatureManagementWebModule : AbpModule public override void ConfigureServices(ServiceConfigurationContext context) { + Configure(options => + { + options.Resources + .Get() + .AddBaseTypes(typeof(AbpUiResource)); + }); + Configure(options => { options.FileSets.AddEmbedded(); diff --git a/modules/identity/.abpstudio/state.json b/modules/identity/.abpstudio/state.json deleted file mode 100644 index 11b53dd162..0000000000 --- a/modules/identity/.abpstudio/state.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "selectedRunnerProfile": null, - "selectedKubernetesProfile": null -} \ No newline at end of file diff --git a/modules/identity/Volo.Abp.Identity.slnx b/modules/identity/Volo.Abp.Identity.slnx index 3205de45ba..968864e7f7 100644 --- a/modules/identity/Volo.Abp.Identity.slnx +++ b/modules/identity/Volo.Abp.Identity.slnx @@ -3,6 +3,9 @@ + + + diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpEmailTwoFactorTokenProvider.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpEmailTwoFactorTokenProvider.cs new file mode 100644 index 0000000000..45e3e8eccb --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpEmailTwoFactorTokenProvider.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Volo.Abp.Identity; +using Volo.Abp.Threading; +using Volo.Abp.Timing; + +namespace Volo.Abp.Identity.AspNetCore; + +/// +/// Single-use email 2FA code provider; replaces the ASP.NET Core Identity TOTP-based +/// EmailTokenProvider<TUser> under . +/// +public class AbpEmailTwoFactorTokenProvider : AbpTwoFactorTokenProvider +{ + public const string ProviderName = "AbpEmailTwoFactor"; + + public override string Name => ProviderName; + + public AbpEmailTwoFactorTokenProvider( + IOptions options, + IIdentityUserRepository userRepository, + ICancellationTokenProvider cancellationTokenProvider, + IClock clock, + IDataProtectionProvider dataProtectionProvider) + : base(options.Value, userRepository, cancellationTokenProvider, clock, dataProtectionProvider) + { + } + + public override async Task CanGenerateTwoFactorTokenAsync(UserManager manager, IdentityUser user) + { + var email = await manager.GetEmailAsync(user); + return !string.IsNullOrWhiteSpace(email) && await manager.IsEmailConfirmedAsync(user); + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpEmailTwoFactorTokenProviderOptions.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpEmailTwoFactorTokenProviderOptions.cs new file mode 100644 index 0000000000..6ec9e96e1a --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpEmailTwoFactorTokenProviderOptions.cs @@ -0,0 +1,5 @@ +namespace Volo.Abp.Identity.AspNetCore; + +public class AbpEmailTwoFactorTokenProviderOptions : AbpTwoFactorTokenProviderOptions +{ +} diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs index 3284a31601..eb8a1a7c54 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; @@ -23,6 +23,8 @@ public class AbpIdentityAspNetCoreModule : AbpModule .AddTokenProvider(AbpPasswordResetTokenProvider.ProviderName) .AddTokenProvider(AbpEmailConfirmationTokenProvider.ProviderName) .AddTokenProvider(AbpChangeEmailTokenProvider.ProviderName) + .AddTokenProvider(TokenOptions.DefaultEmailProvider) + .AddTokenProvider(TokenOptions.DefaultPhoneProvider) .AddSignInManager() .AddUserValidator(); }); diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpPhoneNumberTwoFactorTokenProvider.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpPhoneNumberTwoFactorTokenProvider.cs new file mode 100644 index 0000000000..068e94cb20 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpPhoneNumberTwoFactorTokenProvider.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Volo.Abp.Identity; +using Volo.Abp.Threading; +using Volo.Abp.Timing; + +namespace Volo.Abp.Identity.AspNetCore; + +/// +/// Single-use phone 2FA code provider; replaces the ASP.NET Core Identity TOTP-based +/// PhoneNumberTokenProvider<TUser> under . +/// +public class AbpPhoneNumberTwoFactorTokenProvider : AbpTwoFactorTokenProvider +{ + public const string ProviderName = "AbpPhoneNumberTwoFactor"; + + public override string Name => ProviderName; + + public AbpPhoneNumberTwoFactorTokenProvider( + IOptions options, + IIdentityUserRepository userRepository, + ICancellationTokenProvider cancellationTokenProvider, + IClock clock, + IDataProtectionProvider dataProtectionProvider) + : base(options.Value, userRepository, cancellationTokenProvider, clock, dataProtectionProvider) + { + } + + public override async Task CanGenerateTwoFactorTokenAsync(UserManager manager, IdentityUser user) + { + var phoneNumber = await manager.GetPhoneNumberAsync(user); + return !string.IsNullOrWhiteSpace(phoneNumber) && await manager.IsPhoneNumberConfirmedAsync(user); + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpPhoneNumberTwoFactorTokenProviderOptions.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpPhoneNumberTwoFactorTokenProviderOptions.cs new file mode 100644 index 0000000000..a477ecc594 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpPhoneNumberTwoFactorTokenProviderOptions.cs @@ -0,0 +1,5 @@ +namespace Volo.Abp.Identity.AspNetCore; + +public class AbpPhoneNumberTwoFactorTokenProviderOptions : AbpTwoFactorTokenProviderOptions +{ +} diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs index 4b3147c14e..34bf4e0c8d 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSignInManager.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Security.Claims; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; @@ -16,6 +17,7 @@ public class AbpSignInManager : SignInManager protected ISettingProvider SettingProvider { get; } protected IdentityUserManager IdentityUserManager { get; } protected ICurrentTenant CurrentTenant { get; } + protected IOptions IdentityOptionsAccessor { get; } public AbpSignInManager( IdentityUserManager userManager, @@ -40,6 +42,7 @@ public class AbpSignInManager : SignInManager SettingProvider = settingProvider; IdentityUserManager = userManager; CurrentTenant = currentTenant; + IdentityOptionsAccessor = optionsAccessor; } public override async Task PasswordSignInAsync( @@ -85,6 +88,7 @@ public class AbpSignInManager : SignInManager using (CurrentTenant.Change(user.TenantId)) { + await IdentityOptionsAccessor.SetAsync(); return await SignInOrTwoFactorAsync(user, isPersistent); } } @@ -98,6 +102,7 @@ public class AbpSignInManager : SignInManager using (CurrentTenant.Change(user.TenantId)) { + await IdentityOptionsAccessor.SetAsync(); return await PasswordSignInAsync(user, password, isPersistent, lockoutOnFailure); } } @@ -110,12 +115,17 @@ public class AbpSignInManager : SignInManager return SignInResult.Failed; } - var error = await PreSignInCheck(user); - if (error != null) + using (CurrentTenant.Change(user.TenantId)) { - return error; + await IdentityOptionsAccessor.SetAsync(); + + var error = await PreSignInCheck(user); + if (error != null) + { + return error; + } + return await SignInOrTwoFactorAsync(user, isPersistent, loginProvider, bypassTwoFactor); } - return await SignInOrTwoFactorAsync(user, isPersistent, loginProvider, bypassTwoFactor); } public virtual async Task FindByEmailAsync(string email) @@ -133,6 +143,64 @@ public class AbpSignInManager : SignInManager return await IdentityUserManager.FindSharedUserByLoginAsync(loginProvider, providerKey); } + public override async Task GetTwoFactorAuthenticationUserAsync() + { + var result = await Context.AuthenticateAsync(IdentityConstants.TwoFactorUserIdScheme); + if (result?.Principal == null) + { + return null; + } + + var userId = result.Principal.FindFirstValue(ClaimTypes.Name); + if (string.IsNullOrWhiteSpace(userId)) + { + return null; + } + + return await IdentityUserManager.FindSharedUserByIdAsync(userId); + } + + public override async Task TwoFactorSignInAsync(string provider, string code, bool isPersistent, bool rememberClient) + { + var user = await GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + return SignInResult.Failed; + } + + using (CurrentTenant.Change(user.TenantId)) + { + await IdentityOptionsAccessor.SetAsync(); + return await base.TwoFactorSignInAsync(provider, code, isPersistent, rememberClient); + } + } + + public override async Task TwoFactorRecoveryCodeSignInAsync(string recoveryCode) + { + var user = await GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + return SignInResult.Failed; + } + + using (CurrentTenant.Change(user.TenantId)) + { + await IdentityOptionsAccessor.SetAsync(); + + // Base TwoFactorRecoveryCodeSignInAsync does not invoke PreSignInCheck, which means + // AbpSignInManager's IsActive / ShouldChangePassword checks would be bypassed. Run the + // same pre-sign-in checks here so recovery-code sign-in has the same gating as the + // regular two-factor sign-in path. + var preSignInCheckResult = await PreSignInCheck(user); + if (preSignInCheckResult != null) + { + return preSignInCheckResult; + } + + return await base.TwoFactorRecoveryCodeSignInAsync(recoveryCode); + } + } + /// /// This is to call the protection method PreSignInCheck /// diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSingleActiveTokenProvider.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSingleActiveTokenProvider.cs index aa6454085c..835b52b3b0 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSingleActiveTokenProvider.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpSingleActiveTokenProvider.cs @@ -51,7 +51,7 @@ public abstract class AbpSingleActiveTokenProvider : DataProtectorTokenProvider< var tokenHash = ComputeSha256Hash(token); user.SetToken(InternalLoginProvider, Options.Name + ":" + purpose, tokenHash); - await manager.UpdateAsync(user); + (await manager.UpdateAsync(user)).CheckErrors(); return token; } diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpTwoFactorTokenProvider.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpTwoFactorTokenProvider.cs new file mode 100644 index 0000000000..11fabf3c6b --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpTwoFactorTokenProvider.cs @@ -0,0 +1,224 @@ +using System; +using System.Globalization; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Identity; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.Identity; +using Volo.Abp.Threading; +using Volo.Abp.Timing; + +namespace Volo.Abp.Identity.AspNetCore; + +/// +/// Base class for ABP two-factor verification code providers (e.g. Email, Phone). +/// Generates a numeric OTP, stores it encrypted under +/// with an absolute UTC expiration, and removes the stored entry on successful +/// validation (single-use). Expected to be combined with Identity lockout for +/// rate-limiting. +/// +public abstract class AbpTwoFactorTokenProvider : IUserTwoFactorTokenProvider +{ + public const string InternalLoginProvider = "[AbpTwoFactorToken]"; + + protected const char StoredValueSeparator = '|'; + + protected const string DataProtectionPurposeRoot = "Volo.Abp.Identity.AspNetCore.AbpTwoFactorTokenProvider"; + + protected AbpTwoFactorTokenProviderOptions Options { get; } + + protected IIdentityUserRepository UserRepository { get; } + + protected ICancellationTokenProvider CancellationTokenProvider { get; } + + protected IClock Clock { get; } + + protected IDataProtectionProvider DataProtectionProvider { get; } + + /// Unique provider name; used as the stored-token key prefix and DataProtection purpose segment. + public abstract string Name { get; } + + protected AbpTwoFactorTokenProvider( + AbpTwoFactorTokenProviderOptions options, + IIdentityUserRepository userRepository, + ICancellationTokenProvider cancellationTokenProvider, + IClock clock, + IDataProtectionProvider dataProtectionProvider) + { + Options = options; + UserRepository = userRepository; + CancellationTokenProvider = cancellationTokenProvider; + Clock = clock; + DataProtectionProvider = dataProtectionProvider; + } + + public abstract Task CanGenerateTwoFactorTokenAsync(UserManager manager, IdentityUser user); + + public virtual async Task GenerateAsync(string purpose, UserManager manager, IdentityUser user) + { + var code = GenerateNumericCode(Options.CodeLength); + var protectedCode = CreateProtector(purpose).Protect(code); + var expiresAtUnixSeconds = ToUnixSeconds(Clock.Now.Add(Options.TokenLifespan)); + var storedValue = protectedCode + StoredValueSeparator + expiresAtUnixSeconds.ToString(CultureInfo.InvariantCulture); + + await UserRepository.EnsureCollectionLoadedAsync(user, u => u.Tokens, CancellationTokenProvider.Token); + user.SetToken(InternalLoginProvider, GetTokenName(purpose), storedValue); + + (await manager.UpdateAsync(user)).CheckErrors(); + + return code; + } + + public virtual async Task ValidateAsync(string purpose, string token, UserManager manager, IdentityUser user) + { + if (string.IsNullOrEmpty(token)) + { + return false; + } + + await UserRepository.EnsureCollectionLoadedAsync(user, u => u.Tokens, CancellationTokenProvider.Token); + + var tokenName = GetTokenName(purpose); + var stored = user.FindToken(InternalLoginProvider, tokenName)?.Value; + if (stored == null) + { + return false; + } + + if (!TryParseStoredValue(stored, out var protectedCode, out var expiresAtUnixSeconds)) + { + await TryRemoveStoredTokenAsync(manager, user, tokenName); + return false; + } + + if (ToUnixSeconds(Clock.Now) >= expiresAtUnixSeconds) + { + await TryRemoveStoredTokenAsync(manager, user, tokenName); + return false; + } + + string storedCode; + try + { + storedCode = CreateProtector(purpose).Unprotect(protectedCode); + } + catch (CryptographicException) + { + await TryRemoveStoredTokenAsync(manager, user, tokenName); + return false; + } + catch (FormatException) + { + await TryRemoveStoredTokenAsync(manager, user, tokenName); + return false; + } + + var storedBytes = Encoding.UTF8.GetBytes(storedCode); + var inputBytes = Encoding.UTF8.GetBytes(token); + if (storedBytes.Length != inputBytes.Length || + !CryptographicOperations.FixedTimeEquals(storedBytes, inputBytes)) + { + // Keep stored entry so the user can retry until expiration. Callers must rate-limit. + return false; + } + + // Translate ConcurrencyStamp failure (another request won the consume race) to false, + // so legitimate concurrent verification doesn't surface as a 500. + try + { + await RemoveStoredTokenAsync(manager, user, tokenName); + } + catch (AbpIdentityResultException) + { + return false; + } + + return true; + } + + /// + /// Removes the stored entry. Throws on persistence + /// failure so the successful-verification path knows the consume actually committed. + /// Cleanup paths use instead. + /// + protected virtual async Task RemoveStoredTokenAsync(UserManager manager, IdentityUser user, string tokenName) + { + user.RemoveToken(InternalLoginProvider, tokenName); + (await manager.UpdateAsync(user)).CheckErrors(); + } + + /// + /// Cleanup variant that swallows concurrent-update failures. The next + /// will overwrite whatever remains. + /// + protected virtual async Task TryRemoveStoredTokenAsync(UserManager manager, IdentityUser user, string tokenName) + { + try + { + await RemoveStoredTokenAsync(manager, user, tokenName); + } + catch (AbpIdentityResultException) + { + } + } + + protected virtual string GetTokenName(string purpose) + { + return Name + ":" + purpose; + } + + protected virtual IDataProtector CreateProtector(string purpose) + { + return DataProtectionProvider.CreateProtector(DataProtectionPurposeRoot, Name, purpose); + } + + protected virtual string GenerateNumericCode(int length) + { + if (length is <= 0 or > 9) + { + // Cap at 9 to stay comfortably within Int32 range. + throw new ArgumentOutOfRangeException(nameof(length), length, "Code length must be between 1 and 9."); + } + + var upperBound = (int)Math.Pow(10, length); + var value = RandomNumberGenerator.GetInt32(0, upperBound); + return value.ToString(new string('0', length), CultureInfo.InvariantCulture); + } + + protected virtual long ToUnixSeconds(DateTime moment) + { + // Treat Unspecified as local time to match IClock.Now's default behaviour. + if (moment.Kind == DateTimeKind.Unspecified) + { + moment = DateTime.SpecifyKind(moment, DateTimeKind.Local); + } + + return new DateTimeOffset(moment).ToUnixTimeSeconds(); + } + + private static bool TryParseStoredValue(string stored, out string protectedCode, out long expiresAtUnixSeconds) + { + protectedCode = string.Empty; + expiresAtUnixSeconds = 0; + + var separatorIndex = stored.LastIndexOf(StoredValueSeparator); + if (separatorIndex <= 0 || separatorIndex == stored.Length - 1) + { + return false; + } + + var protectedPart = stored.Substring(0, separatorIndex); + var secondsPart = stored.Substring(separatorIndex + 1); + + if (!long.TryParse(secondsPart, NumberStyles.Integer, CultureInfo.InvariantCulture, out var seconds)) + { + return false; + } + + protectedCode = protectedPart; + expiresAtUnixSeconds = seconds; + return true; + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpTwoFactorTokenProviderOptions.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpTwoFactorTokenProviderOptions.cs new file mode 100644 index 0000000000..973d677091 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpTwoFactorTokenProviderOptions.cs @@ -0,0 +1,12 @@ +using System; + +namespace Volo.Abp.Identity.AspNetCore; + +public abstract class AbpTwoFactorTokenProviderOptions +{ + /// Default: 3 minutes. + public TimeSpan TokenLifespan { get; set; } = TimeSpan.FromMinutes(3); + + /// Default: 6. Valid range: 1 to 9 (inclusive). + public int CodeLength { get; set; } = 6; +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.Server/AbpIdentityBlazorMudBlazorServerModule.cs b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.Server/AbpIdentityBlazorMudBlazorServerModule.cs new file mode 100644 index 0000000000..9abc9222f6 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.Server/AbpIdentityBlazorMudBlazorServerModule.cs @@ -0,0 +1,12 @@ +using Volo.Abp.Modularity; +using Volo.Abp.PermissionManagement.Blazor.MudBlazor.Server; + +namespace Volo.Abp.Identity.Blazor.MudBlazor.Server; + +[DependsOn( + typeof(AbpIdentityBlazorMudBlazorModule), + typeof(AbpPermissionManagementBlazorMudBlazorServerModule) +)] +public class AbpIdentityBlazorMudBlazorServerModule : AbpModule +{ +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.Server/FodyWeavers.xml b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.Server/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.Server/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.Server/FodyWeavers.xsd b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.Server/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.Server/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.Server/Volo.Abp.Identity.Blazor.MudBlazor.Server.csproj b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.Server/Volo.Abp.Identity.Blazor.MudBlazor.Server.csproj new file mode 100644 index 0000000000..544df7438d --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.Server/Volo.Abp.Identity.Blazor.MudBlazor.Server.csproj @@ -0,0 +1,16 @@ + + + + + + + net10.0 + + + + + + + + + diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly/AbpIdentityBlazorMudBlazorWebAssemblyModule.cs b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly/AbpIdentityBlazorMudBlazorWebAssemblyModule.cs new file mode 100644 index 0000000000..7293d59e71 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly/AbpIdentityBlazorMudBlazorWebAssemblyModule.cs @@ -0,0 +1,14 @@ +using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor; +using Volo.Abp.Modularity; +using Volo.Abp.PermissionManagement.Blazor.MudBlazor.WebAssembly; + +namespace Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly; + +[DependsOn( + typeof(AbpIdentityBlazorMudBlazorModule), + typeof(AbpPermissionManagementBlazorMudBlazorWebAssemblyModule), + typeof(AbpIdentityHttpApiClientModule) +)] +public class AbpIdentityBlazorMudBlazorWebAssemblyModule : AbpModule +{ +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly/FodyWeavers.xml b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly/FodyWeavers.xsd b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly/Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly.csproj b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly/Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly.csproj new file mode 100644 index 0000000000..787796d87e --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly/Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly.csproj @@ -0,0 +1,17 @@ + + + + + + + net10.0 + + + + + + + + + + diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/AbpIdentityBlazorMudBlazorMappers.cs b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/AbpIdentityBlazorMudBlazorMappers.cs new file mode 100644 index 0000000000..3edbc1cfa1 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/AbpIdentityBlazorMudBlazorMappers.cs @@ -0,0 +1,25 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace Volo.Abp.Identity.Blazor.MudBlazor; + +[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] +[MapExtraProperties] +public partial class IdentityUserDtoToIdentityUserUpdateDtoMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(IdentityUserUpdateDto.Password))] + [MapperIgnoreTarget(nameof(IdentityUserUpdateDto.RoleNames))] + public override partial IdentityUserUpdateDto Map(IdentityUserDto source); + + [MapperIgnoreTarget(nameof(IdentityUserUpdateDto.Password))] + [MapperIgnoreTarget(nameof(IdentityUserUpdateDto.RoleNames))] + public override partial void Map(IdentityUserDto source, IdentityUserUpdateDto destination); +} + +[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] +[MapExtraProperties] +public partial class IdentityRoleDtoToIdentityRoleUpdateDtoMapper : MapperBase +{ + public override partial IdentityRoleUpdateDto Map(IdentityRoleDto source); + public override partial void Map(IdentityRoleDto source, IdentityRoleUpdateDto destination); +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/AbpIdentityBlazorMudBlazorModule.cs b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/AbpIdentityBlazorMudBlazorModule.cs new file mode 100644 index 0000000000..37227e0573 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/AbpIdentityBlazorMudBlazorModule.cs @@ -0,0 +1,72 @@ +using Localization.Resources.AbpUi; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; +using Volo.Abp.Mapperly; +using Volo.Abp.MudBlazorUI; +using Volo.Abp.Identity.Localization; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.ObjectExtending; +using Volo.Abp.ObjectExtending.Modularity; +using Volo.Abp.PermissionManagement.Blazor.MudBlazor; +using Volo.Abp.Threading; +using Volo.Abp.UI.Navigation; + +namespace Volo.Abp.Identity.Blazor.MudBlazor; + +[DependsOn( + typeof(AbpIdentityApplicationContractsModule), + typeof(AbpMapperlyModule), + typeof(AbpPermissionManagementBlazorMudBlazorModule), + typeof(AbpMudBlazorUIModule) + )] +public class AbpIdentityBlazorMudBlazorModule : AbpModule +{ + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddMapperlyObjectMapper(); + + Configure(options => + { + options.MenuContributors.Add(new AbpIdentityWebMainMenuContributor()); + }); + + Configure(options => + { + options.AdditionalAssemblies.Add(typeof(AbpIdentityBlazorMudBlazorModule).Assembly); + }); + + Configure(options => + { + options.Resources + .Get() + .AddBaseTypes( + typeof(AbpUiResource) + ); + }); + } + + public override void PostConfigureServices(ServiceConfigurationContext context) + { + OneTimeRunner.Run(() => + { + ModuleExtensionConfigurationHelper + .ApplyEntityConfigurationToUi( + IdentityModuleExtensionConsts.ModuleName, + IdentityModuleExtensionConsts.EntityNames.Role, + createFormTypes: new[] { typeof(IdentityRoleCreateDto) }, + editFormTypes: new[] { typeof(IdentityRoleUpdateDto) } + ); + + ModuleExtensionConfigurationHelper + .ApplyEntityConfigurationToUi( + IdentityModuleExtensionConsts.ModuleName, + IdentityModuleExtensionConsts.EntityNames.User, + createFormTypes: new[] { typeof(IdentityUserCreateDto) }, + editFormTypes: new[] { typeof(IdentityUserUpdateDto) } + ); + }); + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/AbpIdentityWebMainMenuContributor.cs b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/AbpIdentityWebMainMenuContributor.cs new file mode 100644 index 0000000000..4b2a2313e0 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/AbpIdentityWebMainMenuContributor.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; +using Volo.Abp.Identity.Localization; +using Volo.Abp.UI.Navigation; +using Volo.Abp.Authorization.Permissions; + +namespace Volo.Abp.Identity.Blazor.MudBlazor; + +public class AbpIdentityWebMainMenuContributor : IMenuContributor +{ + public virtual Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name != StandardMenus.Main) + { + return Task.CompletedTask; + } + + var administrationMenu = context.Menu.GetAdministration(); + + var l = context.GetLocalizer(); + + var identityMenuItem = new ApplicationMenuItem(IdentityMenuNames.GroupName, l["Menu:IdentityManagement"], + icon: "far fa-id-card"); + administrationMenu.AddItem(identityMenuItem); + + identityMenuItem.AddItem(new ApplicationMenuItem( + IdentityMenuNames.Roles, + l["Roles"], + url: "~/identity/roles").RequirePermissions(IdentityPermissions.Roles.Default)); + + identityMenuItem.AddItem(new ApplicationMenuItem( + IdentityMenuNames.Users, + l["Users"], + url: "~/identity/users").RequirePermissions(IdentityPermissions.Users.Default)); + + return Task.CompletedTask; + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/FodyWeavers.xml b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/FodyWeavers.xsd b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/IdentityMenuNames.cs b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/IdentityMenuNames.cs new file mode 100644 index 0000000000..eab8728919 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/IdentityMenuNames.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Identity.Blazor.MudBlazor; + +public class IdentityMenuNames +{ + public const string GroupName = "AbpIdentity"; + + public const string Roles = GroupName + ".Roles"; + public const string Users = GroupName + ".Users"; +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Pages/Identity/RoleManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Pages/Identity/RoleManagement.razor new file mode 100644 index 0000000000..995b1859de --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Pages/Identity/RoleManagement.razor @@ -0,0 +1,87 @@ +@page "/identity/roles" +@page "/{culture}/identity/roles" +@attribute [Authorize(IdentityPermissions.Roles.Default)] +@using global::MudBlazor +@using Volo.Abp.Identity +@using Microsoft.AspNetCore.Authorization +@using Volo.Abp.PermissionManagement.Blazor.MudBlazor.Components +@using Volo.Abp.Identity.Localization +@using Volo.Abp.MudBlazorUI.Components.ObjectExtending +@using Microsoft.Extensions.Localization +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Layout +@using Volo.Abp.UI.Navigation.Localization.Resource +@inject IStringLocalizer LUiNavigation + +@inherits AbpMudCrudPageBase + + + + + + + + + +@* ************************* CREATE DIALOG ************************* *@ +@if (HasCreatePermission) +{ + + + @L["NewRole"] + + + + + + + + + + + @L["Cancel"] + @L["Save"] + + +} + +@* ************************* EDIT DIALOG ************************* *@ +@if (HasUpdatePermission) +{ + + + @L["Edit"] + + + + + + + + + + + + @L["Cancel"] + @L["Save"] + + +} + +@if (HasManagePermissionsPermission) +{ + +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Pages/Identity/RoleManagement.razor.cs b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Pages/Identity/RoleManagement.razor.cs new file mode 100644 index 0000000000..7037b7b570 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Pages/Identity/RoleManagement.razor.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; +using MudBlazor; +using Volo.Abp.AspNetCore.Components.Web.Extensibility.EntityActions; +using Volo.Abp.AspNetCore.Components.Web.Extensibility.TableColumns; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.PageToolbars; +using Volo.Abp.Identity.Localization; +using Volo.Abp.MudBlazorUI; +using Volo.Abp.ObjectExtending; +using Volo.Abp.PermissionManagement.Blazor.MudBlazor.Components; + +namespace Volo.Abp.Identity.Blazor.MudBlazor.Pages.Identity; + +public partial class RoleManagement +{ + [Parameter] + public string? Culture { get; set; } + + protected const string PermissionProviderName = "R"; + + protected PermissionManagementModal? PermissionManagementModal; + + protected string? ManagePermissionsPolicyName; + + protected bool HasManagePermissionsPermission { get; set; } + + protected PageToolbar Toolbar { get; } = new(); + + protected List RoleManagementTableColumns => TableColumns.Get(); + + private AbpMudBlazorMessageLocalizerHelper? _localizerHelper; + + [Inject] + protected IStringLocalizer Localizer { get; set; } = default!; + + public RoleManagement() + { + ObjectMapperContext = typeof(AbpIdentityBlazorMudBlazorModule); + LocalizationResource = typeof(IdentityResource); + + CreatePolicyName = IdentityPermissions.Roles.Create; + UpdatePolicyName = IdentityPermissions.Roles.Update; + DeletePolicyName = IdentityPermissions.Roles.Delete; + ManagePermissionsPolicyName = IdentityPermissions.Roles.ManagePermissions; + } + + protected override async Task OnInitializedAsync() + { + _localizerHelper = new AbpMudBlazorMessageLocalizerHelper(Localizer); + await base.OnInitializedAsync(); + } + + protected override ValueTask SetBreadcrumbItemsAsync() + { + BreadcrumbItems.Add(new BreadcrumbItem(LUiNavigation["Menu:Administration"].Value, null, disabled: true)); + BreadcrumbItems.Add(new BreadcrumbItem(L["Menu:IdentityManagement"].Value, null, disabled: true)); + BreadcrumbItems.Add(new BreadcrumbItem(L["Roles"].Value, null, disabled: true)); + return base.SetBreadcrumbItemsAsync(); + } + + protected override ValueTask SetEntityActionsAsync() + { + EntityActions + .Get() + .AddRange(new EntityAction[] + { + new EntityAction + { + Text = L["Edit"], + Visible = (data) => HasUpdatePermission, + Clicked = async (data) => { await OpenEditDialogAsync(data.As()); } + }, + new EntityAction + { + Text = L["Permissions"], + Visible = (data) => HasManagePermissionsPermission, + Clicked = async (data) => + { + if (PermissionManagementModal != null) + { + await PermissionManagementModal.OpenAsync(PermissionProviderName, + data.As().Name); + } + } + }, + new EntityAction + { + Text = L["Delete"], + Visible = (data) => HasDeletePermission && !data.As().IsStatic, + Clicked = async (data) => await DeleteEntityAsync(data.As()), + ConfirmationMessage = (data) => GetDeleteConfirmationMessage(data.As()) + } + }); + + return base.SetEntityActionsAsync(); + } + + protected override async ValueTask SetTableColumnsAsync() + { + RoleManagementTableColumns + .AddRange(new TableColumn[] + { + new TableColumn + { + Title = L["Actions"], + Actions = EntityActions.Get(), + }, + new TableColumn + { + Title = L["RoleName"], + Sortable = true, + Data = nameof(IdentityRoleDto.Name), + Component = typeof(RoleNameComponent) + }, + }); + + RoleManagementTableColumns.AddRange(await GetExtensionTableColumnsAsync(IdentityModuleExtensionConsts.ModuleName, + IdentityModuleExtensionConsts.EntityNames.Role)); + + await base.SetTableColumnsAsync(); + } + + protected override async Task SetPermissionsAsync() + { + await base.SetPermissionsAsync(); + + HasManagePermissionsPermission = + await AuthorizationService.IsGrantedAsync(IdentityPermissions.Roles.ManagePermissions); + } + + protected override string GetDeleteConfirmationMessage(IdentityRoleDto entity) + { + return string.Format(L["RoleDeletionConfirmationMessage"], entity.Name); + } + + protected override ValueTask SetToolbarItemsAsync() + { + Toolbar.AddMudButton(L["NewRole"], + OpenCreateDialogAsync, + icon: Icons.Material.Filled.Add.ToString(), + requiredPolicyName: CreatePolicyName); + + return base.SetToolbarItemsAsync(); + } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Pages/Identity/RoleNameComponent.razor b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Pages/Identity/RoleNameComponent.razor new file mode 100644 index 0000000000..890671d13c --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Pages/Identity/RoleNameComponent.razor @@ -0,0 +1,16 @@ +@using System; +@using Volo.Abp.Identity +@using Microsoft.Extensions.Localization +@using Volo.Abp.Identity.Localization + +@inject IStringLocalizer L + +@(Data.As().Name) +@if (Data.As().IsDefault) +{ + @L["DisplayName:IsDefault"] +} +@if (Data.As().IsPublic) +{ + @L["DisplayName:IsPublic"] +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Pages/Identity/RoleNameComponent.razor.cs b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Pages/Identity/RoleNameComponent.razor.cs new file mode 100644 index 0000000000..1b209effe0 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Pages/Identity/RoleNameComponent.razor.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Components; + +namespace Volo.Abp.Identity.Blazor.MudBlazor.Pages.Identity; + +public partial class RoleNameComponent : ComponentBase +{ + [Parameter] public object Data { get; set; } = default!; +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Pages/Identity/UserManagement.razor b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Pages/Identity/UserManagement.razor new file mode 100644 index 0000000000..23ac8872c5 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Pages/Identity/UserManagement.razor @@ -0,0 +1,176 @@ +@page "/identity/users" +@page "/{culture}/identity/users" +@attribute [Authorize(IdentityPermissions.Users.Default)] +@using Microsoft.AspNetCore.Authorization +@using Volo.Abp.PermissionManagement.Blazor.MudBlazor.Components +@using Volo.Abp.MudBlazorUI.Components.ObjectExtending +@using Volo.Abp.Identity.Localization +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Components +@using Microsoft.Extensions.Localization +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Layout +@using Volo.Abp.UI.Navigation.Localization.Resource +@inject IStringLocalizer LUiNavigation + +@inherits AbpMudCrudPageBase + + + + + + + + + + + + + + + + +@* ************************* CREATE DIALOG ************************* *@ +@if (HasCreatePermission) +{ + + + @L["NewUser"] + + + + + + + + + + + + + + + + + + + + + + + @if (NewUserRoles != null) + { + @foreach (var role in NewUserRoles) + { + + + } + } + + + + + + @L["Cancel"] + @L["Save"] + + +} + + +@* ************************* EDIT DIALOG ************************* *@ +@if (HasUpdatePermission) +{ + + + @L["Edit"] + + + + + + + + + + + + + @if (!IsEditCurrentUser) + { + + } + + + + + + + + + + @if (EditUserRoles != null && EditUserRoles.Any()) + { + + @foreach (var role in EditUserRoles) + { + + + } + + } + + + + + @L["Cancel"] + @L["Save"] + + +} + +@if (HasManagePermissionsPermission) +{ + +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Pages/Identity/UserManagement.razor.cs b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Pages/Identity/UserManagement.razor.cs new file mode 100644 index 0000000000..f657523cc3 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Pages/Identity/UserManagement.razor.cs @@ -0,0 +1,285 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.Extensions.Localization; +using MudBlazor; +using Volo.Abp.AspNetCore.Components.Web.Extensibility.EntityActions; +using Volo.Abp.AspNetCore.Components.Web.Extensibility.TableColumns; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.PageToolbars; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Identity.Localization; +using Volo.Abp.MudBlazorUI; +using Volo.Abp.ObjectExtending; +using Volo.Abp.PermissionManagement.Blazor.MudBlazor.Components; +using Volo.Abp.Users; + +namespace Volo.Abp.Identity.Blazor.MudBlazor.Pages.Identity; + +public partial class UserManagement +{ + [Parameter] + public string? Culture { get; set; } + + protected const string PermissionProviderName = "U"; + + protected const string DefaultSelectedTab = "UserInformations"; + + protected PermissionManagementModal? PermissionManagementModal; + + protected IReadOnlyList? Roles; + + protected AssignedRoleViewModel[]? NewUserRoles; + + protected AssignedRoleViewModel[]? EditUserRoles; + + protected string? ManagePermissionsPolicyName; + + protected bool HasManagePermissionsPermission { get; set; } + + protected int _createTabIndex = 0; + protected int _editTabIndex = 0; + protected bool _showPassword; + protected bool IsEditCurrentUser; + + protected PageToolbar Toolbar { get; } = new(); + + private List UserManagementTableColumns => TableColumns.Get(); + private AbpMudBlazorMessageLocalizerHelper? _localizerHelper; + + [Inject] + protected IPermissionChecker PermissionChecker { get; set; } = default!; + + [Inject] + protected IStringLocalizer Localizer { get; set; } = default!; + + public UserManagement() + { + ObjectMapperContext = typeof(AbpIdentityBlazorMudBlazorModule); + LocalizationResource = typeof(IdentityResource); + + CreatePolicyName = IdentityPermissions.Users.Create; + UpdatePolicyName = IdentityPermissions.Users.Update; + DeletePolicyName = IdentityPermissions.Users.Delete; + ManagePermissionsPolicyName = IdentityPermissions.Users.ManagePermissions; + } + + protected override async Task OnInitializedAsync() + { + _localizerHelper = new AbpMudBlazorMessageLocalizerHelper(Localizer); + await base.OnInitializedAsync(); + + try + { + Roles = (await AppService.GetAssignableRolesAsync()).Items; + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + protected override ValueTask SetBreadcrumbItemsAsync() + { + BreadcrumbItems.Add(new BreadcrumbItem(LUiNavigation["Menu:Administration"].Value, null, disabled: true)); + BreadcrumbItems.Add(new BreadcrumbItem(L["Menu:IdentityManagement"].Value, null, disabled: true)); + BreadcrumbItems.Add(new BreadcrumbItem(L["Users"].Value, null, disabled: true)); + return base.SetBreadcrumbItemsAsync(); + } + + protected virtual async Task OnSearchKeyUp(KeyboardEventArgs e) + { + if (e.Key == "Enter") + { + CurrentPage = 1; + await _dataGrid.ReloadServerDataAsync(); + } + } + + protected override async Task SetPermissionsAsync() + { + await base.SetPermissionsAsync(); + + HasManagePermissionsPermission = + await AuthorizationService.IsGrantedAsync(IdentityPermissions.Users.ManagePermissions); + } + + protected override async Task OpenCreateDialogAsync() + { + _createTabIndex = 0; + + if (Roles != null) + { + NewUserRoles = Roles.Select(x => new AssignedRoleViewModel + { + Name = x.Name, + IsAssigned = x.IsDefault, + IsAssignable = true + }).ToArray(); + } + + _showPassword = false; + await base.OpenCreateDialogAsync(); + + NewEntity.IsActive = true; + NewEntity.LockoutEnabled = true; + } + + protected override Task OnCreatingEntityAsync() + { + // apply roles before saving + if (NewUserRoles != null) + { + NewEntity.RoleNames = NewUserRoles.Where(x => x.IsAssigned).Select(x => x.Name).ToArray(); + } + + return base.OnCreatingEntityAsync(); + } + + protected override async Task OpenEditDialogAsync(IdentityUserDto entity) + { + try + { + _editTabIndex = 0; + IsEditCurrentUser = entity.Id == CurrentUser.Id; + + if (await PermissionChecker.IsGrantedAsync(IdentityPermissions.Users.ManageRoles)) + { + var assignableRoles = Roles ?? (await AppService.GetAssignableRolesAsync()).Items; + var currentRoles = (await AppService.GetRolesAsync(entity.Id)).Items; + + var combinedRoles = assignableRoles + .Concat(currentRoles) + .GroupBy(role => role.Id) + .Select(group => group.First()) + .ToList(); + + var currentRoleIds = currentRoles.Select(r => r.Id).ToHashSet(); + var assignableRoleIds = assignableRoles.Select(r => r.Id).ToHashSet(); + + EditUserRoles = combinedRoles.Select(x => new AssignedRoleViewModel + { + Name = x.Name, + IsAssigned = currentRoleIds.Contains(x.Id), + IsAssignable = assignableRoleIds.Contains(x.Id) + }).ToArray(); + + _showPassword = false; + } + await base.OpenEditDialogAsync(entity); + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + protected override Task OnUpdatingEntityAsync() + { + // apply roles before saving + if (EditUserRoles != null) + { + EditingEntity.RoleNames = EditUserRoles.Where(x => x.IsAssigned).Select(x => x.Name).ToArray(); + } + return base.OnUpdatingEntityAsync(); + } + + protected override string GetDeleteConfirmationMessage(IdentityUserDto entity) + { + return string.Format(L["UserDeletionConfirmationMessage"], entity.UserName); + } + + protected override ValueTask SetEntityActionsAsync() + { + EntityActions + .Get() + .AddRange(new EntityAction[] + { + new EntityAction + { + Text = L["Edit"], + Visible = (data) => HasUpdatePermission, + Clicked = async (data) => await OpenEditDialogAsync(data.As()) + }, + new EntityAction + { + Text = L["Permissions"], + Visible = (data) => HasManagePermissionsPermission, + Clicked = async (data) => + { + if (PermissionManagementModal != null) + { + await PermissionManagementModal.OpenAsync(PermissionProviderName, + data.As().Id.ToString(), + data.As().UserName); + } + } + }, + new EntityAction + { + Text = L["Delete"], + Visible = (data) => HasDeletePermission && CurrentUser.GetId() != data.As().Id, + Clicked = async (data) => await DeleteEntityAsync(data.As()), + ConfirmationMessage = (data) => GetDeleteConfirmationMessage(data.As()) + } + }); + + return base.SetEntityActionsAsync(); + } + + protected override async ValueTask SetTableColumnsAsync() + { + UserManagementTableColumns + .AddRange(new TableColumn[] + { + new TableColumn + { + Title = L["Actions"], + Actions = EntityActions.Get(), + }, + new TableColumn + { + Title = L["UserName"], + Data = nameof(IdentityUserDto.UserName), + Sortable = true, + }, + new TableColumn + { + Title = L["EmailAddress"], + Data = nameof(IdentityUserDto.Email), + Sortable = true, + }, + new TableColumn + { + Title = L["PhoneNumber"], + Data = nameof(IdentityUserDto.PhoneNumber), + Sortable = true, + } + }); + + UserManagementTableColumns.AddRange(await GetExtensionTableColumnsAsync(IdentityModuleExtensionConsts.ModuleName, + IdentityModuleExtensionConsts.EntityNames.User)); + await base.SetTableColumnsAsync(); + } + + protected override ValueTask SetToolbarItemsAsync() + { + Toolbar.AddMudButton(L["NewUser"], OpenCreateDialogAsync, + icon: Icons.Material.Filled.Add.ToString(), + requiredPolicyName: CreatePolicyName); + + return base.SetToolbarItemsAsync(); + } +} + +public class AssignedRoleViewModel +{ + public string Name { get; set; } = string.Empty; + + public bool IsAssigned { get; set; } + + public bool IsAssignable { get; set; } +} diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Volo.Abp.Identity.Blazor.MudBlazor.csproj b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Volo.Abp.Identity.Blazor.MudBlazor.csproj new file mode 100644 index 0000000000..65d42e0683 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/Volo.Abp.Identity.Blazor.MudBlazor.csproj @@ -0,0 +1,21 @@ + + + + + + + net10.0 + enable + Nullable + + + + + + + + + + + + diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/_Imports.razor b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/_Imports.razor new file mode 100644 index 0000000000..489189a8c9 --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor/_Imports.razor @@ -0,0 +1,6 @@ +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Forms +@using Volo.Abp.AspNetCore.Components.Web +@using Volo.Abp.MudBlazorUI +@using Volo.Abp.MudBlazorUI.Components +@using global::MudBlazor diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityErrorCodes.cs b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityErrorCodes.cs index 4710c62f90..0495679a5f 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityErrorCodes.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/IdentityErrorCodes.cs @@ -11,6 +11,7 @@ public static class IdentityErrorCodes public const string UsersCanNotChangeTwoFactor = "Volo.Abp.Identity:010007"; public const string CanNotChangeTwoFactor = "Volo.Abp.Identity:010008"; public const string YouCannotDelegateYourself = "Volo.Abp.Identity:010009"; + public const string OrganizationUnitParentTenantMismatch = "Volo.Abp.Identity:010010"; public const string ClaimNameExist = "Volo.Abp.Identity:010021"; public const string CanNotUpdateStaticClaimType = "Volo.Abp.Identity:010022"; public const string CanNotDeleteStaticClaimType = "Volo.Abp.Identity:010023"; diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ar.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ar.json index 71b858f834..2d7450ae44 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ar.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ar.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "لا يمكنك تغيير إعداد العاملين.", "Volo.Abp.Identity:010008": "لا يجوز تغيير إعداد عاملين.", "Volo.Abp.Identity:010009": "لا يمكنك تفويض نفسك.", + "Volo.Abp.Identity:010010": "الوحدة التنظيمية الأصل ('{ParentId}') غير موجودة أو تنتمي إلى مستأجر آخر.", "Volo.Abp.Identity:010021": "الاسم {0} موجود بالفعل.", "Volo.Abp.Identity:010022": "لا يمكن تحديث نوع المطابقة.", "Volo.Abp.Identity:010023": "لا يمكن حذف نوع المطابقة.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/cs.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/cs.json index e84320059e..b5613814e1 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/cs.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/cs.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "Nastavení dvou faktorů nemůžete změnit.", "Volo.Abp.Identity:010008": "Není povoleno měnit nastavení dvou faktorů.", "Volo.Abp.Identity:010009": "Nemůžete delegovat své vlastní oprávnění.", + "Volo.Abp.Identity:010010": "Nadřazená organizační jednotka ('{ParentId}') neexistuje nebo patří jinému tenantovi.", "Volo.Abp.Identity:010021": "Název '{0}' již existuje.", "Volo.Abp.Identity:010022": "Nelze aktualizovat statický typ deklarace.", "Volo.Abp.Identity:010023": "Nelze smazat statický typ deklarace.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/de.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/de.json index 4064866f00..8118160f2a 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/de.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/de.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "Sie können Ihre Zwei-Faktor-Einstellung nicht ändern.", "Volo.Abp.Identity:010008": "Die Zwei-Faktor-Einstellung dürfen nicht geändert werden.", "Volo.Abp.Identity:010009": "Sie können sich nicht selbst delegieren.", + "Volo.Abp.Identity:010010": "Die übergeordnete Organisationseinheit ('{ParentId}') existiert nicht oder gehört zu einem anderen Mandanten.", "Volo.Abp.Identity:010021": "Der Name existiert bereits {0}", "Volo.Abp.Identity:010022": "Der Name kann nicht aktualisiert werden.", "Volo.Abp.Identity:010023": "Der Name kann nicht gelöscht werden.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/el.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/el.json index ba610a993a..1a1e10714d 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/el.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/el.json @@ -76,6 +76,7 @@ "Volo.Abp.Identity:010007": "Δεν μπορείτε να αλλάξετε τη ρύθμιση δύο παραγόντων.", "Volo.Abp.Identity:010008": "Δεν επιτρέπεται η αλλαγή της ρύθμισης δύο παραγόντων.", "Volo.Abp.Identity:010009": "Δεν μπορείτε να αναθέσετε την εξουσιοδότησή σας σε εσάς ίδιο.", + "Volo.Abp.Identity:010010": "Η γονική μονάδα οργάνωσης ('{ParentId}') δεν υπάρχει ή ανήκει σε διαφορετικό μισθωτή.", "Volo.Abp.Identity:010021": "Το όνομα '{0}' υπάρχει ήδη.", "Volo.Abp.Identity:010022": "Δεν μπορείτε να ενημερώσετε ένα στατικό τύπο δήλωσης.", "Volo.Abp.Identity:010023": "Δεν μπορείτε να διαγράψετε ένα στατικό τύπο δήλωσης.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en-GB.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en-GB.json index 9f10e1f188..3318b5cf19 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en-GB.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en-GB.json @@ -76,6 +76,7 @@ "Volo.Abp.Identity:010007": "You can't change your two factor setting.", "Volo.Abp.Identity:010008": "Changing the two factor setting is not allowed.", "Volo.Abp.Identity:010009": "You cannot delegate yourself!", + "Volo.Abp.Identity:010010": "The parent organisation unit ('{ParentId}') does not exist, or it belongs to a different tenant.", "Volo.Abp.Identity:010021": "Name exist: '{0}'", "Volo.Abp.Identity:010022": "Can not update a static ClaimType.", "Volo.Abp.Identity:010023": "Can not delete a static ClaimType.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json index 0deb6a4b56..3a6e9e18ca 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/en.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "You can't change your two factor setting.", "Volo.Abp.Identity:010008": "It's not allowed to change two factor setting.", "Volo.Abp.Identity:010009": "You can not delegate yourself.", + "Volo.Abp.Identity:010010": "The parent organization unit ('{ParentId}') does not exist, or it belongs to a different tenant.", "Volo.Abp.Identity:010021": "Name exist: '{0}'.", "Volo.Abp.Identity:010022": "Can not update a static ClaimType.", "Volo.Abp.Identity:010023": "Can not delete a static ClaimType.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/es.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/es.json index a2a9414214..afc75eec4a 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/es.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/es.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "No puedes cambiar tu configuración de autentificación de dos pasos.", "Volo.Abp.Identity:010008": "No está permitido cambiar la configuración de autenticación de dos pasos.", "Volo.Abp.Identity:010009": "No puedes delegar tu propia cuenta!", + "Volo.Abp.Identity:010010": "La unidad organizativa padre ('{ParentId}') no existe o pertenece a otro inquilino.", "Volo.Abp.Identity:010021": "Ya existe un con el nombre '{0}'", "Volo.Abp.Identity:010022": "No se puede actualizar un tipo de reclamación estática.", "Volo.Abp.Identity:010023": "No se puede borrar un tipo de reclamación estática.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fa.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fa.json index e9a1911b52..e6229c5a4d 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fa.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fa.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "شما نمی توانید تنظیمات دو مرحله ای خود را تغییر دهید.", "Volo.Abp.Identity:010008": "تغییر دادن تنظیمات دو مرحله ای مجاز نمی باشد.", "Volo.Abp.Identity:010009": "شما نمی توانید خودتان را معرفی کنید.", + "Volo.Abp.Identity:010010": "واحد سازمانی والد ('{ParentId}') وجود ندارد یا متعلق به مستاجر دیگری است.", "Volo.Abp.Identity:010021": "نام {0} قبلاً استفاده گردیده است.", "Volo.Abp.Identity:010022": "نمی توان نام نقش/وظیفه را تغییر داد.", "Volo.Abp.Identity:010023": "نمی توان نقش/وظیفه را حذف کرد.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fi.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fi.json index 2a37974cbb..2adeb4b309 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fi.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fi.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "Et voi muuttaa kahden tekijän asetusta.", "Volo.Abp.Identity:010008": "Kahden tekijän asetusta ei saa muuttaa.", "Volo.Abp.Identity:010009": "Et voi delegoida itseäsi.", + "Volo.Abp.Identity:010010": "Ylätason organisaatioyksikköä ('{ParentId}') ei ole olemassa tai se kuuluu toiselle vuokralaiselle.", "Volo.Abp.Identity:010021": "{0} on jo olemassa.", "Volo.Abp.Identity:010022": "Staattista väittämätunnusta ei voi päivittää.", "Volo.Abp.Identity:010023": "Staattista väittämätunnusta ei voi poistaa.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fr.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fr.json index 323bb5927a..9028def61b 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fr.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/fr.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "Vous ne pouvez pas modifier votre paramètre à deux facteurs.", "Volo.Abp.Identity:010008": "Il n’est pas permis de changer deux facteurs de réglage.", "Volo.Abp.Identity:010009": "Vous ne pouvez pas vous déléguer vous-même.", + "Volo.Abp.Identity:010010": "L'unité d'organisation parente ('{ParentId}') n'existe pas ou appartient à un autre locataire.", "Volo.Abp.Identity:010021": "Le nom '{0}' existe déjà.", "Volo.Abp.Identity:010022": "Vous ne pouvez pas modifier un type de revendication statique.", "Volo.Abp.Identity:010023": "Vous ne pouvez pas supprimer un type de revendication statique.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hi.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hi.json index 0d5fca3dde..976610c38d 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hi.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hi.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "आप अपनी दो कारक सेटिंग नहीं बदल सकते।", "Volo.Abp.Identity:010008": "इसे दो कारक सेटिंग बदलने की अनुमति नहीं है।", "Volo.Abp.Identity:010009": "आप अपने आप को अधिकृत नहीं कर सकते हैं!", + "Volo.Abp.Identity:010010": "मूल संगठन इकाई ('{ParentId}') मौजूद नहीं है या किसी अन्य किरायेदार की है।", "Volo.Abp.Identity:010021": "नाम '{0}' पहले से ही लिया गया है।", "Volo.Abp.Identity:010022": "स्थैतिक भूमिकाओं का नाम नहीं बदला जा सकता है।", "Volo.Abp.Identity:010023": "स्थैतिक भूमिकाओं को हटाया नहीं जा सकता।", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hr.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hr.json index 0e73aa5f1e..6fa68f1102 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hr.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hr.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "Ne možete promijeniti postavku dva faktora.", "Volo.Abp.Identity:010008": "Nije dopušteno mijenjati postavke dvofaktorske autentifikacije.", "Volo.Abp.Identity:010009": "Ne možete delegirati sami sebi.", + "Volo.Abp.Identity:010010": "Nadređena organizacijska jedinica ('{ParentId}') ne postoji ili pripada drugom najmoprimcu.", "Volo.Abp.Identity:010021": "Naziv '{0}' već postoji.", "Volo.Abp.Identity:010022": "Ne možete ažurirati statički tip deklaracije.", "Volo.Abp.Identity:010023": "Ne možete izbrisati statički tip deklaracije.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hu.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hu.json index 730a831d23..4891880abb 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hu.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/hu.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "Nem változtathatja meg a kétlépcsős bejelentkezés beállítását.", "Volo.Abp.Identity:010008": "Kétlépcsős bejelentkezés beállítás megváltoztatása nem megengedett.", "Volo.Abp.Identity:010009": "Nem adhatja át a saját jogosultságait.", + "Volo.Abp.Identity:010010": "A szülő szervezeti egység ('{ParentId}') nem létezik, vagy egy másik bérlőhöz tartozik.", "Volo.Abp.Identity:010021": "'{0}' nevű már létezik.", "Volo.Abp.Identity:010022": "Nem lehet frissíteni a statikus jogosultság típusát.", "Volo.Abp.Identity:010023": "Nem lehet törölni a statikus jogosultság típusát.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/is.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/is.json index da53a10657..a934b3235e 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/is.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/is.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "Þú getur ekki breytt tveggja þátta stillingu þinni.", "Volo.Abp.Identity:010008": "Það er ekki leyfilegt að breyta tvíþátta stillingu.", "Volo.Abp.Identity:010009": "Þú getur ekki delegað þér sjálfum.", + "Volo.Abp.Identity:010010": "Yfirskipulagseiningin ('{ParentId}') er ekki til eða tilheyrir öðrum leigjanda.", "Volo.Abp.Identity:010021": "Nafnið '{0}' er þegar í notkun.", "Volo.Abp.Identity:010022": "Ekki er hægt að breyta static hlutverk.", "Volo.Abp.Identity:010023": "Ekki er hægt að eyða static hlutverkum.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/it.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/it.json index 163ff98754..869392ac23 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/it.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/it.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "Non puoi modificare l'impostazione dei due fattori.", "Volo.Abp.Identity:010008": "Non è consentito modificare l'impostazione a due fattori.", "Volo.Abp.Identity:010009": "Non puoi delegare te stesso.", + "Volo.Abp.Identity:010010": "L'unità organizzativa padre ('{ParentId}') non esiste o appartiene a un altro tenant.", "Volo.Abp.Identity:010021": "Il nome '{0}' è già in uso.", "Volo.Abp.Identity:010022": "Non puoi modificare il tipo di richiesta statica.", "Volo.Abp.Identity:010023": "Non puoi eliminare il tipo di richiesta statica.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/nl.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/nl.json index 9836137c80..daa3f8b129 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/nl.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/nl.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "U kunt uw instelling voor tweefactor authenticatie niet wijzigen.", "Volo.Abp.Identity:010008": "Het is niet toegestaan om de instelling van tweefactor authenticatie te wijzigen.", "Volo.Abp.Identity:010009": "U kunt uzelf niet delegeren.", + "Volo.Abp.Identity:010010": "De bovenliggende organisatie-eenheid ('{ParentId}') bestaat niet of behoort tot een andere tenant.", "Volo.Abp.Identity:010021": "Er bestaat al een met de naam '{0}'.", "Volo.Abp.Identity:010022": "Kan de naam van een statische claim niet wijzigen.", "Volo.Abp.Identity:010023": "Kan de naam van een statische claim niet verwijderen.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json index 18f33d89c1..0f222bf42c 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pl-PL.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "Nie możesz zmienić ustawienia dwóch czynników.", "Volo.Abp.Identity:010008": "Nie wolno zmieniać ustawienia dwuczynnikowego.", "Volo.Abp.Identity:010009": "Nie możesz delegować swoich uprawnień.", + "Volo.Abp.Identity:010010": "Nadrzędna jednostka organizacyjna ('{ParentId}') nie istnieje lub należy do innego dzierżawcy.", "Volo.Abp.Identity:010021": "Nazwa '{0}' już istnieje.", "Volo.Abp.Identity:010022": "Nie można zmienić typu statycznej deklaracji.", "Volo.Abp.Identity:010023": "Nie można usunąć typu statycznej deklaracji.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pt-BR.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pt-BR.json index cd796edbde..76ab7ea4cb 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pt-BR.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/pt-BR.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "Você não pode alterar sua configuração de dois fatores.", "Volo.Abp.Identity:010008": "Não é permitido alterar a configuração de dois fatores.", "Volo.Abp.Identity:010009": "Você não pode delegar a si mesmo.", + "Volo.Abp.Identity:010010": "A unidade organizacional pai ('{ParentId}') não existe ou pertence a outro inquilino.", "Volo.Abp.Identity:010021": "Já existe um(a) {0} com o nome.", "Volo.Abp.Identity:010022": "Não é possível alterar o tipo de declaração estática.", "Volo.Abp.Identity:010023": "Não é possível excluir o tipo de declaração estática.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ro-RO.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ro-RO.json index 3ebc98d179..f9c624a977 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ro-RO.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ro-RO.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "Nu vă puteţi schimba setările de autentificare în doi paşi.", "Volo.Abp.Identity:010008": "Nu este permisă modificarea setărilor autentificării în doi paşi.", "Volo.Abp.Identity:010009": "Nu vă puteţi delega singur.", + "Volo.Abp.Identity:010010": "Unitatea organizațională părinte ('{ParentId}') nu există sau aparține unui alt chiriaș.", "Volo.Abp.Identity:010021": "Numele '{0}' este deja luat.", "Volo.Abp.Identity:010022": "Nu poate fi schimbată tipul de declaraţie statică.", "Volo.Abp.Identity:010023": "Nu poate fi ştersă declaraţia statică.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ru.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ru.json index f9ddb814d4..5056ad34f2 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ru.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/ru.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "Вы не можете изменить настройку двух факторов.", "Volo.Abp.Identity:010008": "Изменение двухфакторной настройки не допускается.", "Volo.Abp.Identity:010009": "Вы не можете делегировать свои собственные права.", + "Volo.Abp.Identity:010010": "Родительское организационное подразделение ('{ParentId}') не существует или принадлежит другому арендатору.", "Volo.Abp.Identity:010021": "Имя '{0}' уже существует.", "Volo.Abp.Identity:010022": "Невозможно изменить тип статической декларации.", "Volo.Abp.Identity:010023": "Невозможно удалить тип статической декларации.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sk.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sk.json index 26486aa6ab..1609ec94db 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sk.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sk.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "Dvojfaktorové nastavenie nie je možné zmeniť.", "Volo.Abp.Identity:010008": "Dvojfaktorové nastavenie nie je povolené zmeniť.", "Volo.Abp.Identity:010009": "Nemôžete delegovať svoje vlastné oprávnenia.", + "Volo.Abp.Identity:010010": "Nadradená organizačná jednotka ('{ParentId}') neexistuje alebo patrí inému nájomcovi.", "Volo.Abp.Identity:010021": "Názov '{0}' sa už používa.", "Volo.Abp.Identity:010022": "Nie je možné zmeniť typ statického oprávnenia.", "Volo.Abp.Identity:010023": "Nie je možné odstrániť typ statického oprávnenia.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sl.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sl.json index 8ae2a2f81b..98c3b8c4bf 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sl.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sl.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "Nastavitve dveh faktorjev ne morete spremeniti.", "Volo.Abp.Identity:010008": "Ni dovoljeno spreminjati nastavitve dveh faktorjev.", "Volo.Abp.Identity:010009": "Ne morete delegirati svojih pravic.", + "Volo.Abp.Identity:010010": "Nadrejena organizacijska enota ('{ParentId}') ne obstaja ali pripada drugemu najemniku.", "Volo.Abp.Identity:010021": "Naziv '{0}' že obstaja.", "Volo.Abp.Identity:010022": "Ne morete spremeniti tipa statične deklaracije.", "Volo.Abp.Identity:010023": "Ne morete izbrisati tipa statične deklaracije.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sv.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sv.json index aef65e2b11..394c4d0a7e 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sv.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/sv.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "Du kan inte ändra din tvåfaktorsinställning.", "Volo.Abp.Identity:010008": "Det är inte tillåtet att ändra inställningen för två faktorer.", "Volo.Abp.Identity:010009": "Du kan inte delegera dig själv.", + "Volo.Abp.Identity:010010": "Den överordnade organisationsenheten ('{ParentId}') finns inte eller tillhör en annan klient.", "Volo.Abp.Identity:010021": "Namn finns: '{0}'.", "Volo.Abp.Identity:010022": "Kan inte ändra statiskt område.", "Volo.Abp.Identity:010023": "Kan inte ta bort statiskt område.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json index 0a6e4d393c..8e8d678f0a 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/tr.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "İki faktörlü ayarınızı değiştiremezsiniz.", "Volo.Abp.Identity:010008": "İki faktörlü ayarın değiştirilmesine izin verilmez.", "Volo.Abp.Identity:010009": "Kendinizi yetkilendiremezsiniz.", + "Volo.Abp.Identity:010010": "Üst birim ('{ParentId}') mevcut değil veya farklı bir kiracıya ait.", "Volo.Abp.Identity:010021": "{0} isminde bir birim zaten var.", "Volo.Abp.Identity:010022": "Bir statik izin tipi değiştirilemez.", "Volo.Abp.Identity:010023": "Bir statik izin tipi silinemez.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/vi.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/vi.json index 32c1dfe008..46f26b0040 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/vi.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/vi.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "Bạn không thể thay đổi cài đặt hai yếu tố của mình.", "Volo.Abp.Identity:010008": "Không được phép thay đổi cài đặt hai yếu tố.", "Volo.Abp.Identity:010009": "Bạn không thể ủy quyền cho chính mình.", + "Volo.Abp.Identity:010010": "Đơn vị tổ chức cha ('{ParentId}') không tồn tại hoặc thuộc về một thuê khác.", "Volo.Abp.Identity:010021": "Tên '{0}' đã tồn tại.", "Volo.Abp.Identity:010022": "Không thể thay đổi kiểu của phát biểu tĩnh.", "Volo.Abp.Identity:010023": "Không thể xóa phát biểu tĩnh.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hans.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hans.json index 3a1239ddc7..34045a010b 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hans.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hans.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "你不能修改你的双因素身份验证设置", "Volo.Abp.Identity:010008": "不允许修改双因素身份验证设置。", "Volo.Abp.Identity:010009": "你不能委托给自己。", + "Volo.Abp.Identity:010010": "父组织单元 ('{ParentId}') 不存在或属于其他租户.", "Volo.Abp.Identity:010021": "名称:'{0}' 已存在", "Volo.Abp.Identity:010022": "不能更改静态声明的类型。", "Volo.Abp.Identity:010023": "不能删除静态声明的类型。", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hant.json b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hant.json index a28cc6c7d4..c54f18c4b2 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hant.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain.Shared/Volo/Abp/Identity/Localization/zh-Hant.json @@ -77,6 +77,7 @@ "Volo.Abp.Identity:010007": "你不能修改你的雙因素身份驗證設置", "Volo.Abp.Identity:010008": "不允許修改雙因素身份驗證設置.", "Volo.Abp.Identity:010009": "你不能委派給自己", + "Volo.Abp.Identity:010010": "父組織單元 ('{ParentId}') 不存在或屬於其他租戶.", "Volo.Abp.Identity:010021": "名稱 '{0}' 已存在.", "Volo.Abp.Identity:010022": "無法更改靜態宣告的類型.", "Volo.Abp.Identity:010023": "無法刪除靜態宣告的類型.", diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.abppkg.analyze.json b/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.abppkg.analyze.json index 05f8f7d4c0..5a3345f207 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.abppkg.analyze.json +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo.Abp.Identity.Domain.abppkg.analyze.json @@ -3677,6 +3677,23 @@ "isOptional": false } ] + }, + { + "returnType": "IdentityUser", + "namespace": "Volo.Abp.Identity", + "name": "FindSharedUserByIdAsync", + "summary": null, + "isAsync": true, + "isPublic": true, + "isPrivate": false, + "isStatic": false, + "parameters": [ + { + "type": "String", + "name": "userId", + "isOptional": false + } + ] } ], "implementingInterfaces": [ diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs index c2f72366fb..46f7a635f0 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/IdentityUserManager.cs @@ -718,4 +718,31 @@ public class IdentityUserManager : UserManager, IDomainService } } + public virtual async Task FindSharedUserByIdAsync(string userId) + { + if (MultiTenancyOptions.Value.UserSharingStrategy == TenantUserSharingStrategy.Isolated) + { + return await base.FindByIdAsync(userId); + } + + using (CurrentTenant.Change(null)) + { + using (DataFilter.Disable()) + { + var user = await base.FindByIdAsync(userId); + if (user == null) + { + return null; + } + + using (DataFilter.Enable()) + { + using (CurrentTenant.Change(user.TenantId)) + { + return await base.FindByIdAsync(userId); + } + } + } + } + } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs index 160e37c900..cb8fa17a77 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/OrganizationUnitManager.cs @@ -41,6 +41,8 @@ public class OrganizationUnitManager : DomainService [UnitOfWork] public virtual async Task CreateAsync(OrganizationUnit organizationUnit) { + await ValidateParentTenantAsync(organizationUnit.ParentId, organizationUnit.TenantId); + organizationUnit.Code = await GetNextChildCodeAsync(organizationUnit.ParentId); await ValidateOrganizationUnitAsync(organizationUnit); await OrganizationUnitRepository.InsertAsync(organizationUnit); @@ -107,6 +109,8 @@ public class OrganizationUnitManager : DomainService return; } + await ValidateParentTenantAsync(parentId, organizationUnit.TenantId); + //Should find children before Code change var children = await FindChildrenAsync(id, true); @@ -148,6 +152,21 @@ public class OrganizationUnitManager : DomainService } } + protected virtual async Task ValidateParentTenantAsync(Guid? parentId, Guid? tenantId) + { + if (!parentId.HasValue) + { + return; + } + + var parent = await OrganizationUnitRepository.FindAsync(parentId.Value); + if (parent == null || parent.TenantId != tenantId) + { + throw new BusinessException(IdentityErrorCodes.OrganizationUnitParentTenantMismatch) + .WithData("ParentId", parentId); + } + } + public virtual async Task> FindChildrenAsync(Guid? parentId, bool recursive = false) { if (!recursive) diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpEmailTwoFactorTokenProvider_Tests.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpEmailTwoFactorTokenProvider_Tests.cs new file mode 100644 index 0000000000..a967bdcd5b --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpEmailTwoFactorTokenProvider_Tests.cs @@ -0,0 +1,236 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Shouldly; +using Volo.Abp.Threading; +using Volo.Abp.Timing; +using Volo.Abp.Uow; +using Xunit; + +namespace Volo.Abp.Identity.AspNetCore; + +public class AbpEmailTwoFactorTokenProvider_Tests : AbpTwoFactorTokenProviderTestBase +{ + protected override string GetTokenProviderName() => TokenOptions.DefaultEmailProvider; + + protected override string GetInternalProviderName() => AbpEmailTwoFactorTokenProvider.ProviderName; + + [Fact] + public void AbpEmailTwoFactorTokenProvider_Should_Replace_Default_Email_Provider() + { + var identityOptions = GetRequiredService>().Value; + + identityOptions.Tokens.ProviderMap.ShouldContainKey(TokenOptions.DefaultEmailProvider); + identityOptions.Tokens.ProviderMap[TokenOptions.DefaultEmailProvider].ProviderType + .ShouldBe(typeof(AbpEmailTwoFactorTokenProvider)); + } + + [Fact] + public void Default_Options_Should_Match_Documented_Defaults() + { + var options = GetRequiredService>().Value; + + options.CodeLength.ShouldBe(6); + options.TokenLifespan.ShouldBe(TimeSpan.FromMinutes(3)); + } + + [Fact] + public async Task CanGenerateTwoFactorTokenAsync_Should_Require_Confirmed_Email() + { + using var uow = UnitOfWorkManager.Begin(); + + var provider = GetRequiredService(); + var john = await UserRepository.GetAsync(TestData.UserJohnId); + + // john.EmailConfirmed defaults to false in the test seed. + (await provider.CanGenerateTwoFactorTokenAsync(UserManager, john)).ShouldBeFalse(); + + john.SetEmailConfirmed(true); + (await UserManager.UpdateAsync(john)).Succeeded.ShouldBeTrue(); + + john = await UserRepository.GetAsync(TestData.UserJohnId); + (await provider.CanGenerateTwoFactorTokenAsync(UserManager, john)).ShouldBeTrue(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Email_And_Phone_Tokens_For_Same_User_Should_Be_Independent() + { + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var emailCode = await UserManager.GenerateUserTokenAsync(user, TokenOptions.DefaultEmailProvider, TwoFactorPurpose); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + var phoneCode = await UserManager.GenerateUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, TwoFactorPurpose); + + // Consuming the email code must not invalidate the phone code. + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.VerifyUserTokenAsync(user, TokenOptions.DefaultEmailProvider, TwoFactorPurpose, emailCode)) + .ShouldBeTrue(); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.VerifyUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, TwoFactorPurpose, phoneCode)) + .ShouldBeTrue(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Email_Code_Should_Survive_Security_Stamp_Change() + { + // Unlike the default DataProtector-backed token providers, this 2FA provider does + // not bind the code to the user's SecurityStamp, so rotating the stamp (e.g. a + // concurrent password change) must NOT invalidate an outstanding 2FA code. + // This keeps the login flow resilient to legitimate state changes happening + // between the credential step and the verification step. + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var code = await GenerateTokenAsync(user); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.UpdateSecurityStampAsync(user)).Succeeded.ShouldBeTrue(); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, code)).ShouldBeTrue(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Custom_CodeLength_Should_Produce_Code_Of_That_Length() + { + using var uow = UnitOfWorkManager.Begin(); + + var customProvider = new AbpEmailTwoFactorTokenProvider( + Microsoft.Extensions.Options.Options.Create(new AbpEmailTwoFactorTokenProviderOptions { CodeLength = 8 }), + UserRepository, + GetRequiredService(), + Clock, + GetRequiredService()); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var code = await customProvider.GenerateAsync(TwoFactorPurpose, UserManager, user); + + code.Length.ShouldBe(8); + code.ShouldMatch(@"^\d{8}$"); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await customProvider.ValidateAsync(TwoFactorPurpose, code, UserManager, user)).ShouldBeTrue(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Custom_TokenLifespan_Should_Be_Honored() + { + using var uow = UnitOfWorkManager.Begin(); + + var customLifespan = TimeSpan.FromMinutes(30); + + var customProvider = new AbpEmailTwoFactorTokenProvider( + Microsoft.Extensions.Options.Options.Create(new AbpEmailTwoFactorTokenProviderOptions { TokenLifespan = customLifespan }), + UserRepository, + GetRequiredService(), + Clock, + GetRequiredService()); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var beforeGenerate = new DateTimeOffset(Clock.Now.Kind == DateTimeKind.Unspecified + ? DateTime.SpecifyKind(Clock.Now, DateTimeKind.Local) + : Clock.Now).ToUnixTimeSeconds(); + await customProvider.GenerateAsync(TwoFactorPurpose, UserManager, user); + var afterGenerate = new DateTimeOffset(Clock.Now.Kind == DateTimeKind.Unspecified + ? DateTime.SpecifyKind(Clock.Now, DateTimeKind.Local) + : Clock.Now).ToUnixTimeSeconds(); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + var stored = await UserManager.GetAuthenticationTokenAsync( + user, + AbpTwoFactorTokenProvider.InternalLoginProvider, + AbpEmailTwoFactorTokenProvider.ProviderName + ":" + TwoFactorPurpose); + stored.ShouldNotBeNull(); + + var separator = stored.LastIndexOf('|'); + separator.ShouldBeGreaterThan(0); + var secondsPart = stored.Substring(separator + 1); + long.TryParse(secondsPart, NumberStyles.Integer, CultureInfo.InvariantCulture, out var storedExpiresUnix) + .ShouldBeTrue(); + + storedExpiresUnix.ShouldBeGreaterThanOrEqualTo(beforeGenerate + (long)customLifespan.TotalSeconds); + storedExpiresUnix.ShouldBeLessThanOrEqualTo(afterGenerate + (long)customLifespan.TotalSeconds); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Direct_Provider_Generate_And_Validate_Should_Round_Trip() + { + using var uow = UnitOfWorkManager.Begin(); + + var provider = GetRequiredService(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var code = await provider.GenerateAsync(TwoFactorPurpose, UserManager, user); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await provider.ValidateAsync(TwoFactorPurpose, code, UserManager, user)).ShouldBeTrue(); + + // Consumed by the previous successful validation. + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await provider.ValidateAsync(TwoFactorPurpose, code, UserManager, user)).ShouldBeFalse(); + + await uow.CompleteAsync(); + } + + [Fact] + public void Provider_Name_Should_Be_The_Published_Constant() + { + var provider = GetRequiredService(); + + provider.Name.ShouldBe(AbpEmailTwoFactorTokenProvider.ProviderName); + AbpEmailTwoFactorTokenProvider.ProviderName.ShouldBe("AbpEmailTwoFactor"); + } + + [Fact] + public async Task Unprotectable_Stored_Payload_From_Wrong_Protector_Should_Fail_And_Cleanup() + { + // Protect a code under a DIFFERENT DataProtection purpose chain to simulate data + // carried over from another module/provider. ValidateAsync must refuse it (Unprotect + // throws) and clean up the stored entry. + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var code = await GenerateTokenAsync(user); + var tokenName = GetTokenName(TwoFactorPurpose); + + var dpp = GetRequiredService(); + var wrongProtector = dpp.CreateProtector("some-unrelated-purpose"); + var wrongPayload = wrongProtector.Protect("123456"); + + var futureSeconds = new DateTimeOffset(Clock.Now.Kind == DateTimeKind.Unspecified + ? DateTime.SpecifyKind(Clock.Now.AddMinutes(1), DateTimeKind.Local) + : Clock.Now.AddMinutes(1)) + .ToUnixTimeSeconds().ToString(CultureInfo.InvariantCulture); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.SetAuthenticationTokenAsync( + user, AbpTwoFactorTokenProvider.InternalLoginProvider, tokenName, wrongPayload + "|" + futureSeconds)) + .Succeeded.ShouldBeTrue(); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, code)).ShouldBeFalse(); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.GetAuthenticationTokenAsync( + user, AbpTwoFactorTokenProvider.InternalLoginProvider, tokenName)) + .ShouldBeNull(); + + await uow.CompleteAsync(); + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityUserValidator_Tests.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityUserValidator_Tests.cs index 752bc3496a..b1410b2068 100644 --- a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityUserValidator_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpIdentityUserValidator_Tests.cs @@ -190,4 +190,90 @@ public class AbpIdentityUserValidator_SharedUser_Tests : AbpIdentityAspNetCoreTe result.Errors.First().Code.ShouldBe("InvalidEmail"); } } + + [Fact] + public async Task Should_Allow_Update_Without_UserName_Or_Email_Changes() + { + var tenantId = Guid.NewGuid(); + + using (_currentTenant.Change(tenantId)) + { + var user = new IdentityUser(Guid.NewGuid(), "unchanged-user", "unchanged@volosoft.com") { Name = "Original" }; + (await _identityUserManager.CreateAsync(user)).Succeeded.ShouldBeTrue(); + + user.Name = "Changed"; + (await _identityUserManager.UpdateAsync(user)).Succeeded.ShouldBeTrue(); + } + } + + [Fact] + public async Task Should_Allow_Update_Changing_UserName_To_A_Globally_Unique_Value() + { + var tenantId = Guid.NewGuid(); + + using (_currentTenant.Change(tenantId)) + { + var user = new IdentityUser(Guid.NewGuid(), "rename-start", "rename@volosoft.com"); + (await _identityUserManager.CreateAsync(user)).Succeeded.ShouldBeTrue(); + + var result = await _identityUserManager.SetUserNameAsync(user, "rename-end"); + result.Succeeded.ShouldBeTrue(); + } + } + + [Fact] + public async Task Should_Allow_Update_Changing_Email_To_A_Globally_Unique_Value() + { + var tenantId = Guid.NewGuid(); + + using (_currentTenant.Change(tenantId)) + { + var user = new IdentityUser(Guid.NewGuid(), "email-change", "email-before@volosoft.com"); + (await _identityUserManager.CreateAsync(user)).Succeeded.ShouldBeTrue(); + + var result = await _identityUserManager.SetEmailAsync(user, "email-after@volosoft.com"); + result.Succeeded.ShouldBeTrue(); + } + } + + // Host-user scenarios (TenantId == null): still must enforce global uniqueness on Create. + [Fact] + public async Task Should_Reject_Duplicate_UserName_Between_Host_User_And_Tenant_User() + { + var tenantId = Guid.NewGuid(); + const string sharedName = "host-vs-tenant-name"; + + // Host user first. + var hostUser = new IdentityUser(Guid.NewGuid(), sharedName, "host-side@volosoft.com"); + (await _identityUserManager.CreateAsync(hostUser)).Succeeded.ShouldBeTrue(); + + using (_currentTenant.Change(tenantId)) + { + var tenantUser = new IdentityUser(Guid.NewGuid(), sharedName, "tenant-side@volosoft.com"); + var result = await _identityUserManager.CreateAsync(tenantUser); + + result.Succeeded.ShouldBeFalse(); + result.Errors.Any(e => e.Code == "DuplicateUserName").ShouldBeTrue(); + } + } + + [Fact] + public async Task Should_Reject_Duplicate_Email_Between_Host_User_And_Tenant_User() + { + var tenantId = Guid.NewGuid(); + const string sharedEmail = "host-vs-tenant-email@volosoft.com"; + + var hostUser = new IdentityUser(Guid.NewGuid(), "host-email-user", sharedEmail); + (await _identityUserManager.CreateAsync(hostUser)).Succeeded.ShouldBeTrue(); + + using (_currentTenant.Change(tenantId)) + { + var tenantUser = new IdentityUser(Guid.NewGuid(), "tenant-email-user", sharedEmail); + var result = await _identityUserManager.CreateAsync(tenantUser); + + result.Succeeded.ShouldBeFalse(); + result.Errors.Any(e => e.Code == "DuplicateEmail").ShouldBeTrue(); + } + } + } diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpPhoneNumberTwoFactorTokenProvider_Tests.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpPhoneNumberTwoFactorTokenProvider_Tests.cs new file mode 100644 index 0000000000..c56c31ffd6 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpPhoneNumberTwoFactorTokenProvider_Tests.cs @@ -0,0 +1,223 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Shouldly; +using Volo.Abp.Threading; +using Volo.Abp.Timing; +using Volo.Abp.Uow; +using Xunit; + +namespace Volo.Abp.Identity.AspNetCore; + +public class AbpPhoneNumberTwoFactorTokenProvider_Tests : AbpTwoFactorTokenProviderTestBase +{ + protected override string GetTokenProviderName() => TokenOptions.DefaultPhoneProvider; + + protected override string GetInternalProviderName() => AbpPhoneNumberTwoFactorTokenProvider.ProviderName; + + [Fact] + public void AbpPhoneNumberTwoFactorTokenProvider_Should_Replace_Default_Phone_Provider() + { + var identityOptions = GetRequiredService>().Value; + + identityOptions.Tokens.ProviderMap.ShouldContainKey(TokenOptions.DefaultPhoneProvider); + identityOptions.Tokens.ProviderMap[TokenOptions.DefaultPhoneProvider].ProviderType + .ShouldBe(typeof(AbpPhoneNumberTwoFactorTokenProvider)); + } + + [Fact] + public void Default_Options_Should_Match_Documented_Defaults() + { + var options = GetRequiredService>().Value; + + options.CodeLength.ShouldBe(6); + options.TokenLifespan.ShouldBe(TimeSpan.FromMinutes(3)); + } + + [Fact] + public async Task CanGenerateTwoFactorTokenAsync_Should_Require_Confirmed_Phone_Number() + { + using var uow = UnitOfWorkManager.Begin(); + + var provider = GetRequiredService(); + var john = await UserRepository.GetAsync(TestData.UserJohnId); + + // No phone number set by the seed. + (await provider.CanGenerateTwoFactorTokenAsync(UserManager, john)).ShouldBeFalse(); + + (await UserManager.SetPhoneNumberAsync(john, "+1-555-0100")).Succeeded.ShouldBeTrue(); + + john = await UserRepository.GetAsync(TestData.UserJohnId); + // Phone present but not confirmed yet. + (await provider.CanGenerateTwoFactorTokenAsync(UserManager, john)).ShouldBeFalse(); + + john.SetPhoneNumberConfirmed(true); + (await UserManager.UpdateAsync(john)).Succeeded.ShouldBeTrue(); + + john = await UserRepository.GetAsync(TestData.UserJohnId); + (await provider.CanGenerateTwoFactorTokenAsync(UserManager, john)).ShouldBeTrue(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task ChangePhoneNumber_Token_Should_Be_Single_Use() + { + // IdentityOptions.Tokens.ChangePhoneNumberTokenProvider defaults to the same + // "Phone" provider name, so GenerateChangePhoneNumberTokenAsync now routes + // through AbpPhoneNumberTwoFactorTokenProvider and inherits single-use semantics. + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + const string newPhone = "+1-555-0199"; + var token = await UserManager.GenerateChangePhoneNumberTokenAsync(user, newPhone); + + token.ShouldNotBeNullOrEmpty(); + token.Length.ShouldBe(6); + token.ShouldMatch(@"^\d{6}$"); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.VerifyChangePhoneNumberTokenAsync(user, token, newPhone)).ShouldBeTrue(); + + // Single-use: second verification must fail. + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.VerifyChangePhoneNumberTokenAsync(user, token, newPhone)).ShouldBeFalse(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task ChangePhoneNumber_Token_Should_Be_Bound_To_The_Target_Phone_Number() + { + // Purpose is "ChangePhoneNumber:{phoneNumber}" so a token issued for one + // target phone number must not validate against a different one. + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var token = await UserManager.GenerateChangePhoneNumberTokenAsync(user, "+1-555-0111"); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.VerifyChangePhoneNumberTokenAsync(user, token, "+1-555-0222")).ShouldBeFalse(); + + // Original target still works. + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.VerifyChangePhoneNumberTokenAsync(user, token, "+1-555-0111")).ShouldBeTrue(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Phone_Code_Should_Survive_Security_Stamp_Change() + { + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var code = await GenerateTokenAsync(user); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.UpdateSecurityStampAsync(user)).Succeeded.ShouldBeTrue(); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, code)).ShouldBeTrue(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Custom_CodeLength_Should_Produce_Code_Of_That_Length() + { + using var uow = UnitOfWorkManager.Begin(); + + var customProvider = new AbpPhoneNumberTwoFactorTokenProvider( + Microsoft.Extensions.Options.Options.Create(new AbpPhoneNumberTwoFactorTokenProviderOptions { CodeLength = 4 }), + UserRepository, + GetRequiredService(), + Clock, + GetRequiredService()); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var code = await customProvider.GenerateAsync(TwoFactorPurpose, UserManager, user); + + code.Length.ShouldBe(4); + code.ShouldMatch(@"^\d{4}$"); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await customProvider.ValidateAsync(TwoFactorPurpose, code, UserManager, user)).ShouldBeTrue(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Custom_TokenLifespan_Should_Be_Honored() + { + using var uow = UnitOfWorkManager.Begin(); + + var customLifespan = TimeSpan.FromMinutes(15); + + var customProvider = new AbpPhoneNumberTwoFactorTokenProvider( + Microsoft.Extensions.Options.Options.Create(new AbpPhoneNumberTwoFactorTokenProviderOptions { TokenLifespan = customLifespan }), + UserRepository, + GetRequiredService(), + Clock, + GetRequiredService()); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var beforeGenerate = new DateTimeOffset(Clock.Now.Kind == DateTimeKind.Unspecified + ? DateTime.SpecifyKind(Clock.Now, DateTimeKind.Local) + : Clock.Now).ToUnixTimeSeconds(); + await customProvider.GenerateAsync(TwoFactorPurpose, UserManager, user); + var afterGenerate = new DateTimeOffset(Clock.Now.Kind == DateTimeKind.Unspecified + ? DateTime.SpecifyKind(Clock.Now, DateTimeKind.Local) + : Clock.Now).ToUnixTimeSeconds(); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + var stored = await UserManager.GetAuthenticationTokenAsync( + user, + AbpTwoFactorTokenProvider.InternalLoginProvider, + AbpPhoneNumberTwoFactorTokenProvider.ProviderName + ":" + TwoFactorPurpose); + stored.ShouldNotBeNull(); + + var separator = stored.LastIndexOf('|'); + separator.ShouldBeGreaterThan(0); + var secondsPart = stored.Substring(separator + 1); + long.TryParse(secondsPart, NumberStyles.Integer, CultureInfo.InvariantCulture, out var storedExpiresUnix) + .ShouldBeTrue(); + + storedExpiresUnix.ShouldBeGreaterThanOrEqualTo(beforeGenerate + (long)customLifespan.TotalSeconds); + storedExpiresUnix.ShouldBeLessThanOrEqualTo(afterGenerate + (long)customLifespan.TotalSeconds); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Direct_Provider_Generate_And_Validate_Should_Round_Trip() + { + using var uow = UnitOfWorkManager.Begin(); + + var provider = GetRequiredService(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var code = await provider.GenerateAsync(TwoFactorPurpose, UserManager, user); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await provider.ValidateAsync(TwoFactorPurpose, code, UserManager, user)).ShouldBeTrue(); + + // Consumed by the previous successful validation. + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await provider.ValidateAsync(TwoFactorPurpose, code, UserManager, user)).ShouldBeFalse(); + + await uow.CompleteAsync(); + } + + [Fact] + public void Provider_Name_Should_Be_The_Published_Constant() + { + var provider = GetRequiredService(); + + provider.Name.ShouldBe(AbpPhoneNumberTwoFactorTokenProvider.ProviderName); + AbpPhoneNumberTwoFactorTokenProvider.ProviderName.ShouldBe("AbpPhoneNumberTwoFactor"); + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpTwoFactorTokenProviderTestBase.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpTwoFactorTokenProviderTestBase.cs new file mode 100644 index 0000000000..6f72dee980 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/AbpTwoFactorTokenProviderTestBase.cs @@ -0,0 +1,612 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Shouldly; +using Volo.Abp.Timing; +using Volo.Abp.Uow; +using Xunit; + +namespace Volo.Abp.Identity.AspNetCore; + +/// +/// Abstract base class that exercises the common behaviour of every +/// subclass. Concrete subclasses wire up +/// the Identity API calls that route to the provider under test. +/// +public abstract class AbpTwoFactorTokenProviderTestBase : AbpIdentityAspNetCoreTestBase +{ + protected const string TwoFactorPurpose = "TwoFactor"; + protected const string OtherPurpose = "SomeOtherPurpose"; + + protected IIdentityUserRepository UserRepository { get; } + protected IdentityUserManager UserManager { get; } + protected IdentityTestData TestData { get; } + protected IUnitOfWorkManager UnitOfWorkManager { get; } + protected IClock Clock { get; } + + protected AbpTwoFactorTokenProviderTestBase() + { + UserRepository = GetRequiredService(); + UserManager = GetRequiredService(); + TestData = GetRequiredService(); + UnitOfWorkManager = GetRequiredService(); + Clock = GetRequiredService(); + } + + /// Identity provider name (e.g. "Email", "Phone") routed to the provider under test. + protected abstract string GetTokenProviderName(); + + /// Internal used as the stored-token key prefix. + protected abstract string GetInternalProviderName(); + + protected virtual Task GenerateTokenAsync(IdentityUser user, string purpose = TwoFactorPurpose) + => UserManager.GenerateUserTokenAsync(user, GetTokenProviderName(), purpose); + + protected virtual Task VerifyTokenAsync(IdentityUser user, string token, string purpose = TwoFactorPurpose) + => UserManager.VerifyUserTokenAsync(user, GetTokenProviderName(), purpose, token); + + protected string GetTokenName(string purpose) => GetInternalProviderName() + ":" + purpose; + + /// Deterministically produce a code that is guaranteed not to equal . + private static string DifferentCode(string code) + { + if (string.IsNullOrEmpty(code)) + { + return "000000"; + } + + var firstChar = code[0]; + var replacement = firstChar == '9' ? '0' : (char)(firstChar + 1); + return replacement + code.Substring(1); + } + + private long ToUnixSeconds(DateTime moment) + { + if (moment.Kind == DateTimeKind.Unspecified) + { + moment = DateTime.SpecifyKind(moment, DateTimeKind.Local); + } + + return new DateTimeOffset(moment).ToUnixTimeSeconds(); + } + + [Fact] + public async Task Generate_Should_Produce_Six_Digit_Numeric_Code() + { + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var code = await GenerateTokenAsync(user); + + code.ShouldNotBeNullOrEmpty(); + code.Length.ShouldBe(6); + code.ShouldMatch(@"^\d{6}$"); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Generate_And_Verify_Token_Should_Succeed() + { + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var code = await GenerateTokenAsync(user); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, code)).ShouldBeTrue(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Invalid_Token_Should_Fail_Verification() + { + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var code = await GenerateTokenAsync(user); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, DifferentCode(code))).ShouldBeFalse(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Wrong_Code_Should_Not_Consume_Stored_Entry() + { + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var code = await GenerateTokenAsync(user); + + // A wrong attempt must leave the stored entry intact so the user can retry. + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, DifferentCode(code))).ShouldBeFalse(); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, code)).ShouldBeTrue(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Successful_Verification_Should_Consume_The_Code() + { + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var code = await GenerateTokenAsync(user); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, code)).ShouldBeTrue(); + + // Single-use: the same code must not validate twice. + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, code)).ShouldBeFalse(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Second_Token_Generation_Should_Invalidate_First_Token() + { + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var firstCode = await GenerateTokenAsync(user); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + var secondCode = await GenerateTokenAsync(user); + + // Even if first/second codes happen to collide numerically, the first was + // generated against a now-overwritten stored entry, so verifying the FIRST + // code against the NEW stored entry is what counts. We only assert the + // old token no longer validates and the new one does. + user = await UserRepository.GetAsync(TestData.UserJohnId); + if (firstCode != secondCode) + { + (await VerifyTokenAsync(user, firstCode)).ShouldBeFalse(); + user = await UserRepository.GetAsync(TestData.UserJohnId); + } + + (await VerifyTokenAsync(user, secondCode)).ShouldBeTrue(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Expired_Token_Should_Fail_And_Be_Cleaned_Up() + { + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var code = await GenerateTokenAsync(user); + + // Keep the protected payload but rewrite the expiration into the past. + user = await UserRepository.GetAsync(TestData.UserJohnId); + var tokenName = GetTokenName(TwoFactorPurpose); + var stored = await UserManager.GetAuthenticationTokenAsync( + user, AbpTwoFactorTokenProvider.InternalLoginProvider, tokenName); + stored.ShouldNotBeNull(); + + var separator = stored.LastIndexOf('|'); + separator.ShouldBeGreaterThan(0); + var protectedPart = stored.Substring(0, separator); + var expiredValue = protectedPart + "|" + + ToUnixSeconds(Clock.Now.AddMinutes(-1)).ToString(CultureInfo.InvariantCulture); + + (await UserManager.SetAuthenticationTokenAsync( + user, AbpTwoFactorTokenProvider.InternalLoginProvider, tokenName, expiredValue)) + .Succeeded.ShouldBeTrue(); + + // Verification must fail and also wipe the expired entry. + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, code)).ShouldBeFalse(); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + var remaining = await UserManager.GetAuthenticationTokenAsync( + user, AbpTwoFactorTokenProvider.InternalLoginProvider, tokenName); + remaining.ShouldBeNull(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Corrupted_Stored_Value_Should_Return_False_Instead_Of_Throwing() + { + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var code = await GenerateTokenAsync(user); + + var tokenName = GetTokenName(TwoFactorPurpose); + + // Overwrite with a malformed value (no separator, garbage chars). + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.SetAuthenticationTokenAsync( + user, AbpTwoFactorTokenProvider.InternalLoginProvider, tokenName, "not-a-valid-stored-value")) + .Succeeded.ShouldBeTrue(); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, code)).ShouldBeFalse(); + + // Corrupt entry is cleaned up. + user = await UserRepository.GetAsync(TestData.UserJohnId); + var remaining = await UserManager.GetAuthenticationTokenAsync( + user, AbpTwoFactorTokenProvider.InternalLoginProvider, tokenName); + remaining.ShouldBeNull(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Different_Purposes_Should_Be_Isolated() + { + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var twoFactorCode = await GenerateTokenAsync(user, TwoFactorPurpose); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + var otherCode = await GenerateTokenAsync(user, OtherPurpose); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + // Wrong-purpose validation must fail. + (await VerifyTokenAsync(user, otherCode, TwoFactorPurpose)).ShouldBeFalse(); + (await VerifyTokenAsync(user, twoFactorCode, OtherPurpose)).ShouldBeFalse(); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + // Correct purposes still work independently. + (await VerifyTokenAsync(user, twoFactorCode, TwoFactorPurpose)).ShouldBeTrue(); + (await VerifyTokenAsync(user, otherCode, OtherPurpose)).ShouldBeTrue(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Stored_Token_Data_Should_Persist_Across_UnitOfWork_Boundaries() + { + string code; + + using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) + { + var user = await UserRepository.GetAsync(TestData.UserJohnId); + code = await GenerateTokenAsync(user); + await uow.CompleteAsync(); + } + + using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) + { + var user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, code)).ShouldBeTrue(); + await uow.CompleteAsync(); + } + } + + [Fact] + public async Task Verify_Without_Generate_Should_Fail() + { + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, "123456")).ShouldBeFalse(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Empty_Token_Should_Fail_Verification() + { + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + await GenerateTokenAsync(user); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, string.Empty)).ShouldBeFalse(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Different_Users_Should_Be_Isolated() + { + using var uow = UnitOfWorkManager.Begin(); + + var john = await UserRepository.GetAsync(TestData.UserJohnId); + var johnCode = await GenerateTokenAsync(john); + + var david = await UserRepository.GetAsync(TestData.UserDavidId); + var davidCode = await GenerateTokenAsync(david); + + // Neither user's code should validate for the other (assuming distinct codes; + // even if they collide by chance the FixedTimeEquals path still runs, but the + // DataProtection payload is bound to a different user-scoped record). + if (johnCode != davidCode) + { + john = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(john, davidCode)).ShouldBeFalse(); + + david = await UserRepository.GetAsync(TestData.UserDavidId); + (await VerifyTokenAsync(david, johnCode)).ShouldBeFalse(); + } + + // Both still validate against their own user, proving that generating David's + // did not consume John's stored entry (or vice versa). + john = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(john, johnCode)).ShouldBeTrue(); + + david = await UserRepository.GetAsync(TestData.UserDavidId); + (await VerifyTokenAsync(david, davidCode)).ShouldBeTrue(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Multiple_Purposes_Can_Coexist_And_Consume_Independently() + { + using var uow = UnitOfWorkManager.Begin(); + + const string purposeA = "PurposeA"; + const string purposeB = "PurposeB"; + const string purposeC = "PurposeC"; + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var codeA = await GenerateTokenAsync(user, purposeA); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + var codeB = await GenerateTokenAsync(user, purposeB); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + var codeC = await GenerateTokenAsync(user, purposeC); + + // Consume B; A and C must still be valid. + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, codeB, purposeB)).ShouldBeTrue(); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, codeA, purposeA)).ShouldBeTrue(); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, codeC, purposeC)).ShouldBeTrue(); + + // Each is single-use and now consumed. + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, codeA, purposeA)).ShouldBeFalse(); + (await VerifyTokenAsync(user, codeB, purposeB)).ShouldBeFalse(); + (await VerifyTokenAsync(user, codeC, purposeC)).ShouldBeFalse(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Stored_Value_Missing_Separator_Should_Return_False_And_Cleanup() + { + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var code = await GenerateTokenAsync(user); + var tokenName = GetTokenName(TwoFactorPurpose); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.SetAuthenticationTokenAsync( + user, AbpTwoFactorTokenProvider.InternalLoginProvider, tokenName, "no-separator-here")) + .Succeeded.ShouldBeTrue(); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, code)).ShouldBeFalse(); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.GetAuthenticationTokenAsync( + user, AbpTwoFactorTokenProvider.InternalLoginProvider, tokenName)) + .ShouldBeNull(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Stored_Value_With_Non_Numeric_Expiration_Should_Return_False_And_Cleanup() + { + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var code = await GenerateTokenAsync(user); + var tokenName = GetTokenName(TwoFactorPurpose); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.SetAuthenticationTokenAsync( + user, AbpTwoFactorTokenProvider.InternalLoginProvider, tokenName, "validlookingpayload|not-a-number")) + .Succeeded.ShouldBeTrue(); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, code)).ShouldBeFalse(); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.GetAuthenticationTokenAsync( + user, AbpTwoFactorTokenProvider.InternalLoginProvider, tokenName)) + .ShouldBeNull(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Stored_Value_With_Unprotectable_Payload_Should_Return_False_And_Cleanup() + { + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var code = await GenerateTokenAsync(user); + var tokenName = GetTokenName(TwoFactorPurpose); + + var futureSeconds = ToUnixSeconds(Clock.Now.AddMinutes(1)).ToString(CultureInfo.InvariantCulture); + + // Valid expiration, but the protected payload is not a real DataProtection output. + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.SetAuthenticationTokenAsync( + user, AbpTwoFactorTokenProvider.InternalLoginProvider, tokenName, $"not-a-real-protected-payload|{futureSeconds}")) + .Succeeded.ShouldBeTrue(); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, code)).ShouldBeFalse(); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.GetAuthenticationTokenAsync( + user, AbpTwoFactorTokenProvider.InternalLoginProvider, tokenName)) + .ShouldBeNull(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Generation_Should_Not_Touch_Pre_Existing_Unrelated_Tokens() + { + // John already has a pre-seeded token ("test-provider"/"test-name" = "test-value"). + // Generating a 2FA code must not interfere with it. + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var preExisting = await UserManager.GetAuthenticationTokenAsync(user, "test-provider", "test-name"); + preExisting.ShouldBe("test-value"); + + await GenerateTokenAsync(user); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.GetAuthenticationTokenAsync(user, "test-provider", "test-name")) + .ShouldBe("test-value"); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Successful_Verification_Should_Not_Touch_Pre_Existing_Unrelated_Tokens() + { + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var code = await GenerateTokenAsync(user); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, code)).ShouldBeTrue(); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.GetAuthenticationTokenAsync(user, "test-provider", "test-name")) + .ShouldBe("test-value"); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Token_Should_Be_Stored_Under_Internal_Login_Provider_Name() + { + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + await GenerateTokenAsync(user); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + var stored = await UserManager.GetAuthenticationTokenAsync( + user, + AbpTwoFactorTokenProvider.InternalLoginProvider, + GetTokenName(TwoFactorPurpose)); + + stored.ShouldNotBeNullOrEmpty(); + stored.ShouldContain("|"); + + // Must not leak under the Identity provider name itself. + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await UserManager.GetAuthenticationTokenAsync(user, GetTokenProviderName(), TwoFactorPurpose)) + .ShouldBeNull(); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Stored_Value_Should_Be_Encrypted_Not_The_Raw_Code() + { + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var code = await GenerateTokenAsync(user); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + var stored = await UserManager.GetAuthenticationTokenAsync( + user, + AbpTwoFactorTokenProvider.InternalLoginProvider, + GetTokenName(TwoFactorPurpose)); + + stored.ShouldNotBeNull(); + stored.ShouldNotContain(code); + + // DataProtection output is substantially longer than a 6-digit code, which is a + // cheap sanity check that we're not accidentally storing the plaintext. + var protectedPart = stored.Substring(0, stored.LastIndexOf('|')); + protectedPart.Length.ShouldBeGreaterThan(code.Length * 4); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Consecutive_Generations_Should_Produce_Different_Protected_Payloads() + { + // DataProtection embeds per-call randomness, so even if the two underlying codes + // collide, their stored payloads will differ. This replaces the old + // "two generations produce different codes" assertion, which had a ~1e-6 flake rate. + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + await GenerateTokenAsync(user); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + var firstStored = await UserManager.GetAuthenticationTokenAsync( + user, AbpTwoFactorTokenProvider.InternalLoginProvider, GetTokenName(TwoFactorPurpose)); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + await GenerateTokenAsync(user); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + var secondStored = await UserManager.GetAuthenticationTokenAsync( + user, AbpTwoFactorTokenProvider.InternalLoginProvider, GetTokenName(TwoFactorPurpose)); + + firstStored.ShouldNotBeNull(); + secondStored.ShouldNotBeNull(); + secondStored.ShouldNotBe(firstStored); + + await uow.CompleteAsync(); + } + + [Fact] + public async Task Expired_Entry_Should_Not_Block_Future_Generation() + { + using var uow = UnitOfWorkManager.Begin(); + + var user = await UserRepository.GetAsync(TestData.UserJohnId); + var firstCode = await GenerateTokenAsync(user); + + // Force first entry into the past. + user = await UserRepository.GetAsync(TestData.UserJohnId); + var tokenName = GetTokenName(TwoFactorPurpose); + var stored = await UserManager.GetAuthenticationTokenAsync( + user, AbpTwoFactorTokenProvider.InternalLoginProvider, tokenName); + var protectedPart = stored!.Substring(0, stored.LastIndexOf('|')); + var expiredValue = protectedPart + "|" + + ToUnixSeconds(Clock.Now.AddMinutes(-1)).ToString(CultureInfo.InvariantCulture); + await UserManager.SetAuthenticationTokenAsync( + user, AbpTwoFactorTokenProvider.InternalLoginProvider, tokenName, expiredValue); + + // Verifying the expired code fails and cleans up. + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, firstCode)).ShouldBeFalse(); + + // New generation must succeed and verify. + user = await UserRepository.GetAsync(TestData.UserJohnId); + var secondCode = await GenerateTokenAsync(user); + + user = await UserRepository.GetAsync(TestData.UserJohnId); + (await VerifyTokenAsync(user, secondCode)).ShouldBeTrue(); + + await uow.CompleteAsync(); + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/GetTwoFactorAuthenticationUser_Tests.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/GetTwoFactorAuthenticationUser_Tests.cs new file mode 100644 index 0000000000..3ce774ff81 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/GetTwoFactorAuthenticationUser_Tests.cs @@ -0,0 +1,135 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Uow; +using Xunit; + +namespace Volo.Abp.Identity.AspNetCore; + +public class GetTwoFactorAuthenticationUser_Tests : SharedAbpIdentityAspNetCoreTestBase +{ + [Fact] + public async Task Should_Resolve_Tenant_User_By_Id_When_Current_Tenant_Is_Host() + { + var userRepository = GetRequiredService(); + var currentTenant = GetRequiredService(); + var unitOfWorkManager = GetRequiredService(); + + var tenantId = Guid.NewGuid(); + Guid tenantUserId; + + using (var uow = unitOfWorkManager.Begin()) + { + using (currentTenant.Change(tenantId)) + { + var user = new IdentityUser(Guid.NewGuid(), "shared-2fa-tenant-user", "shared-2fa-tenant-user@abp.io", tenantId); + await userRepository.InsertAsync(user); + tenantUserId = user.Id; + } + await uow.CompleteAsync(); + } + + await WriteTwoFactorCookieAsync(tenantUserId); + + var getResponse = await Client.GetAsync("/api/signin-test/get-two-factor-user"); + getResponse.EnsureSuccessStatusCode(); + var content = await getResponse.Content.ReadAsStringAsync(); + + content.ShouldBe(tenantUserId.ToString()); + } + + [Fact] + public async Task Should_Return_Null_When_No_Two_Factor_Cookie() + { + var getResponse = await Client.GetAsync("/api/signin-test/get-two-factor-user"); + getResponse.EnsureSuccessStatusCode(); + var content = await getResponse.Content.ReadAsStringAsync(); + + content.ShouldBe("null"); + } + + [Fact] + public async Task TwoFactorSignInAsync_Should_Resolve_Tenant_User_In_Shared_Mode() + { + // If the tenant switch inside AbpSignInManager.TwoFactorSignInAsync regresses, + // the base implementation would not find the user and return SignInResult.Failed. + // An inactive tenant user lets PreSignInCheck return NotAllowed, proving the user + // was actually located and inspected under its own tenant context. + var tenantUserId = await CreateInactiveTenantUserAsync("shared-2fa-signin", "shared-2fa-signin@abp.io"); + + await WriteTwoFactorCookieAsync(tenantUserId); + + var response = await Client.GetAsync("/api/signin-test/two-factor-signin?provider=Email&code=invalid"); + response.EnsureSuccessStatusCode(); + var result = await response.Content.ReadAsStringAsync(); + + result.ShouldBe("NotAllowed"); + } + + [Fact] + public async Task TwoFactorRecoveryCodeSignInAsync_Should_Resolve_Tenant_User_In_Shared_Mode() + { + // AbpSignInManager.TwoFactorRecoveryCodeSignInAsync runs PreSignInCheck after locating + // the user under its own tenant context. An inactive tenant user therefore produces + // NotAllowed only when the override actually resolves the user; a regression would + // return Failed instead. + var tenantUserId = await CreateInactiveTenantUserAsync("shared-2fa-recovery", "shared-2fa-recovery@abp.io"); + + await WriteTwoFactorCookieAsync(tenantUserId); + + var response = await Client.GetAsync("/api/signin-test/two-factor-recovery-signin?recoveryCode=invalid"); + response.EnsureSuccessStatusCode(); + var result = await response.Content.ReadAsStringAsync(); + + result.ShouldBe("NotAllowed"); + } + + private async Task CreateInactiveTenantUserAsync(string userName, string email) + { + return await CreateTenantUserAsync(userName, email, isActive: false); + } + + private async Task CreateTenantUserAsync(string userName, string email, bool isActive) + { + var userRepository = GetRequiredService(); + var currentTenant = GetRequiredService(); + var unitOfWorkManager = GetRequiredService(); + + var tenantId = Guid.NewGuid(); + Guid userId; + + using (var uow = unitOfWorkManager.Begin()) + { + using (currentTenant.Change(tenantId)) + { + var user = new IdentityUser(Guid.NewGuid(), userName, email, tenantId); + if (!isActive) + { + user.SetIsActive(false); + } + await userRepository.InsertAsync(user); + userId = user.Id; + } + await uow.CompleteAsync(); + } + + return userId; + } + + private async Task WriteTwoFactorCookieAsync(Guid userId) + { + var writeResponse = await Client.GetAsync($"/api/signin-test/write-two-factor-cookie?userId={userId}"); + writeResponse.EnsureSuccessStatusCode(); + + if (writeResponse.Headers.TryGetValues("Set-Cookie", out var setCookies)) + { + Client.DefaultRequestHeaders.Remove("Cookie"); + foreach (var cookie in setCookies) + { + Client.DefaultRequestHeaders.Add("Cookie", cookie.Split(';').First()); + } + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/Isolated_TwoFactor_Tests.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/Isolated_TwoFactor_Tests.cs new file mode 100644 index 0000000000..1fb730538e --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/Isolated_TwoFactor_Tests.cs @@ -0,0 +1,50 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Uow; +using Xunit; + +namespace Volo.Abp.Identity.AspNetCore; + +public class Isolated_TwoFactor_Tests : AbpIdentityAspNetCoreTestBase +{ + [Fact] + public async Task TwoFactorRecoveryCodeSignInAsync_Should_Return_NotAllowed_For_Inactive_User() + { + // The AbpSignInManager override adds PreSignInCheck to the recovery-code path (the base + // AspNetCore Identity implementation does not). This test asserts that behavior also works + // in the default (isolated) configuration so the new invariant is protected across modes. + var userManager = GetRequiredService(); + var userRepository = GetRequiredService(); + var unitOfWorkManager = GetRequiredService(); + + Guid userId; + using (var uow = unitOfWorkManager.Begin()) + { + var user = new IdentityUser(Guid.NewGuid(), "iso-recovery-inactive", "iso-recovery-inactive@abp.io"); + (await userManager.CreateAsync(user, "Iso!9Aa")).Succeeded.ShouldBeTrue(); + user.SetIsActive(false); + await userRepository.UpdateAsync(user); + userId = user.Id; + await uow.CompleteAsync(); + } + + var writeResponse = await Client.GetAsync($"/api/signin-test/write-two-factor-cookie?userId={userId}"); + writeResponse.EnsureSuccessStatusCode(); + if (writeResponse.Headers.TryGetValues("Set-Cookie", out var setCookies)) + { + Client.DefaultRequestHeaders.Remove("Cookie"); + foreach (var cookie in setCookies) + { + Client.DefaultRequestHeaders.Add("Cookie", cookie.Split(';').First()); + } + } + + var response = await Client.GetAsync("/api/signin-test/two-factor-recovery-signin?recoveryCode=invalid"); + response.EnsureSuccessStatusCode(); + var result = await response.Content.ReadAsStringAsync(); + + result.ShouldBe("NotAllowed"); + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/SharedAbpIdentityAspNetCoreTestBase.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/SharedAbpIdentityAspNetCoreTestBase.cs new file mode 100644 index 0000000000..451e56d268 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/SharedAbpIdentityAspNetCoreTestBase.cs @@ -0,0 +1,7 @@ +using Volo.Abp.AspNetCore.TestBase; + +namespace Volo.Abp.Identity.AspNetCore; + +public abstract class SharedAbpIdentityAspNetCoreTestBase : AbpAspNetCoreIntegratedTestBase +{ +} diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/SharedAbpIdentityAspNetCoreTestModule.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/SharedAbpIdentityAspNetCoreTestModule.cs new file mode 100644 index 0000000000..8ad2d55bc3 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/SharedAbpIdentityAspNetCoreTestModule.cs @@ -0,0 +1,17 @@ +using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.Identity.AspNetCore; + +[DependsOn(typeof(AbpIdentityAspNetCoreTestModule))] +public class SharedAbpIdentityAspNetCoreTestModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.IsEnabled = true; + options.UserSharingStrategy = TenantUserSharingStrategy.Shared; + }); + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/SharedAbpIdentityAspNetCoreTestStartup.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/SharedAbpIdentityAspNetCoreTestStartup.cs new file mode 100644 index 0000000000..9e23739708 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/SharedAbpIdentityAspNetCoreTestStartup.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Volo.Abp.Identity.AspNetCore; + +public class SharedAbpIdentityAspNetCoreTestStartup +{ + public void ConfigureServices(IServiceCollection services) + { + services.AddApplication(); + } + + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + { + app.InitializeApplication(); + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/Shared_SignIn_Tests.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/Shared_SignIn_Tests.cs new file mode 100644 index 0000000000..2fa0d30743 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/Shared_SignIn_Tests.cs @@ -0,0 +1,113 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Shouldly; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Uow; +using Xunit; + +namespace Volo.Abp.Identity.AspNetCore; + +public class Shared_SignIn_Tests : SharedAbpIdentityAspNetCoreTestBase +{ + [Fact] + public async Task PasswordSignInAsync_Should_Sign_In_Tenant_User_From_Host_Context() + { + // In shared mode, calling PasswordSignInAsync with a username while CurrentTenant is host + // must resolve the tenant user via FindSharedUserByNameAsync, apply the user's tenant + // IdentityOptions (the fix added in AbpSignInManager), and complete the sign-in. + var userManager = GetRequiredService(); + var currentTenant = GetRequiredService(); + var unitOfWorkManager = GetRequiredService(); + + var tenantId = Guid.NewGuid(); + const string userName = "shared-password-signin"; + const string password = "Shared!9Aa"; + + using (var uow = unitOfWorkManager.Begin()) + { + using (currentTenant.Change(tenantId)) + { + var user = new IdentityUser(Guid.NewGuid(), userName, userName + "@abp.io", tenantId); + (await userManager.CreateAsync(user, password)).Succeeded.ShouldBeTrue(); + } + await uow.CompleteAsync(); + } + + var response = await Client.GetAsync($"/api/signin-test/password?userName={userName}&password={password}"); + response.EnsureSuccessStatusCode(); + var result = await response.Content.ReadAsStringAsync(); + + result.ShouldBe("Succeeded"); + } + + [Fact] + public async Task PasswordSignInAsync_Should_Fail_For_Wrong_Password_In_Shared_Mode() + { + var userManager = GetRequiredService(); + var currentTenant = GetRequiredService(); + var unitOfWorkManager = GetRequiredService(); + + var tenantId = Guid.NewGuid(); + const string userName = "shared-password-wrong"; + + using (var uow = unitOfWorkManager.Begin()) + { + using (currentTenant.Change(tenantId)) + { + var user = new IdentityUser(Guid.NewGuid(), userName, userName + "@abp.io", tenantId); + (await userManager.CreateAsync(user, "Shared!9Aa")).Succeeded.ShouldBeTrue(); + } + await uow.CompleteAsync(); + } + + var response = await Client.GetAsync($"/api/signin-test/password?userName={userName}&password=wrong"); + response.EnsureSuccessStatusCode(); + var result = await response.Content.ReadAsStringAsync(); + + result.ShouldBe("Failed"); + } + + [Fact] + public async Task ExternalLoginSignInAsync_Should_Sign_In_Tenant_User_From_Host_Context() + { + // Covers the AbpSignInManager.ExternalLoginSignInAsync override: finds the user via + // FindSharedUserByLoginAsync, switches CurrentTenant to user.TenantId, applies tenant + // IdentityOptions, then calls PreSignInCheck + SignInOrTwoFactorAsync. + const string loginProvider = "test-provider"; + var providerKey = "ext-" + Guid.NewGuid().ToString("N").Substring(0, 8); + + var userManager = GetRequiredService(); + var currentTenant = GetRequiredService(); + var unitOfWorkManager = GetRequiredService(); + + var tenantId = Guid.NewGuid(); + + using (var uow = unitOfWorkManager.Begin()) + { + using (currentTenant.Change(tenantId)) + { + var user = new IdentityUser(Guid.NewGuid(), "shared-external-signin", "shared-external-signin@abp.io", tenantId); + (await userManager.CreateAsync(user, "Shared!9Aa")).Succeeded.ShouldBeTrue(); + (await userManager.AddLoginAsync(user, new UserLoginInfo(loginProvider, providerKey, "Test Provider"))).Succeeded.ShouldBeTrue(); + } + await uow.CompleteAsync(); + } + + var response = await Client.GetAsync($"/api/signin-test/external-login-signin?loginProvider={loginProvider}&providerKey={providerKey}"); + response.EnsureSuccessStatusCode(); + var result = await response.Content.ReadAsStringAsync(); + + result.ShouldBe("Succeeded"); + } + + [Fact] + public async Task ExternalLoginSignInAsync_Should_Fail_For_Unknown_Provider_Key_In_Shared_Mode() + { + var response = await Client.GetAsync($"/api/signin-test/external-login-signin?loginProvider=unknown&providerKey=none"); + response.EnsureSuccessStatusCode(); + var result = await response.Content.ReadAsStringAsync(); + + result.ShouldBe("Failed"); + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/SignInTestController.cs b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/SignInTestController.cs index bfc0c6d51e..b2381a6a53 100644 --- a/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/SignInTestController.cs +++ b/modules/identity/test/Volo.Abp.Identity.AspNetCore.Tests/Volo/Abp/Identity/AspNetCore/SignInTestController.cs @@ -1,4 +1,6 @@ -using System.Threading.Tasks; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc; @@ -27,4 +29,41 @@ public class SignInTestController : AbpController return Content(result.ToString()); } + + [Route("write-two-factor-cookie")] + public async Task WriteTwoFactorCookie(string userId) + { + var identity = new ClaimsIdentity(IdentityConstants.TwoFactorUserIdScheme); + identity.AddClaim(new Claim(ClaimTypes.Name, userId)); + await HttpContext.SignInAsync(IdentityConstants.TwoFactorUserIdScheme, new ClaimsPrincipal(identity)); + return Content("OK"); + } + + [Route("get-two-factor-user")] + public async Task GetTwoFactorUser() + { + var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); + return Content(user?.Id.ToString() ?? "null"); + } + + [Route("two-factor-signin")] + public async Task TwoFactorSignIn(string provider, string code) + { + var result = await _signInManager.TwoFactorSignInAsync(provider, code, false, false); + return Content(result.ToString()); + } + + [Route("two-factor-recovery-signin")] + public async Task TwoFactorRecoverySignIn(string recoveryCode) + { + var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode); + return Content(result.ToString()); + } + + [Route("external-login-signin")] + public async Task ExternalLoginSignIn(string loginProvider, string providerKey) + { + var result = await _signInManager.ExternalLoginSignInAsync(loginProvider, providerKey, false, false); + return Content(result.ToString()); + } } diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_SharedUser_SeparateDatabase_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_SharedUser_SeparateDatabase_Tests.cs new file mode 100644 index 0000000000..025274ae7c --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_SharedUser_SeparateDatabase_Tests.cs @@ -0,0 +1,7 @@ +using Volo.Abp.Identity.EntityFrameworkCore; + +namespace Volo.Abp.Identity; + +public class IdentityUserManager_SharedUser_SeparateDatabase_Tests : IdentityUserManager_SharedUser_SeparateDatabase_Tests +{ +} diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_SharedUser_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_SharedUser_Tests.cs new file mode 100644 index 0000000000..fb8ea90878 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_SharedUser_Tests.cs @@ -0,0 +1,5 @@ +namespace Volo.Abp.Identity; + +public class IdentityUserManager_SharedUser_Tests : IdentityUserManager_SharedUser_Tests +{ +} diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_Tests.cs index 48da394a05..82c133e698 100644 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/IdentityUserManager_Tests.cs @@ -491,147 +491,3 @@ public class IdentityUserManager_Tests : AbpIdentityDomainTestBase } } -public class SharedTenantUserSharingStrategy_IdentityUserManager_Tests : AbpIdentityDomainTestBase -{ - private readonly IdentityUserManager _identityUserManager; - private readonly IIdentityUserRepository _identityUserRepository; - private readonly ICurrentTenant _currentTenant; - private readonly IUnitOfWorkManager _unitOfWorkManager; - - public SharedTenantUserSharingStrategy_IdentityUserManager_Tests() - { - _identityUserManager = GetRequiredService(); - _identityUserRepository = GetRequiredService(); - _currentTenant = GetRequiredService(); - _unitOfWorkManager = GetRequiredService(); - } - - protected override void AfterAddApplication(IServiceCollection services) - { - services.Configure(options => - { - options.IsEnabled = true; - options.UserSharingStrategy = TenantUserSharingStrategy.Shared; - }); - } - - [Fact] - public async Task FindSharedUserByEmailAsync_Should_Return_Host_User() - { - var tenantId = Guid.NewGuid(); - var email = "shared-email@abp.io"; - - using (var uow = _unitOfWorkManager.Begin()) - { - await CreateUserAsync(null, "shared-host-email", email); - await CreateUserAsync(tenantId, "shared-tenant-email", email); - await uow.CompleteAsync(); - } - - using (_currentTenant.Change(tenantId)) - { - var user = await _identityUserManager.FindSharedUserByEmailAsync(email); - - user.ShouldNotBeNull(); - user.TenantId.ShouldBeNull(); - user.UserName.ShouldBe("shared-host-email"); - } - } - - [Fact] - public async Task FindSharedUserByNameAsync_Should_Return_Host_User() - { - var tenantId = Guid.NewGuid(); - var userName = "shared-user-name"; - - using (var uow = _unitOfWorkManager.Begin()) - { - await CreateUserAsync(null, userName, "shared-host-name@abp.io"); - await CreateUserAsync(tenantId, userName, "shared-tenant-name@abp.io"); - await uow.CompleteAsync(); - } - - using (_currentTenant.Change(tenantId)) - { - var user = await _identityUserManager.FindSharedUserByNameAsync(userName); - - user.ShouldNotBeNull(); - user.TenantId.ShouldBeNull(); - user.UserName.ShouldBe(userName); - } - } - - [Fact] - public async Task FindSharedUserByLoginAsync_Should_Return_Host_User() - { - var tenantId = Guid.NewGuid(); - const string loginProvider = "github"; - const string providerKey = "shared-login"; - - using (var uow = _unitOfWorkManager.Begin()) - { - await CreateUserAsync(null, "shared-host-login", "shared-host-login@abp.io", user => - { - user.AddLogin(new UserLoginInfo(loginProvider, providerKey, "Shared Login")); - }); - - await CreateUserAsync(tenantId, "shared-tenant-login", "shared-tenant-login@abp.io", user => - { - user.AddLogin(new UserLoginInfo(loginProvider, providerKey, "Shared Login")); - }); - - await uow.CompleteAsync(); - } - - using (_currentTenant.Change(tenantId)) - { - var user = await _identityUserManager.FindSharedUserByLoginAsync(loginProvider, providerKey); - - user.ShouldNotBeNull(); - user.TenantId.ShouldBeNull(); - user.UserName.ShouldBe("shared-host-login"); - } - } - - [Fact] - public async Task FindSharedUserByPasskeyIdAsync_Should_Return_Host_User() - { - var tenantId = Guid.NewGuid(); - var credentialId = new byte[] { 10, 20, 30, 40, 50, 60 }; - - using (var uow = _unitOfWorkManager.Begin()) - { - await CreateUserAsync(null, "shared-host-passkey", "shared-host-passkey@abp.io", user => - { - user.AddPasskey(credentialId, new IdentityPasskeyData()); - }); - await uow.CompleteAsync(); - } - - using (_currentTenant.Change(tenantId)) - { - var user = await _identityUserManager.FindSharedUserByPasskeyIdAsync(credentialId); - - user.ShouldNotBeNull(); - user.TenantId.ShouldBeNull(); - user.UserName.ShouldBe("shared-host-passkey"); - } - } - - private async Task CreateUserAsync( - Guid? tenantId, - string userName, - string email, - Action? configureUser = null) - { - var user = new IdentityUser(Guid.NewGuid(), userName, email, tenantId); - configureUser?.Invoke(user); - - using (_currentTenant.Change(tenantId)) - { - await _identityUserRepository.InsertAsync(user); - } - - return user; - } -} diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/OrganizationUnitManager_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/OrganizationUnitManager_Tests.cs index 5b6ed9af27..43c97a945a 100644 --- a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/OrganizationUnitManager_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/OrganizationUnitManager_Tests.cs @@ -1,11 +1,14 @@ -using Microsoft.AspNetCore.Identity; -using Shouldly; -using System.Linq; -using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Shouldly; +using System; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.Data; using Volo.Abp.Guids; -using Volo.Abp.Uow; -using Xunit; - +using Volo.Abp.MultiTenancy; +using Volo.Abp.Uow; +using Xunit; + namespace Volo.Abp.Identity; public class OrganizationUnitManager_Tests : AbpIdentityDomainTestBase @@ -17,6 +20,8 @@ public class OrganizationUnitManager_Tests : AbpIdentityDomainTestBase private readonly ILookupNormalizer _lookupNormalizer; private readonly IUnitOfWorkManager _unitOfWorkManager; private readonly IGuidGenerator _guidGenerator; + private readonly ICurrentTenant _currentTenant; + private readonly IDataFilter _dataFilter; public OrganizationUnitManager_Tests() { _organizationUnitManager = GetRequiredService(); @@ -26,6 +31,8 @@ public class OrganizationUnitManager_Tests : AbpIdentityDomainTestBase _testData = GetRequiredService(); _unitOfWorkManager = GetRequiredService(); _guidGenerator = GetService(); + _currentTenant = GetRequiredService(); + _dataFilter = GetRequiredService(); } [Fact] @@ -117,4 +124,59 @@ public class OrganizationUnitManager_Tests : AbpIdentityDomainTestBase ou = await _organizationUnitRepository.GetAsync("OU1", includeDetails: true); ou.Roles.FirstOrDefault(r => r.RoleId == adminRole.Id).ShouldBeNull(); } -} + + [Fact] + public async Task Should_Not_Create_Organization_Unit_With_Cross_Tenant_Parent() + { + var hostOu = await _organizationUnitRepository.GetAsync("OU1"); + + using (_currentTenant.Change(Guid.NewGuid())) + { + var newOu = new OrganizationUnit(_guidGenerator.Create(), "Cross", hostOu.Id, _currentTenant.Id); + + var ex = await Assert.ThrowsAsync( + async () => await _organizationUnitManager.CreateAsync(newOu)); + + ex.Code.ShouldBe(IdentityErrorCodes.OrganizationUnitParentTenantMismatch); + } + } + + [Fact] + public async Task Should_Not_Move_Organization_Unit_To_Cross_Tenant_Parent() + { + var hostOu1 = await _organizationUnitRepository.GetAsync("OU1"); + + OrganizationUnit tenantOu; + var tenantId = Guid.NewGuid(); + using (_currentTenant.Change(tenantId)) + { + tenantOu = new OrganizationUnit(_guidGenerator.Create(), "TenantRoot", null, tenantId); + await _organizationUnitManager.CreateAsync(tenantOu); + + var ex = await Assert.ThrowsAsync( + async () => await _organizationUnitManager.MoveAsync(tenantOu.Id, hostOu1.Id)); + + ex.Code.ShouldBe(IdentityErrorCodes.OrganizationUnitParentTenantMismatch); + } + } + + [Fact] + public async Task Should_Reject_Cross_Tenant_Parent_When_Multi_Tenancy_Filter_Disabled() + { + var hostOu = await _organizationUnitRepository.GetAsync("OU1"); + + using (_dataFilter.Disable()) + { + var tenantId = Guid.NewGuid(); + using (_currentTenant.Change(tenantId)) + { + var newOu = new OrganizationUnit(_guidGenerator.Create(), "Cross", hostOu.Id, tenantId); + + var ex = await Assert.ThrowsAsync( + async () => await _organizationUnitManager.CreateAsync(newOu)); + + ex.Code.ShouldBe(IdentityErrorCodes.OrganizationUnitParentTenantMismatch); + } + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentitySharedUserSeparateDbEntityFrameworkCoreTestModule.cs b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentitySharedUserSeparateDbEntityFrameworkCoreTestModule.cs new file mode 100644 index 0000000000..640965266a --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentitySharedUserSeparateDbEntityFrameworkCoreTestModule.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore.Sqlite; +using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; +using Volo.Abp.MultiTenancy.ConfigurationStore; +using Volo.Abp.PermissionManagement.EntityFrameworkCore; +using Volo.Abp.Uow; + +namespace Volo.Abp.Identity.EntityFrameworkCore; + +// EF/SQLite equivalent of the MongoDB separate-database test module: each predefined tenant +// has its own keep-alive in-memory SQLite connection. Each test method (and therefore each +// AbpApplication) gets a unique connection-string suffix so the test-data seeder runs into +// fresh databases instead of duplicating into shared cache. +[DependsOn( + typeof(AbpIdentityTestBaseModule), + typeof(AbpPermissionManagementEntityFrameworkCoreModule), + typeof(AbpIdentityEntityFrameworkCoreModule), + typeof(AbpEntityFrameworkCoreSqliteModule))] +public class AbpIdentitySharedUserSeparateDbEntityFrameworkCoreTestModule : AbpModule +{ + public static readonly Guid TenantAId = IdentitySharedUserSeparateDbConstants.TenantAId; + public static readonly Guid TenantBId = IdentitySharedUserSeparateDbConstants.TenantBId; + + // Per-app keep-alive connections so the in-memory SQLite databases survive for the test's + // lifetime (without an open connection, shared-cache in-memory databases are discarded). + // Uses AbpUnitTestSqliteConnection (SemaphoreSlim around CreateCommand) — SQLite isn't + // thread-safe and parallel xUnit collections would otherwise race. Disposed in + // OnApplicationShutdown so connections do not accumulate across tests. + private readonly List _keepAlive = new(); + + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(x => x.BusyTimeout = null); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + // Unique-per-app suffix so each test method gets a fresh trio of databases (the seeder + // in AbpIdentityTestBaseModule.OnApplicationInitialization expects to write into empty + // tables, which would fail if test methods reused the same shared-cache database). + var suffix = Guid.NewGuid().ToString("N"); + var hostConnection = $"Data Source=AbpIdentity_SeparateDb_Host_{suffix};Mode=Memory;Cache=Shared"; + var tenantAConnection = $"Data Source=AbpIdentity_SeparateDb_TenantA_{suffix};Mode=Memory;Cache=Shared"; + var tenantBConnection = $"Data Source=AbpIdentity_SeparateDb_TenantB_{suffix};Mode=Memory;Cache=Shared"; + + EnsureDatabase(hostConnection); + EnsureDatabase(tenantAConnection); + EnsureDatabase(tenantBConnection); + + Configure(options => + { + options.ConnectionStrings.Default = hostConnection; + }); + + Configure(options => + { + options.Configure(ctx => + { + ctx.DbContextOptions.UseSqlite(ctx.ConnectionString); + }); + }); + + Configure(options => + { + options.IsEnabled = true; + options.UserSharingStrategy = TenantUserSharingStrategy.Shared; + }); + + Configure(options => + { + options.Tenants = new[] + { + new TenantConfiguration(TenantAId, "tenant-a") + { + ConnectionStrings = new ConnectionStrings + { + { ConnectionStrings.DefaultConnectionStringName, tenantAConnection } + } + }, + new TenantConfiguration(TenantBId, "tenant-b") + { + ConnectionStrings = new ConnectionStrings + { + { ConnectionStrings.DefaultConnectionStringName, tenantBConnection } + } + } + }; + }); + + context.Services.AddAlwaysDisableUnitOfWorkTransaction(); + } + + public override void OnApplicationShutdown(ApplicationShutdownContext context) + { + foreach (var connection in _keepAlive) + { + connection.Dispose(); + } + _keepAlive.Clear(); + } + + private void EnsureDatabase(string connectionString) + { + var keepAlive = new AbpUnitTestSqliteConnection(connectionString); + keepAlive.Open(); + _keepAlive.Add(keepAlive); + + new IdentityDbContext( + new DbContextOptionsBuilder().UseSqlite(connectionString).Options) + .GetService().CreateTables(); + + new PermissionManagementDbContext( + new DbContextOptionsBuilder().UseSqlite(connectionString).Options) + .GetService().CreateTables(); + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/AbpIdentitySharedUserSeparateDbMongoDbTestModule.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/AbpIdentitySharedUserSeparateDbMongoDbTestModule.cs new file mode 100644 index 0000000000..0b11a09896 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/AbpIdentitySharedUserSeparateDbMongoDbTestModule.cs @@ -0,0 +1,56 @@ +using System; +using Volo.Abp.Data; +using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; +using Volo.Abp.MultiTenancy.ConfigurationStore; + +namespace Volo.Abp.Identity.MongoDB; + +// Registers two predefined tenants, each pointing at its own physical MongoDB database. +// Used by SharedUser tests that need true cross-database isolation (the only place where +// AbpIdentityUserValidator's shared-mode owner lookup and FindSharedUserBy*Async deviate from +// single-DB behavior). +[DependsOn(typeof(AbpIdentityMongoDbTestModule))] +public class AbpIdentitySharedUserSeparateDbMongoDbTestModule : AbpModule +{ + public static readonly Guid TenantAId = IdentitySharedUserSeparateDbConstants.TenantAId; + public static readonly Guid TenantBId = IdentitySharedUserSeparateDbConstants.TenantBId; + + public override void ConfigureServices(ServiceConfigurationContext context) + { + // Fixed tenant db names so MongoSandbox's embedded mongod does not accumulate one tenant + // db per test method. The host/default database is still configured by + // AbpIdentityMongoDbTestModule (random per test run), since AbpIdentityTestBaseModule + // re-seeds the host admin/role on every AbpApplication initialization. Each test method + // generates unique email / userName so cross-test data does not collide. + var tenantAConnection = MongoDbFixture.GetConnectionString("AbpIdentity_SharedSeparateDb_TenantA"); + var tenantBConnection = MongoDbFixture.GetConnectionString("AbpIdentity_SharedSeparateDb_TenantB"); + + Configure(options => + { + options.IsEnabled = true; + options.UserSharingStrategy = TenantUserSharingStrategy.Shared; + }); + + Configure(options => + { + options.Tenants = new[] + { + new TenantConfiguration(TenantAId, "tenant-a") + { + ConnectionStrings = new ConnectionStrings + { + { ConnectionStrings.DefaultConnectionStringName, tenantAConnection } + } + }, + new TenantConfiguration(TenantBId, "tenant-b") + { + ConnectionStrings = new ConnectionStrings + { + { ConnectionStrings.DefaultConnectionStringName, tenantBConnection } + } + } + }; + }); + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityUserManager_SharedUser_SeparateDatabase_Tests.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityUserManager_SharedUser_SeparateDatabase_Tests.cs new file mode 100644 index 0000000000..54456a2e9d --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityUserManager_SharedUser_SeparateDatabase_Tests.cs @@ -0,0 +1,8 @@ +using Xunit; + +namespace Volo.Abp.Identity.MongoDB; + +[Collection(MongoTestCollection.Name)] +public class IdentityUserManager_SharedUser_SeparateDatabase_Tests : IdentityUserManager_SharedUser_SeparateDatabase_Tests +{ +} diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityUserManager_SharedUser_Tests.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityUserManager_SharedUser_Tests.cs new file mode 100644 index 0000000000..c8e7c239a2 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/IdentityUserManager_SharedUser_Tests.cs @@ -0,0 +1,8 @@ +using Xunit; + +namespace Volo.Abp.Identity.MongoDB; + +[Collection(MongoTestCollection.Name)] +public class IdentityUserManager_SharedUser_Tests : IdentityUserManager_SharedUser_Tests +{ +} diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentitySharedUserSeparateDbConstants.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentitySharedUserSeparateDbConstants.cs new file mode 100644 index 0000000000..a2a86ca83e --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentitySharedUserSeparateDbConstants.cs @@ -0,0 +1,12 @@ +using System; + +namespace Volo.Abp.Identity; + +// Single source of truth for the predefined tenant ids used by the shared-user separate-database +// test suite. Concrete EF/Mongo test modules and the abstract test class both reference these +// constants directly so the modules don't have to type-couple back into the test class. +public static class IdentitySharedUserSeparateDbConstants +{ + public static readonly Guid TenantAId = Guid.Parse("11111111-1111-1111-1111-111111111111"); + public static readonly Guid TenantBId = Guid.Parse("22222222-2222-2222-2222-222222222222"); +} diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserManager_SharedUser_SeparateDatabase_Tests.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserManager_SharedUser_SeparateDatabase_Tests.cs new file mode 100644 index 0000000000..23d4a532b6 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserManager_SharedUser_SeparateDatabase_Tests.cs @@ -0,0 +1,201 @@ +using System; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Data; +using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Uow; +using Xunit; + +namespace Volo.Abp.Identity; + +// Multi-tenant separate-database isolation tests, runnable on any storage backend that lets +// each predefined tenant resolve to its own physical connection. EF (SQLite per-tenant +// keep-alive) and MongoDB (per-tenant database) implementations both work. +// +// NOTE on scope: open-source IdentityUserManager.FindSharedUserBy* assumes a single shared +// database; cross-DB shared-user resolution is the Pro UserSharingManager's responsibility. +// These tests therefore only assert the layer the open-source framework owns: tenant +// connection routing and IMultiTenant filter behavior. +// +// Concrete subclasses must register two predefined tenants (TenantAId / TenantBId from this +// class) each with its own connection string, and enable shared-user mode in the test module. +public abstract class IdentityUserManager_SharedUser_SeparateDatabase_Tests : AbpIdentityTestBase + where TStartupModule : IAbpModule +{ + public static readonly Guid TenantAId = IdentitySharedUserSeparateDbConstants.TenantAId; + public static readonly Guid TenantBId = IdentitySharedUserSeparateDbConstants.TenantBId; + + protected IdentityUserManager IdentityUserManager { get; } + protected IIdentityUserRepository IdentityUserRepository { get; } + protected ICurrentTenant CurrentTenant { get; } + protected IUnitOfWorkManager UnitOfWorkManager { get; } + protected IDataFilter DataFilter { get; } + + protected IdentityUserManager_SharedUser_SeparateDatabase_Tests() + { + IdentityUserManager = GetRequiredService(); + IdentityUserRepository = GetRequiredService(); + CurrentTenant = GetRequiredService(); + UnitOfWorkManager = GetRequiredService(); + DataFilter = GetRequiredService(); + } + + [Fact] + public virtual async Task Tenant_Connection_Should_Not_See_Host_Rows() + { + // Disables IMultiTenant before querying so this test fails if connection routing is + // broken (a tenant context unexpectedly hitting the host db) rather than being masked + // by the data filter. + var probeEmail = $"infra-host-{Guid.NewGuid():N}@abp.io"; + Guid hostUserId; + + using (CurrentTenant.Change(null)) + using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) + { + var hostUser = new IdentityUser(Guid.NewGuid(), $"infra-host-{Guid.NewGuid():N}", probeEmail, null); + await IdentityUserRepository.InsertAsync(hostUser); + hostUserId = hostUser.Id; + await uow.CompleteAsync(); + } + + using (CurrentTenant.Change(TenantAId)) + using (DataFilter.Disable()) + using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) + { + (await IdentityUserRepository.GetListAsync()).ShouldNotContain(u => u.Id == hostUserId); + await uow.CompleteAsync(); + } + + using (CurrentTenant.Change(TenantAId)) + { + (await IdentityUserManager.FindByEmailAsync(probeEmail)).ShouldBeNull(); + } + } + + [Fact] + public virtual async Task Host_Connection_Should_Not_See_Tenant_Rows() + { + var probeEmail = $"infra-tenant-{Guid.NewGuid():N}@abp.io"; + Guid tenantUserId; + + using (CurrentTenant.Change(TenantAId)) + using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) + { + var tenantUser = new IdentityUser(Guid.NewGuid(), $"infra-t-{Guid.NewGuid():N}", probeEmail, TenantAId); + await IdentityUserRepository.InsertAsync(tenantUser); + tenantUserId = tenantUser.Id; + await uow.CompleteAsync(); + } + + using (CurrentTenant.Change(null)) + using (DataFilter.Disable()) + using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) + { + (await IdentityUserRepository.GetListAsync()).ShouldNotContain(u => u.Id == tenantUserId); + await uow.CompleteAsync(); + } + + using (CurrentTenant.Change(null)) + { + (await IdentityUserManager.FindByEmailAsync(probeEmail)).ShouldBeNull(); + } + } + + [Fact] + public virtual async Task TenantA_Connection_Should_Not_See_TenantB_Rows() + { + var probeEmail = $"infra-cross-{Guid.NewGuid():N}@abp.io"; + Guid tenantBUserId; + + using (CurrentTenant.Change(TenantBId)) + using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) + { + var u = new IdentityUser(Guid.NewGuid(), $"b-{Guid.NewGuid():N}", probeEmail, TenantBId); + await IdentityUserRepository.InsertAsync(u); + tenantBUserId = u.Id; + await uow.CompleteAsync(); + } + + using (CurrentTenant.Change(TenantAId)) + using (DataFilter.Disable()) + using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) + { + (await IdentityUserRepository.GetListAsync()).ShouldNotContain(u => u.Id == tenantBUserId); + await uow.CompleteAsync(); + } + + using (CurrentTenant.Change(TenantAId)) + { + (await IdentityUserManager.FindByEmailAsync(probeEmail)).ShouldBeNull(); + } + } + + [Fact] + public virtual async Task Different_Tenants_Should_Allow_Same_Email_With_Their_Own_Rows() + { + var email = $"same-email-{Guid.NewGuid():N}@abp.io"; + var nameA = $"a-{Guid.NewGuid():N}"; + var nameB = $"b-{Guid.NewGuid():N}"; + + using (CurrentTenant.Change(TenantAId)) + using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) + { + await IdentityUserRepository.InsertAsync(new IdentityUser(Guid.NewGuid(), nameA, email, TenantAId)); + await uow.CompleteAsync(); + } + using (CurrentTenant.Change(TenantBId)) + using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) + { + await IdentityUserRepository.InsertAsync(new IdentityUser(Guid.NewGuid(), nameB, email, TenantBId)); + await uow.CompleteAsync(); + } + + using (CurrentTenant.Change(TenantAId)) + { + var userA = await IdentityUserManager.FindByEmailAsync(email); + userA.ShouldNotBeNull(); + userA.UserName.ShouldBe(nameA); + } + using (CurrentTenant.Change(TenantBId)) + { + var userB = await IdentityUserManager.FindByEmailAsync(email); + userB.ShouldNotBeNull(); + userB.UserName.ShouldBe(nameB); + } + } + + [Fact] + public virtual async Task Different_Tenants_Should_Allow_Same_UserName_With_Their_Own_Rows() + { + var userName = $"same-name-{Guid.NewGuid():N}"; + var emailA = $"a-{Guid.NewGuid():N}@abp.io"; + var emailB = $"b-{Guid.NewGuid():N}@abp.io"; + + using (CurrentTenant.Change(TenantAId)) + using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) + { + await IdentityUserRepository.InsertAsync(new IdentityUser(Guid.NewGuid(), userName, emailA, TenantAId)); + await uow.CompleteAsync(); + } + using (CurrentTenant.Change(TenantBId)) + using (var uow = UnitOfWorkManager.Begin(requiresNew: true)) + { + await IdentityUserRepository.InsertAsync(new IdentityUser(Guid.NewGuid(), userName, emailB, TenantBId)); + await uow.CompleteAsync(); + } + + using (CurrentTenant.Change(TenantAId)) + { + var userInTenantA = await IdentityUserManager.FindByNameAsync(userName); + userInTenantA.ShouldNotBeNull(); + userInTenantA.Email.ShouldBe(emailA); + } + using (CurrentTenant.Change(TenantBId)) + { + var userInTenantB = await IdentityUserManager.FindByNameAsync(userName); + userInTenantB.ShouldNotBeNull(); + userInTenantB.Email.ShouldBe(emailB); + } + } +} diff --git a/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserManager_SharedUser_Tests.cs b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserManager_SharedUser_Tests.cs new file mode 100644 index 0000000000..4c73f4bd80 --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.TestBase/Volo/Abp/Identity/IdentityUserManager_SharedUser_Tests.cs @@ -0,0 +1,254 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Uow; +using Xunit; + +namespace Volo.Abp.Identity; + +// Abstract test suite for IdentityUserManager.FindSharedUserBy*Async under +// TenantUserSharingStrategy.Shared. Concrete subclasses in Domain.Tests (EF) and +// MongoDB.Tests pick the storage backend by passing the corresponding TStartupModule. +public abstract class IdentityUserManager_SharedUser_Tests : AbpIdentityTestBase + where TStartupModule : IAbpModule +{ + protected IdentityUserManager IdentityUserManager { get; } + protected IIdentityUserRepository IdentityUserRepository { get; } + protected ICurrentTenant CurrentTenant { get; } + protected IUnitOfWorkManager UnitOfWorkManager { get; } + + protected IdentityUserManager_SharedUser_Tests() + { + IdentityUserManager = GetRequiredService(); + IdentityUserRepository = GetRequiredService(); + CurrentTenant = GetRequiredService(); + UnitOfWorkManager = GetRequiredService(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + services.Configure(options => + { + options.IsEnabled = true; + options.UserSharingStrategy = TenantUserSharingStrategy.Shared; + }); + } + + [Fact] + public virtual async Task FindSharedUserByEmailAsync_Should_Return_Host_User() + { + var tenantId = Guid.NewGuid(); + var email = $"shared-email-{Guid.NewGuid():N}@abp.io"; + + using (var uow = UnitOfWorkManager.Begin()) + { + await CreateUserAsync(null, $"shared-host-email-{Guid.NewGuid():N}", email); + await CreateUserAsync(tenantId, $"shared-tenant-email-{Guid.NewGuid():N}", email); + await uow.CompleteAsync(); + } + + using (CurrentTenant.Change(tenantId)) + { + var user = await IdentityUserManager.FindSharedUserByEmailAsync(email); + user.ShouldNotBeNull(); + user.TenantId.ShouldBeNull(); + } + } + + [Fact] + public virtual async Task FindSharedUserByEmailAsync_Should_Find_Tenant_User_When_No_Host_User() + { + var tenantId = Guid.NewGuid(); + var email = $"shared-tenant-only-{Guid.NewGuid():N}@abp.io"; + + using (var uow = UnitOfWorkManager.Begin()) + { + await CreateUserAsync(tenantId, $"shared-tenant-only-{Guid.NewGuid():N}", email); + await uow.CompleteAsync(); + } + + using (CurrentTenant.Change(null)) + { + var user = await IdentityUserManager.FindSharedUserByEmailAsync(email); + user.ShouldNotBeNull(); + user.TenantId.ShouldBe(tenantId); + } + } + + [Fact] + public virtual async Task FindSharedUserByEmailAsync_Should_Return_Null_For_Unknown_Email() + { + using (CurrentTenant.Change(null)) + { + var user = await IdentityUserManager.FindSharedUserByEmailAsync($"missing-{Guid.NewGuid():N}@abp.io"); + user.ShouldBeNull(); + } + } + + [Fact] + public virtual async Task FindSharedUserByNameAsync_Should_Return_Host_User() + { + var tenantId = Guid.NewGuid(); + var userName = $"shared-name-{Guid.NewGuid():N}"; + + using (var uow = UnitOfWorkManager.Begin()) + { + await CreateUserAsync(null, userName, $"host-{Guid.NewGuid():N}@abp.io"); + await CreateUserAsync(tenantId, userName, $"tenant-{Guid.NewGuid():N}@abp.io"); + await uow.CompleteAsync(); + } + + using (CurrentTenant.Change(tenantId)) + { + var user = await IdentityUserManager.FindSharedUserByNameAsync(userName); + user.ShouldNotBeNull(); + user.TenantId.ShouldBeNull(); + } + } + + [Fact] + public virtual async Task FindSharedUserByLoginAsync_Should_Return_Host_User() + { + var tenantId = Guid.NewGuid(); + var loginProvider = "github"; + var providerKey = $"shared-login-{Guid.NewGuid():N}"; + + using (var uow = UnitOfWorkManager.Begin()) + { + await CreateUserAsync(null, $"host-login-{Guid.NewGuid():N}", $"host-login-{Guid.NewGuid():N}@abp.io", + u => u.AddLogin(new UserLoginInfo(loginProvider, providerKey, "Shared Login"))); + await CreateUserAsync(tenantId, $"tenant-login-{Guid.NewGuid():N}", $"tenant-login-{Guid.NewGuid():N}@abp.io", + u => u.AddLogin(new UserLoginInfo(loginProvider, providerKey, "Shared Login"))); + await uow.CompleteAsync(); + } + + using (CurrentTenant.Change(tenantId)) + { + var user = await IdentityUserManager.FindSharedUserByLoginAsync(loginProvider, providerKey); + user.ShouldNotBeNull(); + user.TenantId.ShouldBeNull(); + } + } + + [Fact] + public virtual async Task FindSharedUserByPasskeyIdAsync_Should_Return_Host_User() + { + var tenantId = Guid.NewGuid(); + var credentialId = Guid.NewGuid().ToByteArray(); + + using (var uow = UnitOfWorkManager.Begin()) + { + await CreateUserAsync(null, $"shared-host-passkey-{Guid.NewGuid():N}", $"shared-host-passkey-{Guid.NewGuid():N}@abp.io", + u => u.AddPasskey(credentialId, new IdentityPasskeyData())); + await uow.CompleteAsync(); + } + + using (CurrentTenant.Change(tenantId)) + { + var user = await IdentityUserManager.FindSharedUserByPasskeyIdAsync(credentialId); + user.ShouldNotBeNull(); + user.TenantId.ShouldBeNull(); + } + } + + [Fact] + public virtual async Task FindSharedUserByIdAsync_Should_Find_Tenant_User_From_Host_Context() + { + // Core 2FA shared-mode bug condition: a tenant-only user must be reachable by id from + // a host context (CurrentTenant=null). The IMultiTenant filter would otherwise hide it. + var tenantId = Guid.NewGuid(); + IdentityUser tenantUser; + + using (var uow = UnitOfWorkManager.Begin()) + { + tenantUser = await CreateUserAsync(tenantId, $"shared-id-tenant-{Guid.NewGuid():N}", $"shared-id-tenant-{Guid.NewGuid():N}@abp.io"); + await uow.CompleteAsync(); + } + + using (CurrentTenant.Change(null)) + { + var user = await IdentityUserManager.FindSharedUserByIdAsync(tenantUser.Id.ToString()); + user.ShouldNotBeNull(); + user.Id.ShouldBe(tenantUser.Id); + user.TenantId.ShouldBe(tenantId); + } + } + + [Fact] + public virtual async Task FindSharedUserByIdAsync_Should_Find_Host_User_From_Tenant_Context() + { + var tenantId = Guid.NewGuid(); + IdentityUser hostUser; + + using (var uow = UnitOfWorkManager.Begin()) + { + hostUser = await CreateUserAsync(null, $"shared-id-host-{Guid.NewGuid():N}", $"shared-id-host-{Guid.NewGuid():N}@abp.io"); + await uow.CompleteAsync(); + } + + using (CurrentTenant.Change(tenantId)) + { + var user = await IdentityUserManager.FindSharedUserByIdAsync(hostUser.Id.ToString()); + user.ShouldNotBeNull(); + user.TenantId.ShouldBeNull(); + } + } + + [Fact] + public virtual async Task FindSharedUserByIdAsync_Should_Return_Null_For_Unknown_Id() + { + using (CurrentTenant.Change(null)) + { + var user = await IdentityUserManager.FindSharedUserByIdAsync(Guid.NewGuid().ToString()); + user.ShouldBeNull(); + } + } + + [Fact] + public virtual async Task Login_Then_TwoFactor_MidFlow_Should_Resolve_Same_Tenant_User() + { + // End-to-end shape of the 2FA shared-mode regression: a host-context lookup-by-name + // followed by lookup-by-id must return the same tenant row both times. + var tenantId = Guid.NewGuid(); + var userName = $"shared-2fa-{Guid.NewGuid():N}"; + + using (var uow = UnitOfWorkManager.Begin()) + { + await CreateUserAsync(tenantId, userName, $"{userName}@abp.io"); + await uow.CompleteAsync(); + } + + using (CurrentTenant.Change(null)) + { + var loginUser = await IdentityUserManager.FindSharedUserByNameAsync(userName); + loginUser.ShouldNotBeNull(); + loginUser.TenantId.ShouldBe(tenantId); + + var twoFactorUser = await IdentityUserManager.FindSharedUserByIdAsync(loginUser.Id.ToString()); + twoFactorUser.ShouldNotBeNull(); + twoFactorUser.Id.ShouldBe(loginUser.Id); + twoFactorUser.TenantId.ShouldBe(tenantId); + } + } + + protected async Task CreateUserAsync( + Guid? tenantId, + string userName, + string email, + Action configureUser = null) + { + var user = new IdentityUser(Guid.NewGuid(), userName, email, tenantId); + configureUser?.Invoke(user); + + using (CurrentTenant.Change(tenantId)) + { + await IdentityUserRepository.InsertAsync(user); + } + + return user; + } +} diff --git a/modules/openiddict/app/OpenIddict.Demo.Server/OpenIddict.Demo.Server.csproj b/modules/openiddict/app/OpenIddict.Demo.Server/OpenIddict.Demo.Server.csproj index 09a185919a..2bc17b4f7c 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Server/OpenIddict.Demo.Server.csproj +++ b/modules/openiddict/app/OpenIddict.Demo.Server/OpenIddict.Demo.Server.csproj @@ -68,6 +68,10 @@ runtime; build; native; contentfiles; analyzers compile; contentFiles; build; buildMultitargeting; buildTransitive; analyzers; native + + runtime; build; native; contentfiles; analyzers + compile; contentFiles; build; buildMultitargeting; buildTransitive; analyzers; native + diff --git a/modules/openiddict/app/OpenIddict.Demo.Server/package.json b/modules/openiddict/app/OpenIddict.Demo.Server/package.json index abb49011d8..0b8b07c6be 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Server/package.json +++ b/modules/openiddict/app/OpenIddict.Demo.Server/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.4.0" } } diff --git a/modules/openiddict/app/angular/package.json b/modules/openiddict/app/angular/package.json index 8e57ee5313..3903400110 100644 --- a/modules/openiddict/app/angular/package.json +++ b/modules/openiddict/app/angular/package.json @@ -12,15 +12,15 @@ }, "private": true, "dependencies": { - "@abp/ng.account": "~10.3.0", - "@abp/ng.components": "~10.3.0", - "@abp/ng.core": "~10.3.0", - "@abp/ng.oauth": "~10.3.0", - "@abp/ng.identity": "~10.3.0", - "@abp/ng.setting-management": "~10.3.0", - "@abp/ng.tenant-management": "~10.3.0", - "@abp/ng.theme.shared": "~10.3.0", - "@abp/ng.theme.lepton-x": "~5.3.0", + "@abp/ng.account": "~10.4.0", + "@abp/ng.components": "~10.4.0", + "@abp/ng.core": "~10.4.0", + "@abp/ng.oauth": "~10.4.0", + "@abp/ng.identity": "~10.4.0", + "@abp/ng.setting-management": "~10.4.0", + "@abp/ng.tenant-management": "~10.4.0", + "@abp/ng.theme.shared": "~10.4.0", + "@abp/ng.theme.lepton-x": "~5.4.0", "@angular/animations": "^15.0.1", "@angular/common": "^15.0.1", "@angular/compiler": "^15.0.1", @@ -36,7 +36,7 @@ "zone.js": "~0.11.4" }, "devDependencies": { - "@abp/ng.schematics": "~10.3.0", + "@abp/ng.schematics": "~10.4.0", "@angular-devkit/build-angular": "^15.0.1", "@angular-eslint/builder": "~15.1.0", "@angular-eslint/eslint-plugin": "~15.1.0", diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs index 3a9c8109fc..48496874d0 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs @@ -25,9 +25,16 @@ public class AbpOpenIddictAspNetCoreModule : AbpModule Configure(options => { + options.ClaimsPrincipalHandlers.Add(); options.ClaimsPrincipalHandlers.Add(); }); + var preActions = context.Services.GetPreConfigureActions(); + Configure(options => + { + preActions.Configure(options); + }); + Configure(options => { options.ViewLocationFormats.Add("/Volo/Abp/OpenIddict/Views/{1}/{0}.cshtml"); diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictOptions.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictOptions.cs index 3339b6d376..f0c2415fb2 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictOptions.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictOptions.cs @@ -25,4 +25,33 @@ public class AbpOpenIddictAspNetCoreOptions /// Set the url of the select account page. /// public string SelectAccountPage { get; set; } = "~/Account/SelectAccount"; + + /// + /// When set to true, the access token issued for the client_credentials grant + /// automatically includes the scopes configured on the client application (permissions + /// prefixed with oi_scp:) when the client does not explicitly request any scope. + /// Default: false. + /// + public bool UseDefaultScopesForClientCredentials { get; set; } + + /// + /// When set to true, the token response for the password grant automatically + /// grants the scopes configured on the client application (permissions prefixed with + /// oi_scp:) when the client does not explicitly request any scope. If the configured + /// scopes include openid/profile/email/roles, the corresponding + /// id_token and claim destinations are affected as well. + /// Default: false. + /// + public bool UseDefaultScopesForPassword { get; set; } + + /// + /// When set to true, the token response for the + /// urn:ietf:params:oauth:grant-type:token-exchange grant automatically grants the + /// scopes configured on the client application (permissions prefixed with oi_scp:) + /// when the client does not explicitly request any scope. If the configured scopes include + /// openid/profile/email/roles, the corresponding id_token and + /// claim destinations are affected as well. + /// Default: false. + /// + public bool UseDefaultScopesForTokenExchange { get; set; } } diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Claims/AbpDefaultScopesHandler.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Claims/AbpDefaultScopesHandler.cs new file mode 100644 index 0000000000..82ab4d3bb2 --- /dev/null +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Claims/AbpDefaultScopesHandler.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using OpenIddict.Abstractions; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.OpenIddict; + +public class AbpDefaultScopesHandler : IAbpOpenIddictClaimsPrincipalHandler, ITransientDependency +{ + public ILogger Logger { get; set; } + = NullLogger.Instance; + + public virtual async Task HandleAsync(AbpOpenIddictClaimsPrincipalHandlerContext context) + { + var options = context.ScopeServiceProvider + .GetRequiredService>().Value; + + var request = context.OpenIddictRequest; + if (!IsDefaultScopesEnabled(request, options)) + { + return; + } + + if (!context.Principal.GetScopes().IsDefaultOrEmpty) + { + return; + } + + var clientId = request.ClientId; + if (string.IsNullOrEmpty(clientId)) + { + return; + } + + var applicationManager = context.ScopeServiceProvider.GetRequiredService(); + var scopeManager = context.ScopeServiceProvider.GetRequiredService(); + + var application = await applicationManager.FindByClientIdAsync(clientId); + if (application == null) + { + return; + } + + var permissions = await applicationManager.GetPermissionsAsync(application); + var prefix = OpenIddictConstants.Permissions.Prefixes.Scope; + + var scopes = permissions + .Where(p => p.StartsWith(prefix, StringComparison.Ordinal)) + .Select(p => p[prefix.Length..]) + .ToImmutableArray(); + + if (scopes.IsDefaultOrEmpty) + { + return; + } + + Logger.LogDebug( + "Injecting default scopes for client {ClientId} (grant_type {GrantType}): {Scopes}", + clientId, + request.GrantType, + string.Join(", ", scopes)); + + context.Principal.SetScopes(scopes); + context.Principal.SetResources(await scopeManager.ListResourcesAsync(scopes).ToListAsync()); + } + + protected virtual bool IsDefaultScopesEnabled(OpenIddictRequest request, AbpOpenIddictAspNetCoreOptions options) + { + if (request.IsClientCredentialsGrantType()) + { + return options.UseDefaultScopesForClientCredentials; + } + + if (request.IsPasswordGrantType()) + { + return options.UseDefaultScopesForPassword; + } + + if (request.IsTokenExchangeGrantType()) + { + return options.UseDefaultScopesForTokenExchange; + } + + return false; + } +} diff --git a/modules/permission-management/Volo.Abp.PermissionManagement.slnx b/modules/permission-management/Volo.Abp.PermissionManagement.slnx index fbfc4f71b8..b1220e4a26 100644 --- a/modules/permission-management/Volo.Abp.PermissionManagement.slnx +++ b/modules/permission-management/Volo.Abp.PermissionManagement.slnx @@ -2,6 +2,9 @@ + + + diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.Server/AbpPermissionManagementBlazorMudBlazorServerModule.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.Server/AbpPermissionManagementBlazorMudBlazorServerModule.cs new file mode 100644 index 0000000000..5b38c31260 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.Server/AbpPermissionManagementBlazorMudBlazorServerModule.cs @@ -0,0 +1,12 @@ +using Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor; +using Volo.Abp.Modularity; + +namespace Volo.Abp.PermissionManagement.Blazor.MudBlazor.Server; + +[DependsOn( + typeof(AbpPermissionManagementBlazorMudBlazorModule), + typeof(AbpAspNetCoreComponentsServerThemingMudBlazorModule) +)] +public class AbpPermissionManagementBlazorMudBlazorServerModule : AbpModule +{ +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.Server/FodyWeavers.xml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.Server/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.Server/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.Server/FodyWeavers.xsd b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.Server/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.Server/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.Server/Volo.Abp.PermissionManagement.Blazor.MudBlazor.Server.csproj b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.Server/Volo.Abp.PermissionManagement.Blazor.MudBlazor.Server.csproj new file mode 100644 index 0000000000..df904cf169 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.Server/Volo.Abp.PermissionManagement.Blazor.MudBlazor.Server.csproj @@ -0,0 +1,15 @@ + + + + + + + net10.0 + + + + + + + + diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.WebAssembly/AbpPermissionManagementBlazorMudBlazorWebAssemblyModule.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.WebAssembly/AbpPermissionManagementBlazorMudBlazorWebAssemblyModule.cs new file mode 100644 index 0000000000..074a9e7be4 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.WebAssembly/AbpPermissionManagementBlazorMudBlazorWebAssemblyModule.cs @@ -0,0 +1,13 @@ +using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor; +using Volo.Abp.Modularity; + +namespace Volo.Abp.PermissionManagement.Blazor.MudBlazor.WebAssembly; + +[DependsOn( + typeof(AbpPermissionManagementBlazorMudBlazorModule), + typeof(AbpAspNetCoreComponentsWebAssemblyThemingMudBlazorModule), + typeof(AbpPermissionManagementHttpApiClientModule) +)] +public class AbpPermissionManagementBlazorMudBlazorWebAssemblyModule : AbpModule +{ +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xsd b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.WebAssembly/Volo.Abp.PermissionManagement.Blazor.MudBlazor.WebAssembly.csproj b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.WebAssembly/Volo.Abp.PermissionManagement.Blazor.MudBlazor.WebAssembly.csproj new file mode 100644 index 0000000000..bf8592660a --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.WebAssembly/Volo.Abp.PermissionManagement.Blazor.MudBlazor.WebAssembly.csproj @@ -0,0 +1,16 @@ + + + + + + + net10.0 + + + + + + + + + diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/AbpPermissionManagementBlazorMudBlazorModule.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/AbpPermissionManagementBlazorMudBlazorModule.cs new file mode 100644 index 0000000000..161b8c10c2 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/AbpPermissionManagementBlazorMudBlazorModule.cs @@ -0,0 +1,26 @@ +using Localization.Resources.AbpUi; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.PermissionManagement.Localization; + +namespace Volo.Abp.PermissionManagement.Blazor.MudBlazor; + +[DependsOn( + typeof(AbpAspNetCoreComponentsWebThemingMudBlazorModule), + typeof(AbpPermissionManagementApplicationContractsModule) + )] +public class AbpPermissionManagementBlazorMudBlazorModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Resources + .Get() + .AddBaseTypes( + typeof(AbpUiResource) + ); + }); + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/Components/PermissionManagementModal.razor b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/Components/PermissionManagementModal.razor new file mode 100644 index 0000000000..4c7eba0bfc --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/Components/PermissionManagementModal.razor @@ -0,0 +1,75 @@ +@inherits Volo.Abp.AspNetCore.Components.AbpComponentBase +@using global::MudBlazor +@using JetBrains.Annotations +@using Volo.Abp.PermissionManagement.Localization + + + + @L["Permissions"] - @_entityDisplayName + + + + + + + + + + + + + @L["PermissionGroup"] + + @if (_groups != null && _groups.Any()) + { + + @foreach (var group in _groups) + { + x.IsGranted)})")"> +
+ + + + @foreach (var permission in group.Permissions) + { + var depth = GetPermissionDepthOrDefault(permission.Name); +
+ +
+ } +
+
+ } +
+ } +
+
+ + @L["Cancel"] + @L["Save"] + +
diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/Components/PermissionManagementModal.razor.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/Components/PermissionManagementModal.razor.cs new file mode 100644 index 0000000000..0d2b3d4192 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/Components/PermissionManagementModal.razor.cs @@ -0,0 +1,375 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Options; +using MudBlazor; +using Volo.Abp.AspNetCore.Components.Messages; +using Volo.Abp.AspNetCore.Components.Web.Configuration; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Localization; +using Volo.Abp.PermissionManagement.Localization; + +namespace Volo.Abp.PermissionManagement.Blazor.MudBlazor.Components; + +public partial class PermissionManagementModal +{ + [Inject] protected IPermissionAppService PermissionAppService { get; set; } = default!; + [Inject] protected ICurrentApplicationConfigurationCacheResetService CurrentApplicationConfigurationCacheResetService { get; set; } = default!; + [Inject] protected IOptions LocalizationOptions { get; set; } = default!; + [Inject] protected IDialogService DialogService { get; set; } = default!; + [Inject] protected ISnackbar Snackbar { get; set; } = default!; + + protected bool _isVisible; + + protected string? _providerName; + protected string? _providerKey; + + protected string? _entityDisplayName; + protected List? _allGroups; + protected List? _groups; + + protected int _activeTabIndex = 0; + + protected bool _selectAllDisabled; + + protected string? _permissionGroupSearchText; + + protected bool GrantAll { get; set; } + protected bool GrantAny { get; set; } + + protected Dictionary _permissionDepths = new Dictionary(); + + public PermissionManagementModal() + { + LocalizationResource = typeof(AbpPermissionManagementResource); + } + + public virtual async Task OpenAsync(string providerName, string providerKey, string? entityDisplayName = null) + { + try + { + _providerName = providerName; + _providerKey = providerKey; + _permissionGroupSearchText = null; + + var result = await PermissionAppService.GetAsync(_providerName, _providerKey); + + _entityDisplayName = entityDisplayName ?? result.EntityDisplayName; + _allGroups = result.Groups.OrderBy(x => x.DisplayName).ToList(); + _groups = _allGroups.ToList(); + + NormalizePermissionGroup(); + + GrantAll = _allGroups.SelectMany(x => x.Permissions).All(p => p.IsGranted); + GrantAny = !GrantAll && _allGroups.SelectMany(x => x.Permissions).Any(p => p.IsGranted); + + _isVisible = true; + await InvokeAsync(StateHasChanged); + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + protected virtual async Task GrantAllAsync(bool grantAll) + { + GrantAll = grantAll; + GrantAny = false; + + if (_allGroups == null) + { + return; + } + + await ResetSearchTextAsync(); + + foreach (var permission in _allGroups.SelectMany(x => x.Permissions)) + { + if (IsDisabledPermission(permission)) + { + continue; + } + + permission.IsGranted = grantAll; + } + + await InvokeAsync(StateHasChanged); + } + + protected virtual void NormalizePermissionGroup(bool checkDisabledPermissions = true) + { + if (_groups == null) + { + return; + } + + _selectAllDisabled = _groups.All(IsPermissionGroupDisabled); + + foreach (var group in _groups) + { + SetPermissionDepths(group.Permissions, null, 0); + } + + if (_groups.Count != 0) + { + _activeTabIndex = 0; + } + } + + protected Task CloseModal() + { + _isVisible = false; + return InvokeAsync(StateHasChanged); + } + + protected virtual async Task SaveAsync() + { + try + { + if (_allGroups == null) + { + return; + } + + var updateDto = new UpdatePermissionsDto + { + Permissions = _allGroups + .SelectMany(g => g.Permissions) + .Select(p => new UpdatePermissionDto { IsGranted = p.IsGranted, Name = p.Name }) + .ToArray() + }; + + if (!updateDto.Permissions.Any(x => x.IsGranted)) + { + var confirmed = await DialogService.ShowMessageBox( + L["Warning"], + L["SaveWithoutAnyPermissionsWarningMessage"], + yesText: L["Yes"], + cancelText: L["Cancel"]); + + if (confirmed != true) + { + return; + } + } + + await PermissionAppService.UpdateAsync(_providerName!, _providerKey!, updateDto); + + Guid? userId = null; + if (_providerName == UserPermissionValueProvider.ProviderName && Guid.TryParse(_providerKey, out var parsedUserId)) + { + userId = parsedUserId; + } + + await CurrentApplicationConfigurationCacheResetService.ResetAsync(userId); + + _isVisible = false; + await InvokeAsync(StateHasChanged); + Snackbar.Add(L["SavedSuccessfully"], Severity.Success); + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + protected virtual string GetNormalizedGroupName(string name) + { + return "PermissionGroup_" + name.Replace(".", "_"); + } + + protected virtual void SetPermissionDepths(List permissions, string? currentParent, int currentDepth) + { + foreach (var item in permissions) + { + if (item.ParentName == currentParent) + { + _permissionDepths[item.Name] = currentDepth; + SetPermissionDepths(permissions, item.Name, currentDepth + 1); + } + } + } + + protected virtual int GetPermissionDepthOrDefault(string name) + { + return _permissionDepths.GetValueOrDefault(name, 0); + } + + protected virtual async Task GroupGrantAllChanged(bool value, PermissionGroupDto permissionGroup) + { + foreach (var permission in permissionGroup.Permissions) + { + if (!IsDisabledPermission(permission)) + { + SetPermissionGrant(permission, value); + } + } + + if (_allGroups != null) + { + GrantAll = _allGroups.SelectMany(x => x.Permissions).All(p => p.IsGranted); + GrantAny = !GrantAll && _allGroups.SelectMany(x => x.Permissions).Any(p => p.IsGranted); + } + await InvokeAsync(StateHasChanged); + } + + protected virtual async Task PermissionChanged(bool value, PermissionGroupDto permissionGroup, PermissionGrantInfoDto permission) + { + SetPermissionGrant(permission, value); + + if (value) + { + SetParentPermissionGrant(permissionGroup, permission); + } + else + { + var childPermissions = GetChildPermissions(permissionGroup, permission); + + foreach (var childPermission in childPermissions) + { + SetPermissionGrant(childPermission, false); + } + } + + if (_allGroups != null) + { + GrantAll = _allGroups.SelectMany(x => x.Permissions).All(p => p.IsGranted); + GrantAny = !GrantAll && _allGroups.SelectMany(x => x.Permissions).Any(p => p.IsGranted); + } + await InvokeAsync(StateHasChanged); + } + + private void SetParentPermissionGrant(PermissionGroupDto permissionGroup, PermissionGrantInfoDto permission) + { + if (permission.ParentName == null) + { + return; + } + + var parentPermission = GetParentPermission(permissionGroup, permission); + SetPermissionGrant(parentPermission, true); + + SetParentPermissionGrant(permissionGroup, parentPermission); + } + + private void SetPermissionGrant(PermissionGrantInfoDto permission, bool value) + { + if (permission.IsGranted == value) + { + return; + } + + permission.IsGranted = value; + } + + protected virtual PermissionGrantInfoDto GetParentPermission(PermissionGroupDto permissionGroup, PermissionGrantInfoDto permission) + { + return permissionGroup.Permissions.First(x => x.Name == permission.ParentName); + } + + protected virtual List GetChildPermissions(PermissionGroupDto permissionGroup, PermissionGrantInfoDto permission) + { + var childPermissions = new List(); + GetChildPermissions(childPermissions, permissionGroup.Permissions, permission); + return childPermissions; + } + + protected virtual void GetChildPermissions(List allChildPermissions, List permissions, PermissionGrantInfoDto permission) + { + var childPermissions = permissions.Where(x => x.ParentName == permission.Name).ToList(); + if (childPermissions.Count == 0) + { + return; + } + + allChildPermissions.AddRange(childPermissions); + + foreach (var childPermission in childPermissions) + { + GetChildPermissions(allChildPermissions, permissions, childPermission); + } + } + + protected virtual bool IsDisabledPermission(PermissionGrantInfoDto permissionGrantInfo) + { + if (!permissionGrantInfo.IsEditable) + { + return true; + } + + return permissionGrantInfo.IsGranted && + permissionGrantInfo.GrantedProviders.Any() && + permissionGrantInfo.GrantedProviders.All(p => p.ProviderName != _providerName); + } + + protected virtual string GetShownName(PermissionGrantInfoDto permissionGrantInfo) + { + if (permissionGrantInfo.GrantedProviders.All(p => p.ProviderName == _providerName)) + { + return permissionGrantInfo.DisplayName; + } + + var grantedByOtherProviders = permissionGrantInfo.GrantedProviders + .Where(p => p.ProviderName != _providerName) + .ToList(); + + if (!grantedByOtherProviders.Any()) + { + return permissionGrantInfo.DisplayName; + } + + return string.Format( + "{0} ({1})", + permissionGrantInfo.DisplayName, + grantedByOtherProviders + .Select(p => p.ProviderName) + .JoinAsString(", ") + ); + } + + protected virtual bool IsPermissionGroupDisabled(PermissionGroupDto group) + { + return group.Permissions.All(IsDisabledPermission); + } + + protected virtual async Task ResetSearchTextAsync() + { + _permissionGroupSearchText = string.Empty; + if (_allGroups != null) + { + _groups = _permissionGroupSearchText.IsNullOrWhiteSpace() + ? _allGroups.ToList() + : _allGroups.Where(x => x.DisplayName.Contains(_permissionGroupSearchText!, StringComparison.OrdinalIgnoreCase) || x.Permissions.Any(permission => permission.DisplayName.Contains(_permissionGroupSearchText!, StringComparison.OrdinalIgnoreCase))).ToList(); + + NormalizePermissionGroup(false); + } + + await InvokeAsync(StateHasChanged); + } + + protected virtual async Task OnPermissionGroupSearchTextChangedAsync(string? value) + { + if (value == _permissionGroupSearchText) + { + return; + } + + _permissionGroupSearchText = value; + if (_allGroups != null) + { + _groups = _permissionGroupSearchText.IsNullOrWhiteSpace() + ? _allGroups.ToList() + : _allGroups.Where(x => x.DisplayName.Contains(_permissionGroupSearchText!, StringComparison.OrdinalIgnoreCase) || x.Permissions.Any(permission => permission.DisplayName.Contains(_permissionGroupSearchText!, StringComparison.OrdinalIgnoreCase))).ToList(); + + GrantAll = _allGroups.SelectMany(x => x.Permissions).All(p => p.IsGranted); + GrantAny = !GrantAll && _allGroups.SelectMany(x => x.Permissions).Any(p => p.IsGranted); + + NormalizePermissionGroup(false); + } + + await InvokeAsync(StateHasChanged); + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/Components/ResourcePermissionManagementModal.razor b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/Components/ResourcePermissionManagementModal.razor new file mode 100644 index 0000000000..011c5dd926 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/Components/ResourcePermissionManagementModal.razor @@ -0,0 +1,154 @@ +@using global::MudBlazor +@inherits Volo.Abp.AspNetCore.Components.AbpComponentBase + + + + @L["ResourcePermissions"] - @ResourceDisplayName + + + @if (HasAnyResourcePermission && HasAnyResourceProviderKeyLookupService) + { +
+ + @L["AddResourcePermission"] + +
+ + + + + + + @L["Edit"] + + + @L["Delete"] + + + + + + + + + @context.Item.ProviderName + + + @context.Item.ProviderDisplayName + + + + + @foreach (var permission in context.Item.Permissions) + { + + @permission.DisplayName + + } + + + + + @L["NoDataAvailableInDatatable"] + + + } + else + { + + @if (!HasAnyResourcePermission) + { + @L["NoResourcePermissionFound"] + } + else if (!HasAnyResourceProviderKeyLookupService) + { + @L["NoResourceProviderKeyLookupServiceFound"] + } + + } +
+ + @L["Close"] + +
+ +@* Create Resource Permission Dialog *@ +@if (_createDialogVisible) +{ + + + @L["AddResourcePermission"] + + + + @L["ResourcePermissionTarget"] + + @foreach (var keyLookupService in ResourceProviderKeyLookupServices) + { + @keyLookupService.DisplayName + } + + + @L["ResourcePermissionPermissions"] + +
+ @foreach (var permission in CreateEntity.Permissions) + { + + } +
+
+
+ + @L["Cancel"] + @L["Save"] + +
+} + +@* Edit Resource Permission Dialog *@ +@if (_editDialogVisible) +{ + + + @L["UpdateResourcePermission"] + + + + @L["ResourcePermissionPermissions"] + +
+ @foreach (var permission in EditEntity.Permissions) + { + + } +
+
+
+ + @L["Cancel"] + @L["Save"] + +
+} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/Components/ResourcePermissionManagementModal.razor.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/Components/ResourcePermissionManagementModal.razor.cs new file mode 100644 index 0000000000..1df024471e --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/Components/ResourcePermissionManagementModal.razor.cs @@ -0,0 +1,337 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using MudBlazor; +using Volo.Abp.AspNetCore.Components.Messages; +using Volo.Abp.PermissionManagement.Localization; + +namespace Volo.Abp.PermissionManagement.Blazor.MudBlazor.Components; + +public partial class ResourcePermissionManagementModal +{ + [Inject] protected IPermissionAppService PermissionAppService { get; set; } = default!; + + [Inject] protected IUiMessageService UiMessageService { get; set; } = default!; + + [Inject] protected ISnackbar Snackbar { get; set; } = default!; + + [Inject] protected IDialogService DialogService { get; set; } = default!; + + protected bool _isVisible; + protected bool _createDialogVisible; + protected bool _editDialogVisible; + protected MudForm? _createFormRef; + protected MudForm? _editFormRef; + + public bool HasAnyResourcePermission { get; set; } + public bool HasAnyResourceProviderKeyLookupService { get; set; } + protected string? ResourceName { get; set; } + protected string? ResourceKey { get; set; } + protected string? ResourceDisplayName { get; set; } + protected int PageSize { get; set; } = 10; + + protected CreateModel CreateEntity { get; set; } = new CreateModel + { + Permissions = new List() + }; + + protected SearchProviderKeyInfo? _selectedProviderKey; + protected string? ProviderKey => _selectedProviderKey?.ProviderKey; + protected string? ProviderDisplayName => _selectedProviderKey?.ProviderDisplayName; + + public GetResourcePermissionDefinitionListResultDto ResourcePermissionDefinitions { get; set; } = new() + { + Permissions = new List() + }; + protected string? CurrentLookupService { get; set; } + protected List ResourceProviderKeyLookupServices { get; set; } = new(); + protected List ProviderKeys { get; set; } = new(); + protected GetResourcePermissionListResultDto ResourcePermissionList = new() + { + Permissions = new List() + }; + + protected EditModel EditEntity { get; set; } = new EditModel + { + Permissions = new List() + }; + + protected bool _grantAllCreate; + protected bool _grantAllEdit; + + public ResourcePermissionManagementModal() + { + LocalizationResource = typeof(AbpPermissionManagementResource); + } + + public virtual async Task OpenAsync(string resourceName, string resourceKey, string resourceDisplayName) + { + try + { + ResourceName = resourceName; + ResourceKey = resourceKey; + ResourceDisplayName = resourceDisplayName; + + ResourcePermissionDefinitions = await PermissionAppService.GetResourceDefinitionsAsync(ResourceName); + ResourceProviderKeyLookupServices = (await PermissionAppService.GetResourceProviderKeyLookupServicesAsync(ResourceName)).Providers; + + HasAnyResourcePermission = ResourcePermissionDefinitions.Permissions.Any(); + if (HasAnyResourcePermission) + { + HasAnyResourceProviderKeyLookupService = ResourceProviderKeyLookupServices.Count > 0; + } + + await InvokeAsync(StateHasChanged); + + ResourcePermissionList = await PermissionAppService.GetResourceAsync(ResourceName, ResourceKey); + + _isVisible = true; + await InvokeAsync(StateHasChanged); + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + protected virtual Task CloseModal() + { + _isVisible = false; + return InvokeAsync(StateHasChanged); + } + + protected virtual async Task OpenCreateDialogAsync() + { + CurrentLookupService = ResourceProviderKeyLookupServices.FirstOrDefault()?.Name; + + _selectedProviderKey = null; + ProviderKeys = new List(); + if (_createFormRef != null) + { + await _createFormRef.ResetAsync(); + } + + CreateEntity = new CreateModel + { + Permissions = ResourcePermissionDefinitions.Permissions.Select(x => new ResourcePermissionModel + { + Name = x.Name, + DisplayName = x.DisplayName, + IsGranted = false + }).ToList() + }; + + _grantAllCreate = false; + _createDialogVisible = true; + await InvokeAsync(StateHasChanged); + } + + protected virtual async Task SelectedProviderKeyChanged(SearchProviderKeyInfo? value) + { + _selectedProviderKey = value; + + if (value != null && CurrentLookupService != null) + { + var permissionGrants = await PermissionAppService.GetResourceByProviderAsync(ResourceName!, ResourceKey!, CurrentLookupService, value.ProviderKey); + foreach (var permission in CreateEntity.Permissions) + { + permission.IsGranted = permissionGrants.Permissions.Any(p => p.Name == permission.Name && p.Providers.Contains(CurrentLookupService) && p.IsGranted); + } + + _grantAllCreate = CreateEntity.Permissions.All(x => x.IsGranted); + } + + await InvokeAsync(StateHasChanged); + } + + private async Task> SearchProviderKeyAsync(string value, CancellationToken token) + { + if (value.IsNullOrWhiteSpace() || CurrentLookupService == null) + { + ProviderKeys = new List(); + return ProviderKeys; + } + + ProviderKeys = (await PermissionAppService.SearchResourceProviderKeyAsync(ResourceName!, CurrentLookupService, value, 1)).Keys; + + return ProviderKeys; + } + + protected virtual async Task GrantAllCreateAsync(bool value) + { + _grantAllCreate = value; + foreach (var permission in CreateEntity.Permissions) + { + permission.IsGranted = value; + } + await InvokeAsync(StateHasChanged); + } + + protected virtual async Task GrantAllEditAsync(bool value) + { + _grantAllEdit = value; + foreach (var permission in EditEntity.Permissions) + { + permission.IsGranted = value; + } + await InvokeAsync(StateHasChanged); + } + + protected virtual async Task OpenEditDialogAsync(ResourcePermissionGrantInfoDto permission) + { + var resourcePermissions = await PermissionAppService.GetResourceByProviderAsync(ResourceName!, ResourceKey!, permission.ProviderName, permission.ProviderKey); + EditEntity = new EditModel + { + ProviderName = permission.ProviderName, + ProviderKey = permission.ProviderKey, + Permissions = resourcePermissions.Permissions.Select(x => new ResourcePermissionModel + { + Name = x.Name, + DisplayName = x.DisplayName, + IsGranted = x.IsGranted + }).ToList() + }; + + _grantAllEdit = EditEntity.Permissions.All(x => x.IsGranted); + _editDialogVisible = true; + await InvokeAsync(StateHasChanged); + } + + protected virtual async Task CloseCreateDialogAsync() + { + _createDialogVisible = false; + await InvokeAsync(StateHasChanged); + } + + protected virtual async Task CloseEditDialogAsync() + { + _editDialogVisible = false; + await InvokeAsync(StateHasChanged); + } + + protected virtual async Task OnLookupServiceCheckedValueChanged(string? value) + { + CurrentLookupService = value; + _selectedProviderKey = null; + ProviderKeys = new List(); + if (_createFormRef != null) + { + await _createFormRef.ResetAsync(); + } + await InvokeAsync(StateHasChanged); + } + + protected virtual async Task CreateResourcePermissionAsync() + { + if (_createFormRef == null) + { + return; + } + + await _createFormRef.Validate(); + if (!_createFormRef.IsValid) + { + return; + } + + if (ProviderKey == null || CurrentLookupService == null) + { + return; + } + + await PermissionAppService.UpdateResourceAsync( + ResourceName!, + ResourceKey!, + new UpdateResourcePermissionsDto + { + ProviderName = CurrentLookupService, + ProviderKey = ProviderKey, + Permissions = CreateEntity.Permissions.Where(p => p.IsGranted).Select(p => p.Name).ToList() + } + ); + + await CloseCreateDialogAsync(); + ResourcePermissionList = await PermissionAppService.GetResourceAsync(ResourceName!, ResourceKey!); + await InvokeAsync(StateHasChanged); + Snackbar.Add(L["SavedSuccessfully"], Severity.Success); + } + + protected virtual async Task UpdateResourcePermissionAsync() + { + if (_editFormRef == null) + { + return; + } + + await _editFormRef.Validate(); + if (!_editFormRef.IsValid) + { + return; + } + + await PermissionAppService.UpdateResourceAsync( + ResourceName!, + ResourceKey!, + new UpdateResourcePermissionsDto + { + ProviderName = EditEntity.ProviderName, + ProviderKey = EditEntity.ProviderKey, + Permissions = EditEntity.Permissions.Where(p => p.IsGranted).Select(p => p.Name).ToList() + } + ); + + await CloseEditDialogAsync(); + ResourcePermissionList = await PermissionAppService.GetResourceAsync(ResourceName!, ResourceKey!); + await InvokeAsync(StateHasChanged); + Snackbar.Add(L["SavedSuccessfully"], Severity.Success); + } + + protected virtual async Task DeleteResourcePermissionAsync(ResourcePermissionGrantInfoDto permission) + { + var confirmed = await DialogService.ShowMessageBox( + L["AreYouSure"], + L["ResourcePermissionDeletionConfirmationMessage"], + yesText: L["Yes"], + cancelText: L["Cancel"]); + + if (confirmed == true) + { + await PermissionAppService.DeleteResourceAsync( + ResourceName!, + ResourceKey!, + permission.ProviderName, + permission.ProviderKey + ); + + ResourcePermissionList = await PermissionAppService.GetResourceAsync(ResourceName!, ResourceKey!); + Snackbar.Add(L["DeletedSuccessfully"], Severity.Success); + await InvokeAsync(StateHasChanged); + } + } + + public class CreateModel + { + public List Permissions { get; set; } = new(); + } + + public class EditModel + { + public string ProviderName { get; set; } = string.Empty; + + public string ProviderKey { get; set; } = string.Empty; + + public List Permissions { get; set; } = new(); + } + + public class ResourcePermissionModel + { + public string Name { get; set; } = string.Empty; + + public string DisplayName { get; set; } = string.Empty; + + public bool IsGranted { get; set; } + } +} diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/FodyWeavers.xml b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/FodyWeavers.xsd b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/Volo.Abp.PermissionManagement.Blazor.MudBlazor.csproj b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/Volo.Abp.PermissionManagement.Blazor.MudBlazor.csproj new file mode 100644 index 0000000000..d12603d05f --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/Volo.Abp.PermissionManagement.Blazor.MudBlazor.csproj @@ -0,0 +1,18 @@ + + + + + + + net10.0 + + + + + + + + + + + diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/_Imports.razor b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/_Imports.razor new file mode 100644 index 0000000000..bfec4381b3 --- /dev/null +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor/_Imports.razor @@ -0,0 +1,3 @@ +@using Microsoft.AspNetCore.Components.Web +@using Volo.Abp.AspNetCore.Components.Web +@using MudBlazor diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer.cs index 707a3e89ea..22d0b0e179 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer.cs @@ -93,7 +93,7 @@ public class PermissionDefinitionSerializer : IPermissionDefinitionSerializer, I permission.IsEnabled, permission.MultiTenancySide, SerializeProviders(permission.Providers), - SerializeStateCheckers(permission.StateCheckers) + SerializeStateCheckers(permission, permission.StateCheckers) ); foreach (var property in permission.Properties) @@ -112,8 +112,10 @@ public class PermissionDefinitionSerializer : IPermissionDefinitionSerializer, I : null; } - protected virtual string SerializeStateCheckers(List> stateCheckers) + protected virtual string SerializeStateCheckers( + PermissionDefinition permission, + List> stateCheckers) { - return StateCheckerSerializer.Serialize(stateCheckers); + return StateCheckerSerializer.Serialize(stateCheckers, permission); } } diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer_Tests.cs index 4dca621a63..c395398bb7 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer_Tests.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/PermissionDefinitionSerializer_Tests.cs @@ -57,7 +57,7 @@ public class PermissionDefinitionSerializer_Tests : PermissionTestBase .WithProperty("CustomProperty2", "CustomValue2") .RequireAuthenticated() //For for testing, not so meaningful .RequireGlobalFeatures("GlobalFeature1", "GlobalFeature2") - .RequireFeatures("Feature1", "Feature2") + .RequireFeatures(requiresAll: true, batchCheck: false, "Feature1", "Feature2") .RequirePermissions(requiresAll: false, batchCheck: false,"Permission2", "Permission3"); // Act @@ -79,6 +79,74 @@ public class PermissionDefinitionSerializer_Tests : PermissionTestBase } + [Fact] + public async Task Serialize_Permission_With_Default_Batch_RequireFeatures() + { + var context = new PermissionDefinitionContext(null); + var group = context.AddGroup("AbpAuditLogging"); + var permission = group.AddPermission( + "AbpAuditLogging.Settings", + new LocalizableString(typeof(AbpPermissionManagementResource), "Permission1")) + .RequireFeatures("AbpAuditLogging.SettingManagement"); + + var record = await _serializer.SerializeAsync(permission, group); + + record.StateCheckers.ShouldBe( + "[{\"T\":\"F\",\"A\":true,\"N\":[\"AbpAuditLogging.SettingManagement\"]}]"); + } + + [Fact] + public async Task Serialize_Permission_With_Default_Batch_RequireFeatures_Picks_Per_State_Model() + { + var context = new PermissionDefinitionContext(null); + var group = context.AddGroup("Group1"); + + var permA = group.AddPermission("PermA", new LocalizableString(typeof(AbpPermissionManagementResource), "Permission1")) + .RequireFeatures("FeatureForA"); + var permB = group.AddPermission("PermB", new LocalizableString(typeof(AbpPermissionManagementResource), "Permission1")) + .RequireFeatures("FeatureForB1", "FeatureForB2"); + + var recordA = await _serializer.SerializeAsync(permA, group); + var recordB = await _serializer.SerializeAsync(permB, group); + + recordA.StateCheckers.ShouldBe("[{\"T\":\"F\",\"A\":true,\"N\":[\"FeatureForA\"]}]"); + recordB.StateCheckers.ShouldBe("[{\"T\":\"F\",\"A\":true,\"N\":[\"FeatureForB1\",\"FeatureForB2\"]}]"); + } + + [Fact] + public async Task Serialize_Permission_With_Default_Batch_RequirePermissions() + { + var context = new PermissionDefinitionContext(null); + var group = context.AddGroup("Group1"); + var permission = group.AddPermission( + "Permission1", + new LocalizableString(typeof(AbpPermissionManagementResource), "Permission1")) + .RequirePermissions("OtherPermission1", "OtherPermission2"); + + var record = await _serializer.SerializeAsync(permission, group); + + record.StateCheckers.ShouldBe( + "[{\"T\":\"P\",\"A\":true,\"N\":[\"OtherPermission1\",\"OtherPermission2\"]}]"); + } + + [Fact] + public async Task Serialize_Permission_With_Default_Batch_RequirePermissions_Picks_Per_State_Model() + { + var context = new PermissionDefinitionContext(null); + var group = context.AddGroup("Group1"); + + var permA = group.AddPermission("PermA", new LocalizableString(typeof(AbpPermissionManagementResource), "Permission1")) + .RequirePermissions("OtherForA"); + var permB = group.AddPermission("PermB", new LocalizableString(typeof(AbpPermissionManagementResource), "Permission1")) + .RequirePermissions(requiresAll: false, "OtherForB1", "OtherForB2"); + + var recordA = await _serializer.SerializeAsync(permA, group); + var recordB = await _serializer.SerializeAsync(permB, group); + + recordA.StateCheckers.ShouldBe("[{\"T\":\"P\",\"A\":true,\"N\":[\"OtherForA\"]}]"); + recordB.StateCheckers.ShouldBe("[{\"T\":\"P\",\"A\":false,\"N\":[\"OtherForB1\",\"OtherForB2\"]}]"); + } + [Fact] public async Task Serialize_Complex_Resource_Permission_Definition() { @@ -96,7 +164,7 @@ public class PermissionDefinitionSerializer_Tests : PermissionTestBase .WithProperty("CustomProperty2", "CustomValue2") .RequireAuthenticated() //For for testing, not so meaningful .RequireGlobalFeatures("GlobalFeature1", "GlobalFeature2") - .RequireFeatures("Feature1", "Feature2") + .RequireFeatures(requiresAll: true, batchCheck: false, "Feature1", "Feature2") .RequirePermissions(requiresAll: false, batchCheck: false,"Permission2", "Permission3"); // Act diff --git a/modules/setting-management/Volo.Abp.SettingManagement.slnx b/modules/setting-management/Volo.Abp.SettingManagement.slnx index f44b64c8e0..5b73c332f0 100644 --- a/modules/setting-management/Volo.Abp.SettingManagement.slnx +++ b/modules/setting-management/Volo.Abp.SettingManagement.slnx @@ -5,6 +5,9 @@ + + + diff --git a/modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/package.json b/modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/package.json index 4de9410e3b..50a8ad3d4c 100644 --- a/modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/package.json +++ b/modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/package.json @@ -3,6 +3,6 @@ "name": "demo-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.4.0" } } diff --git a/modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/yarn.lock b/modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/yarn.lock index 2e8f03c042..6cbad6797a 100644 --- a/modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/yarn.lock +++ b/modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/yarn.lock @@ -2,186 +2,186 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.3.0.tgz#bf48e8fa571067d15bc19bdd3f13509acf58f9c7" - integrity sha512-t9pHqrXaCDYcsdrz0VPKT28ujnnU+EVxyVDogQSRRpKsDoLhNcOD8NI3swdAWBQZ46YSCDfeDR+gfwjC3yqpXQ== +"@abp/aspnetcore.mvc.ui.theme.basic@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.4.0.tgz#c0414583f1c1fca14b0133818a63434b4fbf7df0" + integrity sha512-sOg3URmFRO/18D734B56jdFuC4LflGXXpw4w1Rdd77BJxugynxcmwNOQbLYFvMfqj0eKqDOH3E4yEcmS2IT1BQ== dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.shared" "~10.4.0" -"@abp/aspnetcore.mvc.ui.theme.shared@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.3.0.tgz#bcf72b5dd1dea285c3fa7cb1b6ce27c29b985ff7" - integrity sha512-X5xmMSxnIzNJPexvKR9y6jyn0v5n2wscN1SECG/bMI1YdKugMlkfkSuyUmLn+RX9/YdcK0jOsdMhfuzs5mgAOA== +"@abp/aspnetcore.mvc.ui.theme.shared@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.4.0.tgz#49002e55db00c98931d27357f7ea40eeab09e2e8" + integrity sha512-bNQ+WOnsfQhb98Z9YO0zZsW2K8ZEgOQ+7/86QaqoYTf+ZFvLJsICmNuHryNkCGRM1XJf9EaDZ3qSOwoOGQwS9A== dependencies: - "@abp/aspnetcore.mvc.ui" "~10.3.0" - "@abp/bootstrap" "~10.3.0" - "@abp/bootstrap-datepicker" "~10.3.0" - "@abp/bootstrap-daterangepicker" "~10.3.0" - "@abp/datatables.net-bs5" "~10.3.0" - "@abp/font-awesome" "~10.3.0" - "@abp/jquery-validation-unobtrusive" "~10.3.0" - "@abp/lodash" "~10.3.0" - "@abp/luxon" "~10.3.0" - "@abp/malihu-custom-scrollbar-plugin" "~10.3.0" - "@abp/moment" "~10.3.0" - "@abp/select2" "~10.3.0" - "@abp/sweetalert2" "~10.3.0" - "@abp/timeago" "~10.3.0" - -"@abp/aspnetcore.mvc.ui@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.3.0.tgz#0d125efd172b6772c516eae82cfe35f144da73fc" - integrity sha512-kp0XdwrlNz+NeLdxSJ6xdUaB5PM7xdbiLsIyTUcwa7AUKBtcVU6yq6qttbk9VyO0KqDgLywSuEL+9PkFhthuPg== + "@abp/aspnetcore.mvc.ui" "~10.4.0" + "@abp/bootstrap" "~10.4.0" + "@abp/bootstrap-datepicker" "~10.4.0" + "@abp/bootstrap-daterangepicker" "~10.4.0" + "@abp/datatables.net-bs5" "~10.4.0" + "@abp/font-awesome" "~10.4.0" + "@abp/jquery-validation-unobtrusive" "~10.4.0" + "@abp/lodash" "~10.4.0" + "@abp/luxon" "~10.4.0" + "@abp/malihu-custom-scrollbar-plugin" "~10.4.0" + "@abp/moment" "~10.4.0" + "@abp/select2" "~10.4.0" + "@abp/sweetalert2" "~10.4.0" + "@abp/timeago" "~10.4.0" + +"@abp/aspnetcore.mvc.ui@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.4.0.tgz#24e40fdc101c411f29a87adb77baa642299e979c" + integrity sha512-0qVFthS4f4fkCv//UY2GseO+NX+XYqlamZJJwOTmv1UXFW6LxAXxuEQqWYVfFKzwk2lbkXDUcgP8NhhQ9xp3Sw== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.3.0.tgz#032aaf0e474f971b9f90ca7fb83ff7ca4d7be933" - integrity sha512-7mRuRRIE4R0yw1cZuoIskgrqZPUa5/SJzse0K6u+/QDqvyLjjOsyE51WuqAXxlVfKpLp/fBvXZwnCDeCRU9iZQ== +"@abp/bootstrap-datepicker@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.4.0.tgz#a392fd5ce31c86e9fc07f7dedbd0c56a02af341e" + integrity sha512-kOpuCEO64zoJ7a9JASsCLFZIjEYgN0GG/oApS2L729pv6/oEhcupWpwrnvDUhsdGOezXwLIsgTzc6tiG3HxwGA== dependencies: bootstrap-datepicker "^1.10.1" -"@abp/bootstrap-daterangepicker@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.3.0.tgz#b2a9171595ee311c32f57460013870cde67c6b7f" - integrity sha512-ejLu3sWhfNDlsXeUXAQudZvPv0xM4McxIK65vuVEZiKIwFSnrLYil8jBi12EJJmAw5yYA/KC0EdyG82E+AWpmA== +"@abp/bootstrap-daterangepicker@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.4.0.tgz#95f68d3f488a12ede3c12352637b48782eeb031a" + integrity sha512-HX4HutkbPDQWBJNPDozpT1Um9mHcfV+RL+ukwEy0N4e7nD8VJyqLT/a+/FzQN7iUkwV+aNR2v5c0W+dgyqTaEA== dependencies: - "@abp/moment" "~10.3.0" + "@abp/moment" "~10.4.0" bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.3.0.tgz#23535c90613271b02fdad3eae1556b67844729e6" - integrity sha512-JnVOeJUyR78oo+QURvaNqa9xGZaVux0PRIpFpd4Qsqiiz9FvJrbCuSoBkkZoj/0eH23WoZ3bJxy0K9LDE19qvg== +"@abp/bootstrap@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.4.0.tgz#64b22bbdbf6b5bda279b21a7bae0652290b1221f" + integrity sha512-WQzXi5h1mej9twrAB6kD4aJtTNfSxpnuo1pxIOu/8GKGWoDayVY2NMQl+MCVel6dST+6EUTmuDRxc2q9qHRxLg== dependencies: - "@abp/core" "~10.3.0" - "@abp/popper.js" "~10.3.0" + "@abp/core" "~10.4.0" + "@abp/popper.js" "~10.4.0" bootstrap "^5.3.8" -"@abp/core@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.3.0.tgz#9960f8179d1fdc42250d0bbba60e379590c8199c" - integrity sha512-DpL4qrsyCUolypUwxKVDDK8bPVhNbYKx6l2ytUjoOu73CwgPoCSGkvHWrmKIsEsmTfOC9J7+GDErh8jWR/5pVA== +"@abp/core@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.4.0.tgz#de0c09e26d1f20451da9430d939beebb5bb6e9dd" + integrity sha512-3yKLFqUtFo5rgBPQxWkNPsdq1bE9VKwm02yB14lu913EIYhP//akizY4uSK08BozocJNveCASQbgd66z4x6v/Q== dependencies: - "@abp/utils" "~10.3.0" + "@abp/utils" "~10.4.0" -"@abp/datatables.net-bs5@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.3.0.tgz#d8711c52f7c03df51d14d1a5f0e6b00f6d9551fc" - integrity sha512-I8rEU98kEhsNZjVdpPvOcG6F4xr1CgIRb0bTf5bfd8ufzxEUKB+xF9bguJIB5kYwjme8QbMlcJ0V/c+N4iewtQ== +"@abp/datatables.net-bs5@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.4.0.tgz#a24eef99399324e4b89a00a204d2c4c2de379da1" + integrity sha512-T4f7e6sR1qHcvygNUNfdPWsK61C3NQORV35Lzpl98eDwY8XLg2/RLAegpFI8GjESVy2+2PCKVqUoSYZtXaMFmw== dependencies: - "@abp/datatables.net" "~10.3.0" + "@abp/datatables.net" "~10.4.0" datatables.net-bs5 "^2.3.4" -"@abp/datatables.net@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.3.0.tgz#f98319d8e226da5aced4d1d94d3ababb11327074" - integrity sha512-imM25WmO1V0hqeG5iPeLvYMID2HX7SG9drz47qilr74MhpY2i2J7HlsvZDonNkjpxzhK4DHpGeQd6mE2QzG7Hw== +"@abp/datatables.net@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.4.0.tgz#ab48f6e3ccae8d0644f0c01011ff98cc6ca16181" + integrity sha512-esIUozaSEa29G7bdWbEZr22fRChaTuGfNNB8rgBHmVKwgkbHSG7qUTgsfGhNz0T2vfDsK3X37n55xmSdE9RElg== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" datatables.net "^2.3.4" -"@abp/font-awesome@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.3.0.tgz#43602605f5633132cc6fe21b6ce5ab19b86bdf27" - integrity sha512-RUOVHxDyT81hp01DhUuSshKvLM+NiTt9VP+O4/rSj+SOaSJAm3wX7eziCKrDi6H41FwhoZL75CifLhcZVyVO4A== +"@abp/font-awesome@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.4.0.tgz#327979191615fff571f0ea2e99d01bbcca5378ce" + integrity sha512-RBzDSi/hrjhLKc7h6Kf9CQqC+LCGvS7h8HrRVaIzWCZSeqTfIMFpixjCUrNgkqWI0bU4NDUKXZ4XCOv4nPVlBg== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" "@fortawesome/fontawesome-free" "^7.0.1" -"@abp/jquery-validation-unobtrusive@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.3.0.tgz#5bc14ed642d48f9eae54761ad39bd9f51beeb005" - integrity sha512-j/9fA7tAs1/t3P2U1kKUiDlHAImmseul6KzvV77sNpnms7g4+x6Fgsa+sMzicSRGOLz7lK5RHdBFyvfKxt7/nA== +"@abp/jquery-validation-unobtrusive@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.4.0.tgz#e0114347344931667f48e0f2de5ea4bd6f9d2a88" + integrity sha512-AJncXVB+eTSizq0fTAwLii9a0GeYFPiY+gw+Ki5pak8Pp9LQivphsW32TqQmPC3qZVuYtd4myUUraWuOTT1Adg== dependencies: - "@abp/jquery-validation" "~10.3.0" + "@abp/jquery-validation" "~10.4.0" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.3.0.tgz#e669a54d28f6c0b863a284322e527a5c65cb7dbf" - integrity sha512-Wo95ZJLS1ScKm7dm3zFlEFUKqXrqo3m9+82DX5mFJUVCHHuBPrhxvDjxNJ18z4gpoOUu08uqxX7LFbphtkRM3g== +"@abp/jquery-validation@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.4.0.tgz#bd1ef0217c2500363f25223dd7316ba919a505b5" + integrity sha512-W9QiGnOpaq6uDDwBy4UEqWrP1p2UegHQShW5Cev9vr6g7Lk97UsS1Agh9lnjbvMzFSAiLl3M9gHmyMD4aU3NPA== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" jquery-validation "^1.21.0" -"@abp/jquery@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.3.0.tgz#3b60bcdfe8f9a33be79db1bea2500837fe09c67d" - integrity sha512-TndX/8bJx5vGAAwIykx2CJUZIFFDkwpD+cUjE45WO2dSrY2M5vNfWiuw95OcLGngO8RaT/gTkFGzPuZqKn9ggg== +"@abp/jquery@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.4.0.tgz#7ed3f1808682717ccfa7c6ac6e8db8a8b648b1e1" + integrity sha512-kjFoVIhqlaIHlEs917sIeP1sHmLWXMn+DVKaCkVQUXC2KVq+yvQRC8AZiFQVxOwLvzd9dFFpGwGZgpWkDfvhvg== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" jquery "~3.7.1" -"@abp/lodash@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.3.0.tgz#bd091fc811d85d2f535802631af6165ac1c8a198" - integrity sha512-7JDU9UlbD+9odhFQjolvyJZ6EQKG5kiL2Pt9T1RMVchNh47iEuZwfi8c+CIxa3YxA3n6lAsZ+8Pmxq4yOku+RQ== +"@abp/lodash@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.4.0.tgz#b6fc25b6e2fd74420ca16a5a13cb8fcd39b89efd" + integrity sha512-RuiIGAqJtSBS+4VAk4cg7Zsfj07B3xkiewCaK5j0xUcyjsCJKL1UdXe2zVNL3oE5bXTNMU7yNQWJS4knv2mfwA== dependencies: - "@abp/core" "~10.3.0" - lodash "^4.17.21" + "@abp/core" "~10.4.0" + lodash "^4.18.1" -"@abp/luxon@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.3.0.tgz#acbd92289456a546607ed4506dc36f71e6da53c9" - integrity sha512-IIi2+odQzuFK4qV1KJ2HtlTewSPby2F9CJFNNtNRMzX/P30dQkAGWpNV5/FZzJwFIihR+zSei5wlu11WpPugsw== +"@abp/luxon@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.4.0.tgz#731d20877ec679fa518b7199128676631d030a90" + integrity sha512-/hF+FBugJMkp/H9WNZxlsOy3Rl4DTeR2hqmRPI0sPcdFOixCziYrwzyHg/2Kx8cI/zHa6GRRt1B4Jz4yjQTvdw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" luxon "^3.7.2" -"@abp/malihu-custom-scrollbar-plugin@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.3.0.tgz#209931d02a994ef448371d6203e9ec74c72b61e6" - integrity sha512-hJjwEExP3hKE1MNDG+pcvoHRFUihh3t/0wCPNaoePFd8+QnJuyQ9U0VA+vdtykTxm8uFNE2uaCgyHffoHmzeZA== +"@abp/malihu-custom-scrollbar-plugin@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.4.0.tgz#e1c207d9c66cce64fcb4fe13c091c4b86b00b6d7" + integrity sha512-LC+imsGQganhKWybvQytATjQMXfQwfCfi6RRWREP+pejKPuHpqHzPd+AjKwZpx500K2/ZTwT62FKJ1uDiJXSKA== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.3.0.tgz#5a5c4ece14d1bc4dd33134f85d1d085733d3c975" - integrity sha512-8DKw2jgC/28nLrB/CR9BiyUSt0pUmWF+bEgAsduln6VrSCHn0aRLqgs+wi/9Ol+1bl7pO2wbJOYg1qbnkDhrAQ== +"@abp/moment@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.4.0.tgz#6737898b90b579ce5e0d683657757725a4def09d" + integrity sha512-WUtzjD6+1JGpgS6Yv2Qo/h4jM77LkkasPrywsetmtj/6zyVQWrkk7tKHTCjhAJtdinctcROwIIcwLsELi0nDHg== dependencies: moment "^2.30.1" -"@abp/popper.js@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-10.3.0.tgz#cb2415e45a905854980f4e864c00c8fe1156a6f9" - integrity sha512-Yfafs50t7DKnShxAl9WPpihZxG5cLAt99rJmIyXBYUQIpkHkxDyUIA5b4gCoVCqKgjFDOxG7i6L+Ovd/2N8k7g== +"@abp/popper.js@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-10.4.0.tgz#9d8d09c4e18020b6a75ba30daf6786606f555e84" + integrity sha512-KR5wGZ/MfR1rOPcmFr5ekraYhOSRul9gqempir69hlqrFKUFqt46F9/wLWyaJ7vlsMU6o4gBo6/jE5tICIpzNw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" "@popperjs/core" "^2.11.8" -"@abp/select2@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.3.0.tgz#199777173ca3e43a5448f8b51b30c3aafe4a3333" - integrity sha512-6sEIYIakVI1DM3eOqOaxwdG8vkyVFBWvVZhoChPZkgIIQ0EH2JLerHv0pePJEl6mQSxOUrikPA8++B2y+sBnQA== +"@abp/select2@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.4.0.tgz#e744988abf81e2194a5850acc4ac1333e9578df8" + integrity sha512-tyAdGzX32joI5vw+MUJGR0iToWCGZrbQg/zKeoCib80Zx5cn+e6GXj6wZyPbsJgVm0jZ7zBweWt3x8HzW9N8Ww== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" select2 "^4.0.13" -"@abp/sweetalert2@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.3.0.tgz#515e0477574b54bc6650b9f5ee193bfc648c715a" - integrity sha512-bY5MXEiKja+vk71ujSn8rpzRC45Ge8zvcca1k1vBJs+oKyRCii//JBxa3H0lagZo5r/QwIqv5ihdyLE1xsuLSg== +"@abp/sweetalert2@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.4.0.tgz#65cb26c8c7c9c02158c9a487969eef033fe04a73" + integrity sha512-kyVMlfnI6MZojJlaRstdHj/CL3olOn4BszjQ155UQaDsGHUu0THmbhpfi4P0q3TCLLzcLM4pkuY3wJHPNl3ZOw== dependencies: - "@abp/core" "~10.3.0" + "@abp/core" "~10.4.0" sweetalert2 "^11.23.0" -"@abp/timeago@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.3.0.tgz#3214a6d000c79504c9fa7cb4e004a967fe46459b" - integrity sha512-Rgcex+jvM9Z9/E0I5Q3wlC2bY5550/+6xRX407k5ho4gxCccYo3c0Gkpiy5pJRFhTwtBgcNzYxArh6NCez+tDg== +"@abp/timeago@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.4.0.tgz#100fc0598e3a605346397f8a395bb96dc70f429d" + integrity sha512-G0ppO7Evai3DdhSg4ozxQpvIke0t3vyg63pNdfOtAPngmuam7WBM1WemBi4u29M84WcSfmk4KuAubXO0aCJgDg== dependencies: - "@abp/jquery" "~10.3.0" + "@abp/jquery" "~10.4.0" timeago "^1.6.7" -"@abp/utils@~10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.3.0.tgz#d88c0d639d69d6210c3070892aac61d5e945c124" - integrity sha512-iNehxiqWJQl2wwE9EbsQANPv/ITMh0yUcPATUlG/McoLWdpPcqobx75NxXcHDqW8ZarecqSktjJDZPvaUaD+jA== +"@abp/utils@~10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.4.0.tgz#75175f798e58643c0dec1e85a502186c811e9226" + integrity sha512-BPc7KGDcbdFZ4GonAt3SOi/gYhG+iXI76TMlAzSKMBRhBU0vxFS8I/pb7U5zX0Gjh5M+aT0l2vA3l+POCHPBfw== dependencies: just-compare "^2.3.0" @@ -268,10 +268,10 @@ just-compare@^2.3.0: resolved "https://registry.yarnpkg.com/just-compare/-/just-compare-2.3.0.tgz#a2adcc1d1940536263275f5a1ef1298bcacfeda7" integrity sha512-6shoR7HDT+fzfL3gBahx1jZG3hWLrhPAf+l7nCwahDdT9XDtosB9kIF0ZrzUp5QY8dJWfQVr5rnsPqsbvflDzg== -lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lodash@^4.18.1: + version "4.18.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" + integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== luxon@^3.7.2: version "3.7.2" diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.Server/AbpSettingManagementBlazorMudBlazorServerModule.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.Server/AbpSettingManagementBlazorMudBlazorServerModule.cs new file mode 100644 index 0000000000..8a100a605e --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.Server/AbpSettingManagementBlazorMudBlazorServerModule.cs @@ -0,0 +1,12 @@ +using Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor; +using Volo.Abp.Modularity; + +namespace Volo.Abp.SettingManagement.Blazor.MudBlazor.Server; + +[DependsOn( + typeof(AbpSettingManagementBlazorMudBlazorModule), + typeof(AbpAspNetCoreComponentsServerThemingMudBlazorModule) +)] +public class AbpSettingManagementBlazorMudBlazorServerModule : AbpModule +{ +} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.Server/FodyWeavers.xml b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.Server/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.Server/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.Server/FodyWeavers.xsd b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.Server/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.Server/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.Server/Volo.Abp.SettingManagement.Blazor.MudBlazor.Server.csproj b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.Server/Volo.Abp.SettingManagement.Blazor.MudBlazor.Server.csproj new file mode 100644 index 0000000000..99c10ba6e0 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.Server/Volo.Abp.SettingManagement.Blazor.MudBlazor.Server.csproj @@ -0,0 +1,15 @@ + + + + + + + net10.0 + + + + + + + + diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly/AbpSettingManagementBlazorMudBlazorWebAssemblyModule.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly/AbpSettingManagementBlazorMudBlazorWebAssemblyModule.cs new file mode 100644 index 0000000000..6ecf4db202 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly/AbpSettingManagementBlazorMudBlazorWebAssemblyModule.cs @@ -0,0 +1,13 @@ +using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor; +using Volo.Abp.Modularity; + +namespace Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly; + +[DependsOn( + typeof(AbpSettingManagementBlazorMudBlazorModule), + typeof(AbpAspNetCoreComponentsWebAssemblyThemingMudBlazorModule), + typeof(AbpSettingManagementHttpApiClientModule) +)] +public class AbpSettingManagementBlazorMudBlazorWebAssemblyModule : AbpModule +{ +} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xml b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xsd b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly/Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly.csproj b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly/Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly.csproj new file mode 100644 index 0000000000..c219f565c9 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly/Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly.csproj @@ -0,0 +1,16 @@ + + + + + + + net10.0 + + + + + + + + + diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/AbpSettingManagementBlazorMudBlazorModule.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/AbpSettingManagementBlazorMudBlazorModule.cs new file mode 100644 index 0000000000..2e5f322047 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/AbpSettingManagementBlazorMudBlazorModule.cs @@ -0,0 +1,51 @@ +using Localization.Resources.AbpUi; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor; +using Volo.Abp.Localization; +using Volo.Abp.Mapperly; +using Volo.Abp.Modularity; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.Menus; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.Settings; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; +using Volo.Abp.SettingManagement.Localization; +using Volo.Abp.UI.Navigation; + +namespace Volo.Abp.SettingManagement.Blazor.MudBlazor; + +[DependsOn( + typeof(AbpMapperlyModule), + typeof(AbpAspNetCoreComponentsWebThemingMudBlazorModule), + typeof(AbpSettingManagementApplicationContractsModule) +)] +public class AbpSettingManagementBlazorMudBlazorModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddMapperlyObjectMapper(); + + Configure(options => + { + options.MenuContributors.Add(new SettingManagementMenuContributor()); + }); + + Configure(options => + { + options.AdditionalAssemblies.Add(typeof(AbpSettingManagementBlazorMudBlazorModule).Assembly); + }); + + Configure(options => + { + options.Contributors.Add(new EmailingPageContributor()); + options.Contributors.Add(new TimeZonePageContributor()); + }); + + Configure(options => + { + options.Resources + .Get() + .AddBaseTypes( + typeof(AbpUiResource) + ); + }); + } +} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/FodyWeavers.xml b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/FodyWeavers.xsd b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/ISettingComponentContributor.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/ISettingComponentContributor.cs new file mode 100644 index 0000000000..b81cd7f78d --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/ISettingComponentContributor.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.SettingManagement.Blazor.MudBlazor; + +public interface ISettingComponentContributor +{ + Task ConfigureAsync(SettingComponentCreationContext context); + + Task CheckPermissionsAsync(SettingComponentCreationContext context); +} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Menus/SettingManagementMenuContributor.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Menus/SettingManagementMenuContributor.cs new file mode 100644 index 0000000000..477f719a21 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Menus/SettingManagementMenuContributor.cs @@ -0,0 +1,63 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.Features; +using Volo.Abp.SettingManagement.Localization; +using Volo.Abp.UI.Navigation; + +namespace Volo.Abp.SettingManagement.Blazor.MudBlazor.Menus; + +public class SettingManagementMenuContributor : IMenuContributor +{ + public async Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name == StandardMenus.Main) + { + await ConfigureMainMenuAsync(context); + } + } + + private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) + { + var settingManagementPageOptions = context.ServiceProvider.GetRequiredService>().Value; + var settingPageCreationContext = new SettingComponentCreationContext(context.ServiceProvider); + if (!settingManagementPageOptions.Contributors.Any() || + !(await CheckAnyOfPagePermissionsGranted(settingManagementPageOptions, settingPageCreationContext)) + ) + { + return; + } + + var l = context.GetLocalizer(); + + /* This may happen if MVC UI is being used in the same application. + * In this case, we are removing the MVC setting management UI. */ + context.Menu.GetAdministration().TryRemoveMenuItem(SettingManagementMenus.GroupName); + + context.Menu + .GetAdministration() + .AddItem( + new ApplicationMenuItem( + SettingManagementMenus.GroupName, + l["Settings"], + "~/setting-management", + icon: "fa fa-cog" + ).RequireFeatures(SettingManagementFeatures.Enable) + ); + } + + protected virtual async Task CheckAnyOfPagePermissionsGranted( + SettingManagementComponentOptions settingManagementComponentOptions, + SettingComponentCreationContext settingComponentCreationContext) + { + foreach (var contributor in settingManagementComponentOptions.Contributors) + { + if (await contributor.CheckPermissionsAsync(settingComponentCreationContext)) + { + return true; + } + } + return false; + } +} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Menus/SettingManagementMenus.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Menus/SettingManagementMenus.cs new file mode 100644 index 0000000000..8c773725e7 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Menus/SettingManagementMenus.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.SettingManagement.Blazor.MudBlazor.Menus; + +public class SettingManagementMenus +{ + public const string GroupName = "SettingManagement"; +} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Pages/SettingManagement/EmailSettingGroup/EmailSettingGroupViewComponent.razor b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Pages/SettingManagement/EmailSettingGroup/EmailSettingGroupViewComponent.razor new file mode 100644 index 0000000000..e306bebe83 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Pages/SettingManagement/EmailSettingGroup/EmailSettingGroupViewComponent.razor @@ -0,0 +1,106 @@ +@using Volo.Abp.SettingManagement.Localization +@using global::MudBlazor +@inherits Volo.Abp.AspNetCore.Components.AbpComponentBase + +@if (EmailSettings != null) +{ + + + + + + + + + + + + + + + + + + + + + @if (!EmailSettings.SmtpUseDefaultCredentials) + { + + + + + + + + + + } + + + +
+ @if (HasSendTestEmailPermission) + { + + @L["SendTestEmail"] + + } + + @L["Save"] + +
+ + @if (HasSendTestEmailPermission) + { + + + @L["SendTestEmail"] + + + + + + + + + + + @L["Cancel"] + @L["Send"] + + + } +} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Pages/SettingManagement/EmailSettingGroup/EmailSettingGroupViewComponent.razor.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Pages/SettingManagement/EmailSettingGroup/EmailSettingGroupViewComponent.razor.cs new file mode 100644 index 0000000000..9e15fad338 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Pages/SettingManagement/EmailSettingGroup/EmailSettingGroupViewComponent.razor.cs @@ -0,0 +1,207 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using MudBlazor; +using Volo.Abp.AspNetCore.Components.Messages; +using Volo.Abp.AspNetCore.Components.Web.Configuration; +using Volo.Abp.Auditing; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.SettingManagement.Localization; + +namespace Volo.Abp.SettingManagement.Blazor.MudBlazor.Pages.SettingManagement.EmailSettingGroup; + +public partial class EmailSettingGroupViewComponent +{ + [Inject] + protected IEmailSettingsAppService EmailSettingsAppService { get; set; } = default!; + + [Inject] + protected IPermissionChecker PermissionChecker { get; set; } = default!; + + [Inject] + private ICurrentApplicationConfigurationCacheResetService CurrentApplicationConfigurationCacheResetService { get; set; } = default!; + + [Inject] + protected IUiMessageService UiMessageService { get; set; } = default!; + + [Inject] + protected ISnackbar Snackbar { get; set; } = default!; + + protected UpdateEmailSettingsViewModel? EmailSettings; + + protected SendTestEmailViewModel SendTestEmailInput = new(); + + protected MudForm? _emailFormRef; + protected MudForm? _testEmailFormRef; + + protected bool _sendTestEmailDialogVisible; + + protected bool HasSendTestEmailPermission { get; set; } + + public EmailSettingGroupViewComponent() + { + ObjectMapperContext = typeof(AbpSettingManagementBlazorMudBlazorModule); + LocalizationResource = typeof(AbpSettingManagementResource); + } + + protected async override Task OnInitializedAsync() + { + try + { + EmailSettings = ObjectMapper.Map(await EmailSettingsAppService.GetAsync()); + HasSendTestEmailPermission = await PermissionChecker.IsGrantedAsync(SettingManagementPermissions.EmailingTest); + SendTestEmailInput = new SendTestEmailViewModel(); + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + protected virtual async Task UpdateSettingsAsync() + { + try + { + if (_emailFormRef == null) + { + return; + } + + await _emailFormRef.Validate(); + if (!_emailFormRef.IsValid) + { + return; + } + + if (EmailSettings == null) + { + return; + } + + await EmailSettingsAppService.UpdateAsync(ObjectMapper.Map(EmailSettings)); + + await CurrentApplicationConfigurationCacheResetService.ResetAsync(); + + Snackbar.Add(L["SavedSuccessfully"], Severity.Success); + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + protected virtual async Task OpenSendTestEmailDialogAsync() + { + try + { + if (_testEmailFormRef != null) + { + await _testEmailFormRef.ResetAsync(); + } + var emailSettings = await EmailSettingsAppService.GetAsync(); + SendTestEmailInput = new SendTestEmailViewModel + { + SenderEmailAddress = emailSettings.DefaultFromAddress, + TargetEmailAddress = CurrentUser.Email, + Subject = L["TestEmailSubject", new Random().Next(1000, 9999)], + Body = L["TestEmailBody"] + }; + + _sendTestEmailDialogVisible = true; + await InvokeAsync(StateHasChanged); + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + protected virtual Task CloseSendTestEmailDialogAsync() + { + _sendTestEmailDialogVisible = false; + return InvokeAsync(StateHasChanged); + } + + protected virtual async Task SendTestEmailAsync() + { + try + { + if (_testEmailFormRef == null) + { + return; + } + + await _testEmailFormRef.Validate(); + if (!_testEmailFormRef.IsValid) + { + return; + } + + await EmailSettingsAppService.SendTestEmailAsync(ObjectMapper.Map(SendTestEmailInput)); + + Snackbar.Add(L["SentSuccessfully"], Severity.Success); + + await CloseSendTestEmailDialogAsync(); + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + public class UpdateEmailSettingsViewModel + { + [MaxLength(256)] + [Display(Name = "SmtpHost")] + public string? SmtpHost { get; set; } + + [Range(1, 65535)] + [Display(Name = "SmtpPort")] + public int SmtpPort { get; set; } + + [MaxLength(1024)] + [Display(Name = "SmtpUserName")] + public string? SmtpUserName { get; set; } + + [MaxLength(1024)] + [DataType(DataType.Password)] + [DisableAuditing] + [Display(Name = "SmtpPassword")] + public string? SmtpPassword { get; set; } + + [MaxLength(1024)] + [Display(Name = "SmtpDomain")] + public string? SmtpDomain { get; set; } + + [Display(Name = "SmtpEnableSsl")] + public bool SmtpEnableSsl { get; set; } + + [Display(Name = "SmtpUseDefaultCredentials")] + public bool SmtpUseDefaultCredentials { get; set; } + + [MaxLength(1024)] + [Required] + [Display(Name = "DefaultFromAddress")] + public string? DefaultFromAddress { get; set; } + + [MaxLength(1024)] + [Required] + [Display(Name = "DefaultFromDisplayName")] + public string? DefaultFromDisplayName { get; set; } + } + + public class SendTestEmailViewModel + { + [Required] + public string? SenderEmailAddress { get; set; } + + [Required] + public string? TargetEmailAddress { get; set; } + + [Required] + public string? Subject { get; set; } + + public string? Body { get; set; } + } +} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Pages/SettingManagement/SettingManagement.razor b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Pages/SettingManagement/SettingManagement.razor new file mode 100644 index 0000000000..785374eb55 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Pages/SettingManagement/SettingManagement.razor @@ -0,0 +1,44 @@ +@page "/setting-management" +@page "/{culture}/setting-management" +@using Microsoft.AspNetCore.Authorization +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Components +@using Volo.Abp.Features +@attribute [Authorize] +@attribute [RequiresFeature(SettingManagementFeatures.Enable)] +@using Microsoft.Extensions.Localization +@using Volo.Abp.UI.Navigation.Localization.Resource +@inject IStringLocalizer LUiNavigation +@using global::MudBlazor + +@* ************************* PAGE HEADER ************************* *@ + + + + + @if (!string.IsNullOrEmpty(SelectedGroup)) + { + + @foreach (var group in SettingComponentCreationContext.Groups) + { + +
+ @{ + SettingItemRenders.Add(builder => + { + builder.OpenComponent(0, group.ComponentType); + builder.CloseComponent(); + }); + } + + @SettingItemRenders.Last() +
+
+ } +
+ } +
+
diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Pages/SettingManagement/SettingManagement.razor.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Pages/SettingManagement/SettingManagement.razor.cs new file mode 100644 index 0000000000..f04e62b9fc --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Pages/SettingManagement/SettingManagement.razor.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Options; +using MudBlazor; +using Volo.Abp.MudBlazorUI; +using Volo.Abp.SettingManagement.Localization; + +namespace Volo.Abp.SettingManagement.Blazor.MudBlazor.Pages.SettingManagement; + +public partial class SettingManagement +{ + [Parameter] + public string? Culture { get; set; } + + [Inject] + protected IServiceProvider ServiceProvider { get; set; } = default!; + + protected SettingComponentCreationContext SettingComponentCreationContext { get; set; } = default!; + + [Inject] + protected IOptions _options { get; set; } = default!; + + [Inject] + protected IStringLocalizer L { get; set; } = default!; + + protected SettingManagementComponentOptions Options => _options.Value; + + protected List SettingItemRenders { get; set; } = new List(); + + protected string? SelectedGroup; + protected List BreadcrumbItems = new(); + + protected int _activeTabIndex = 0; + + protected async override Task OnInitializedAsync() + { + BreadcrumbItems.Add(new BreadcrumbItem(LUiNavigation["Menu:Administration"].Value, null, disabled: true)); + BreadcrumbItems.Add(new BreadcrumbItem(L["Menu:Settings"].Value, null, disabled: true)); + + SettingComponentCreationContext = new SettingComponentCreationContext(ServiceProvider); + + foreach (var contributor in Options.Contributors) + { + await contributor.ConfigureAsync(SettingComponentCreationContext); + } + SettingComponentCreationContext.Normalize(); + SettingItemRenders.Clear(); + + if (SelectedGroup.IsNullOrEmpty() && SettingComponentCreationContext.Groups.Any()) + { + SelectedGroup = GetNormalizedString(SettingComponentCreationContext.Groups.First().Id); + _activeTabIndex = 0; + } + } + + protected virtual string GetNormalizedString(string value) + { + return value.Replace('.', '_'); + } +} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Pages/SettingManagement/TimeZoneSettingGroup/TimeZoneSettingGroupViewComponent.razor b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Pages/SettingManagement/TimeZoneSettingGroup/TimeZoneSettingGroupViewComponent.razor new file mode 100644 index 0000000000..f2689862c6 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Pages/SettingManagement/TimeZoneSettingGroup/TimeZoneSettingGroupViewComponent.razor @@ -0,0 +1,32 @@ +@using Volo.Abp.SettingManagement.Localization +@using global::MudBlazor +@inherits Volo.Abp.AspNetCore.Components.AbpComponentBase + +@if (TimezoneSettings != null) +{ + + + + + @foreach (var item in TimezoneSettings.TimeZoneItems) + { + @item.Name + } + + @L["TimezoneHelpText"] + + +
+ + @L["Save"] + +
+
+} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Pages/SettingManagement/TimeZoneSettingGroup/TimeZoneSettingGroupViewComponent.razor.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Pages/SettingManagement/TimeZoneSettingGroup/TimeZoneSettingGroupViewComponent.razor.cs new file mode 100644 index 0000000000..90481994e7 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Pages/SettingManagement/TimeZoneSettingGroup/TimeZoneSettingGroupViewComponent.razor.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using MudBlazor; +using Volo.Abp.AspNetCore.Components.Messages; +using Volo.Abp.AspNetCore.Components.Web.Configuration; +using Volo.Abp.SettingManagement.Localization; + +namespace Volo.Abp.SettingManagement.Blazor.MudBlazor.Pages.SettingManagement.TimeZoneSettingGroup; + +public partial class TimeZoneSettingGroupViewComponent +{ + [Inject] + protected ITimeZoneSettingsAppService TimeZoneSettingsAppService { get; set; } = default!; + + [Inject] + private ICurrentApplicationConfigurationCacheResetService CurrentApplicationConfigurationCacheResetService { get; set; } = default!; + + [Inject] + protected IUiMessageService UiMessageService { get; set; } = default!; + + [Inject] + protected ISnackbar Snackbar { get; set; } = default!; + + protected UpdateTimezoneSettingsViewModel? TimezoneSettings; + + public TimeZoneSettingGroupViewComponent() + { + ObjectMapperContext = typeof(AbpSettingManagementBlazorMudBlazorModule); + LocalizationResource = typeof(AbpSettingManagementResource); + } + + protected async override Task OnInitializedAsync() + { + TimezoneSettings = new UpdateTimezoneSettingsViewModel() + { + Timezone = await TimeZoneSettingsAppService.GetAsync(), + TimeZoneItems = await TimeZoneSettingsAppService.GetTimezonesAsync() + }; + } + + protected virtual async Task OnSelectedValueChangedAsync(string? timezone) + { + if (TimezoneSettings != null) + { + TimezoneSettings.Timezone = timezone; + } + await InvokeAsync(StateHasChanged); + } + + protected virtual async Task UpdateSettingsAsync() + { + try + { + if (TimezoneSettings == null) + { + return; + } + + await TimeZoneSettingsAppService.UpdateAsync(TimezoneSettings.Timezone); + await CurrentApplicationConfigurationCacheResetService.ResetAsync(); + Snackbar.Add(L["SavedSuccessfully"], Severity.Success); + } + catch (Exception ex) + { + await HandleErrorAsync(ex); + } + } + + public class UpdateTimezoneSettingsViewModel + { + public string? Timezone { get; set; } + + public List? TimeZoneItems { get; set; } + } +} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/SettingComponentCreationContext.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/SettingComponentCreationContext.cs new file mode 100644 index 0000000000..b032b09841 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/SettingComponentCreationContext.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.SettingManagement.Blazor.MudBlazor; + +public class SettingComponentCreationContext : IServiceProviderAccessor +{ + public IServiceProvider ServiceProvider { get; } + + public List Groups { get; private set; } + + public SettingComponentCreationContext(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + + Groups = new List(); + } + + public void Normalize() + { + Order(); + } + + private void Order() + { + Groups = Groups.OrderBy(item => item.Order).ThenBy(item => item.DisplayName).ToList(); + } +} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/SettingComponentGroup.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/SettingComponentGroup.cs new file mode 100644 index 0000000000..9755b410b1 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/SettingComponentGroup.cs @@ -0,0 +1,40 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.SettingManagement.Blazor.MudBlazor; + +public class SettingComponentGroup +{ + public const int DefaultOrder = 1000; + + public string Id { + get => _id; + set => _id = Check.NotNullOrWhiteSpace(value, nameof(Id)); + } + private string _id = string.Empty; + + public string DisplayName { + get => _displayName; + set => _displayName = Check.NotNullOrWhiteSpace(value, nameof(DisplayName)); + } + private string _displayName = string.Empty; + + public Type ComponentType { + get => _componentType; + set => _componentType = Check.NotNull(value, nameof(ComponentType)); + } + private Type _componentType = null!; + + public object? Parameter { get; set; } + + public int Order { get; set; } + + public SettingComponentGroup([NotNull] string id, [NotNull] string displayName, [NotNull] Type componentType, object? parameter = null, int order = DefaultOrder) + { + Id = id; + DisplayName = displayName; + ComponentType = componentType; + Parameter = parameter; + Order = order; + } +} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/SettingManagementBlazorMudBlazorMappers.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/SettingManagementBlazorMudBlazorMappers.cs new file mode 100644 index 0000000000..89b9b23de6 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/SettingManagementBlazorMudBlazorMappers.cs @@ -0,0 +1,30 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; +using static Volo.Abp.SettingManagement.Blazor.MudBlazor.Pages.SettingManagement.EmailSettingGroup.EmailSettingGroupViewComponent; + +namespace Volo.Abp.SettingManagement.Blazor.MudBlazor; + +[Mapper] +public partial class UpdateEmailSettingsViewModelToUpdateEmailSettingsDtoMapper : MapperBase +{ + public override partial UpdateEmailSettingsDto Map(UpdateEmailSettingsViewModel source); + + public override partial void Map(UpdateEmailSettingsViewModel source, UpdateEmailSettingsDto destination); +} + + +[Mapper] +public partial class EmailSettingsDtoToUpdateEmailSettingsViewModelMapper : MapperBase +{ + public override partial UpdateEmailSettingsViewModel Map(EmailSettingsDto source); + + public override partial void Map(EmailSettingsDto source, UpdateEmailSettingsViewModel destination); +} + +[Mapper] +public partial class SendTestEmailViewModelToSendTestEmailInputMapper : MapperBase +{ + public override partial SendTestEmailInput Map(SendTestEmailViewModel source); + + public override partial void Map(SendTestEmailViewModel source, SendTestEmailInput destination); +} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/SettingManagementComponentOptions.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/SettingManagementComponentOptions.cs new file mode 100644 index 0000000000..7e5e473c3d --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/SettingManagementComponentOptions.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Volo.Abp.SettingManagement.Blazor.MudBlazor; + +public class SettingManagementComponentOptions +{ + public List Contributors { get; } + + public SettingManagementComponentOptions() + { + Contributors = new List(); + } +} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Settings/EmailingPageContributor.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Settings/EmailingPageContributor.cs new file mode 100644 index 0000000000..bdb204e698 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Settings/EmailingPageContributor.cs @@ -0,0 +1,62 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using Volo.Abp.Features; +using Volo.Abp.MultiTenancy; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.Pages.SettingManagement.EmailSettingGroup; +using Volo.Abp.SettingManagement.Localization; + +namespace Volo.Abp.SettingManagement.Blazor.MudBlazor.Settings; + +public class EmailingPageContributor : ISettingComponentContributor +{ + public async Task ConfigureAsync(SettingComponentCreationContext context) + { + if (!await CheckPermissionsInternalAsync(context)) + { + return; + } + + var l = context.ServiceProvider.GetRequiredService>(); + context.Groups.Add( + new SettingComponentGroup( + "Volo.Abp.SettingManagement", + l["Menu:Emailing"], + typeof(EmailSettingGroupViewComponent) + ) + ); + } + + public async Task CheckPermissionsAsync(SettingComponentCreationContext context) + { + return await CheckPermissionsInternalAsync(context); + } + + private async Task CheckPermissionsInternalAsync(SettingComponentCreationContext context) + { + if (!await CheckFeatureAsync(context)) + { + return false; + } + + var authorizationService = context.ServiceProvider.GetRequiredService(); + + return await authorizationService.IsGrantedAsync(SettingManagementPermissions.Emailing); + } + + private async Task CheckFeatureAsync(SettingComponentCreationContext context) + { + var currentTenant = context.ServiceProvider.GetRequiredService(); + + if (!currentTenant.IsAvailable) + { + return true; + } + + var featureCheck = context.ServiceProvider.GetRequiredService(); + + return await featureCheck.IsEnabledAsync(SettingManagementFeatures.AllowChangingEmailSettings); + + } +} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Settings/TimeZonePageContributor.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Settings/TimeZonePageContributor.cs new file mode 100644 index 0000000000..6f812bd6e1 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Settings/TimeZonePageContributor.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; +using Volo.Abp.Features; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.Pages.SettingManagement.TimeZoneSettingGroup; +using Volo.Abp.SettingManagement.Localization; +using Volo.Abp.Timing; + +namespace Volo.Abp.SettingManagement.Blazor.MudBlazor.Settings; + +public class TimeZonePageContributor : ISettingComponentContributor +{ + public async Task ConfigureAsync(SettingComponentCreationContext context) + { + var l = context.ServiceProvider.GetRequiredService>(); + if (await CheckPermissionsAsync(context)) + { + context.Groups.Add( + new SettingComponentGroup( + "Volo.Abp.TimeZone", + l["Menu:TimeZone"], + typeof(TimeZoneSettingGroupViewComponent) + ) + ); + } + } + + public async Task CheckPermissionsAsync(SettingComponentCreationContext context) + { + var authorizationService = context.ServiceProvider.GetRequiredService(); + + return context.ServiceProvider.GetRequiredService().SupportsMultipleTimezone + && await authorizationService.IsGrantedAsync(SettingManagementPermissions.TimeZone); + } +} diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Volo.Abp.SettingManagement.Blazor.MudBlazor.csproj b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Volo.Abp.SettingManagement.Blazor.MudBlazor.csproj new file mode 100644 index 0000000000..93f85f21e9 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/Volo.Abp.SettingManagement.Blazor.MudBlazor.csproj @@ -0,0 +1,19 @@ + + + + + + + net10.0 + Volo.Abp.SettingManagement.Blazor.MudBlazor + + + + + + + + + + + diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/_Imports.razor b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/_Imports.razor new file mode 100644 index 0000000000..4e91324cd8 --- /dev/null +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor/_Imports.razor @@ -0,0 +1,4 @@ +@using Microsoft.AspNetCore.Components.Web +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor +@using MudBlazor +@using Volo.Abp.MudBlazorUI diff --git a/modules/tenant-management/Volo.Abp.TenantManagement.slnx b/modules/tenant-management/Volo.Abp.TenantManagement.slnx index ee9677787f..e44ab4110a 100644 --- a/modules/tenant-management/Volo.Abp.TenantManagement.slnx +++ b/modules/tenant-management/Volo.Abp.TenantManagement.slnx @@ -2,6 +2,9 @@ + + + diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.Server/AbpTenantManagementBlazorMudBlazorServerModule.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.Server/AbpTenantManagementBlazorMudBlazorServerModule.cs new file mode 100644 index 0000000000..59c6cb267f --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.Server/AbpTenantManagementBlazorMudBlazorServerModule.cs @@ -0,0 +1,14 @@ +using Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor; +using Volo.Abp.FeatureManagement.Blazor.MudBlazor.Server; +using Volo.Abp.Modularity; + +namespace Volo.Abp.TenantManagement.Blazor.MudBlazor.Server; + +[DependsOn( + typeof(AbpTenantManagementBlazorMudBlazorModule), + typeof(AbpAspNetCoreComponentsServerThemingMudBlazorModule), + typeof(AbpFeatureManagementBlazorMudBlazorServerModule) +)] +public class AbpTenantManagementBlazorMudBlazorServerModule : AbpModule +{ +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.Server/FodyWeavers.xml b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.Server/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.Server/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.Server/FodyWeavers.xsd b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.Server/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.Server/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.Server/Volo.Abp.TenantManagement.Blazor.MudBlazor.Server.csproj b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.Server/Volo.Abp.TenantManagement.Blazor.MudBlazor.Server.csproj new file mode 100644 index 0000000000..96cf8a86b0 --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.Server/Volo.Abp.TenantManagement.Blazor.MudBlazor.Server.csproj @@ -0,0 +1,16 @@ + + + + + + + net10.0 + + + + + + + + + diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly/AbpTenantManagementBlazorMudBlazorWebAssemblyModule.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly/AbpTenantManagementBlazorMudBlazorWebAssemblyModule.cs new file mode 100644 index 0000000000..4258f1a8c4 --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly/AbpTenantManagementBlazorMudBlazorWebAssemblyModule.cs @@ -0,0 +1,15 @@ +using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor; +using Volo.Abp.FeatureManagement.Blazor.MudBlazor.WebAssembly; +using Volo.Abp.Modularity; + +namespace Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly; + +[DependsOn( + typeof(AbpTenantManagementBlazorMudBlazorModule), + typeof(AbpAspNetCoreComponentsWebAssemblyThemingMudBlazorModule), + typeof(AbpFeatureManagementBlazorMudBlazorWebAssemblyModule), + typeof(AbpTenantManagementHttpApiClientModule) +)] +public class AbpTenantManagementBlazorMudBlazorWebAssemblyModule : AbpModule +{ +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xml b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xsd b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly/Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly.csproj b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly/Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly.csproj new file mode 100644 index 0000000000..f41f2e4ef0 --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly/Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly.csproj @@ -0,0 +1,17 @@ + + + + + + + net10.0 + + + + + + + + + + diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/AbpTenantManagementBlazorMudBlazorMapperlyMappers.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/AbpTenantManagementBlazorMudBlazorMapperlyMappers.cs new file mode 100644 index 0000000000..83b1474647 --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/AbpTenantManagementBlazorMudBlazorMapperlyMappers.cs @@ -0,0 +1,16 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; + +namespace Volo.Abp.TenantManagement.Blazor.MudBlazor; + +[Mapper] +[MapExtraProperties] +public partial class TenantDtoToTenantUpdateDtoMapper + : MapperBase +{ + [MapperIgnoreSource(nameof(TenantDto.Id))] + public override partial TenantUpdateDto Map(TenantDto source); + + [MapperIgnoreSource(nameof(TenantDto.Id))] + public override partial void Map(TenantDto source, TenantUpdateDto destination); +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/AbpTenantManagementBlazorMudBlazorModule.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/AbpTenantManagementBlazorMudBlazorModule.cs new file mode 100644 index 0000000000..dfee56fd12 --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/AbpTenantManagementBlazorMudBlazorModule.cs @@ -0,0 +1,64 @@ +using Localization.Resources.AbpUi; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Mapperly; +using Volo.Abp.FeatureManagement.Blazor.MudBlazor; +using Volo.Abp.FeatureManagement.Localization; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.ObjectExtending; +using Volo.Abp.ObjectExtending.Modularity; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.Navigation; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; +using Volo.Abp.TenantManagement.Localization; +using Volo.Abp.Threading; +using Volo.Abp.UI.Navigation; + +namespace Volo.Abp.TenantManagement.Blazor.MudBlazor; + +[DependsOn( + typeof(AbpMapperlyModule), + typeof(AbpTenantManagementApplicationContractsModule), + typeof(AbpFeatureManagementBlazorMudBlazorModule) +)] +public class AbpTenantManagementBlazorMudBlazorModule : AbpModule +{ + private static readonly OneTimeRunner OneTimeRunner = new(); + + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddMapperlyObjectMapper(); + + Configure(options => + { + options.MenuContributors.Add(new TenantManagementBlazorMenuContributor()); + }); + + Configure(options => + { + options.AdditionalAssemblies.Add(typeof(AbpTenantManagementBlazorMudBlazorModule).Assembly); + }); + + Configure(options => + { + options.Resources + .Get() + .AddBaseTypes( + typeof(AbpFeatureManagementResource), + typeof(AbpUiResource)); + }); + } + + public override void PostConfigureServices(ServiceConfigurationContext context) + { + OneTimeRunner.Run(() => + { + ModuleExtensionConfigurationHelper + .ApplyEntityConfigurationToUi( + TenantManagementModuleExtensionConsts.ModuleName, + TenantManagementModuleExtensionConsts.EntityNames.Tenant, + createFormTypes: new[] { typeof(TenantCreateDto) }, + editFormTypes: new[] { typeof(TenantUpdateDto) } + ); + }); + } +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/FodyWeavers.xml b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/FodyWeavers.xsd b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/Navigation/TenantManagementBlazorMenuContributor.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/Navigation/TenantManagementBlazorMenuContributor.cs new file mode 100644 index 0000000000..04540c85a6 --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/Navigation/TenantManagementBlazorMenuContributor.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using Volo.Abp.TenantManagement.Localization; +using Volo.Abp.UI.Navigation; +using Volo.Abp.Authorization.Permissions; + +namespace Volo.Abp.TenantManagement.Blazor.MudBlazor.Navigation; + +public class TenantManagementBlazorMenuContributor : IMenuContributor +{ + public virtual Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name != StandardMenus.Main) + { + return Task.CompletedTask; + } + + var administrationMenu = context.Menu.GetAdministration(); + + var l = context.GetLocalizer(); + + var tenantManagementMenuItem = new ApplicationMenuItem( + TenantManagementMenuNames.GroupName, + l["Menu:TenantManagement"], + icon: "fa fa-users" + ); + administrationMenu.AddItem(tenantManagementMenuItem); + + tenantManagementMenuItem.AddItem(new ApplicationMenuItem( + TenantManagementMenuNames.Tenants, + l["Tenants"], + url: "~/tenant-management/tenants" + ).RequirePermissions(TenantManagementPermissions.Tenants.Default)); + + return Task.CompletedTask; + } +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/Navigation/TenantManagementMenuNames.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/Navigation/TenantManagementMenuNames.cs new file mode 100644 index 0000000000..2f5dda85fa --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/Navigation/TenantManagementMenuNames.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.TenantManagement.Blazor.MudBlazor.Navigation; + +public class TenantManagementMenuNames +{ + public const string GroupName = "TenantManagement"; + + public const string Tenants = GroupName + ".Tenants"; +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/Pages/TenantManagement/TenantManagement.razor b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/Pages/TenantManagement/TenantManagement.razor new file mode 100644 index 0000000000..3cbbc614dc --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/Pages/TenantManagement/TenantManagement.razor @@ -0,0 +1,96 @@ +@page "/tenant-management/tenants" +@page "/{culture}/tenant-management/tenants" +@attribute [Authorize(TenantManagementPermissions.Tenants.Default)] +@using global::MudBlazor +@using Microsoft.AspNetCore.Authorization +@using Volo.Abp.FeatureManagement.Blazor.MudBlazor.Components +@using Volo.Abp.MudBlazorUI.Components +@using Volo.Abp.MudBlazorUI.Components.ObjectExtending +@using Volo.Abp.TenantManagement.Localization +@using Microsoft.Extensions.Localization +@using Volo.Abp.UI.Navigation.Localization.Resource +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Layout +@inject IStringLocalizer LUiNavigation +@inherits AbpMudCrudPageBase + + + + + + + + + +@* ************************* CREATE MODAL ************************* *@ +@if (HasCreatePermission) +{ + + + @L["NewTenant"] + + + + + + + + + + + @L["Cancel"] + @L["Save"] + + +} + +@* ************************* EDIT MODAL ************************* *@ +@if (HasUpdatePermission) +{ + + + @L["Edit"] + + + + + + + + + @L["Cancel"] + @L["Save"] + + +} + +@if (HasManageFeaturesPermission) +{ + +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/Pages/TenantManagement/TenantManagement.razor.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/Pages/TenantManagement/TenantManagement.razor.cs new file mode 100644 index 0000000000..4587475f01 --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/Pages/TenantManagement/TenantManagement.razor.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; +using MudBlazor; +using Volo.Abp.AspNetCore.Components.Web.Extensibility.EntityActions; +using Volo.Abp.AspNetCore.Components.Web.Extensibility.TableColumns; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.PageToolbars; +using Volo.Abp.FeatureManagement.Blazor.MudBlazor.Components; +using Volo.Abp.MudBlazorUI; +using Volo.Abp.ObjectExtending; +using Volo.Abp.TenantManagement.Localization; + +namespace Volo.Abp.TenantManagement.Blazor.MudBlazor.Pages.TenantManagement; + +public partial class TenantManagement +{ + [Parameter] + public string? Culture { get; set; } + + protected const string FeatureProviderName = "T"; + + protected bool HasManageFeaturesPermission; + protected string? ManageFeaturesPolicyName; + + protected FeatureManagementModal? FeatureManagementModal; + + protected bool ShowPassword { get; set; } + + protected PageToolbar Toolbar { get; } = new(); + + protected List TenantManagementTableColumns => TableColumns.Get(); + + private AbpMudBlazorMessageLocalizerHelper? _localizerHelper; + + [Inject] + protected IStringLocalizer Localizer { get; set; } = default!; + + private MudForm? _createFormRef; + private MudForm? _editFormRef; + + public TenantManagement() + { + LocalizationResource = typeof(AbpTenantManagementResource); + ObjectMapperContext = typeof(AbpTenantManagementBlazorMudBlazorModule); + + CreatePolicyName = TenantManagementPermissions.Tenants.Create; + UpdatePolicyName = TenantManagementPermissions.Tenants.Update; + DeletePolicyName = TenantManagementPermissions.Tenants.Delete; + + ManageFeaturesPolicyName = TenantManagementPermissions.Tenants.ManageFeatures; + } + + protected override ValueTask SetBreadcrumbItemsAsync() + { + BreadcrumbItems.Add(new BreadcrumbItem(LUiNavigation["Menu:Administration"].Value, null, disabled: true)); + BreadcrumbItems.Add(new BreadcrumbItem(L["Menu:TenantManagement"].Value, null, disabled: true)); + BreadcrumbItems.Add(new BreadcrumbItem(L["Tenants"].Value, null, disabled: true)); + return base.SetBreadcrumbItemsAsync(); + } + + protected override async Task OnInitializedAsync() + { + _localizerHelper = new AbpMudBlazorMessageLocalizerHelper(Localizer); + await base.OnInitializedAsync(); + } + + protected override async Task SetPermissionsAsync() + { + await base.SetPermissionsAsync(); + + HasManageFeaturesPermission = await AuthorizationService.IsGrantedAsync(ManageFeaturesPolicyName!); + } + + protected override string GetDeleteConfirmationMessage(TenantDto entity) + { + return string.Format(L["TenantDeletionConfirmationMessage"], entity.Name); + } + + protected override ValueTask SetToolbarItemsAsync() + { + Toolbar.AddMudButton(L["NewTenant"], + OpenCreateDialogAsync, + icon: Icons.Material.Filled.Add.ToString(), + requiredPolicyName: CreatePolicyName); + + return base.SetToolbarItemsAsync(); + } + + protected override ValueTask SetEntityActionsAsync() + { + EntityActions + .Get() + .AddRange(new EntityAction[] + { + new EntityAction + { + Text = L["Edit"], + Visible = (data) => HasUpdatePermission, + Clicked = async (data) => { await OpenEditDialogAsync(data.As()); } + }, + new EntityAction + { + Text = L["Features"], + Visible = (data) => HasManageFeaturesPermission, + Clicked = async (data) => + { + var tenant = data.As(); + if (FeatureManagementModal != null) + { + await FeatureManagementModal.OpenAsync(FeatureProviderName, tenant.Id.ToString(), tenant.Name); + } + } + }, + new EntityAction + { + Text = L["Delete"], + Visible = (data) => HasDeletePermission, + Clicked = async (data) => await DeleteEntityAsync(data.As()), + ConfirmationMessage = (data) => GetDeleteConfirmationMessage(data.As()) + } + }); + + return base.SetEntityActionsAsync(); + } + + protected override async ValueTask SetTableColumnsAsync() + { + TenantManagementTableColumns + .AddRange(new TableColumn[] + { + new TableColumn + { + Title = L["Actions"], + Actions = EntityActions.Get(), + }, + new TableColumn + { + Title = L["TenantName"], + Sortable = true, + Data = nameof(TenantDto.Name), + }, + }); + + TenantManagementTableColumns.AddRange(await GetExtensionTableColumnsAsync( + TenantManagementModuleExtensionConsts.ModuleName, + TenantManagementModuleExtensionConsts.EntityNames.Tenant)); + + await base.SetTableColumnsAsync(); + } + + protected virtual void TogglePasswordVisibility() + { + ShowPassword = !ShowPassword; + } + + protected override async Task CreateEntityAsync() + { + if (_createFormRef == null) + { + return; + } + + await _createFormRef.Validate(); + if (!_createFormRef.IsValid) + { + return; + } + + await base.CreateEntityAsync(); + } + + protected override async Task UpdateEntityAsync() + { + if (_editFormRef == null) + { + return; + } + + await _editFormRef.Validate(); + if (!_editFormRef.IsValid) + { + return; + } + + await base.UpdateEntityAsync(); + } +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/Volo.Abp.TenantManagement.Blazor.MudBlazor.csproj b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/Volo.Abp.TenantManagement.Blazor.MudBlazor.csproj new file mode 100644 index 0000000000..d7748927ac --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/Volo.Abp.TenantManagement.Blazor.MudBlazor.csproj @@ -0,0 +1,22 @@ + + + + + + + net10.0 + + + + + + + + + + + + + + + diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/_Imports.razor b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/_Imports.razor new file mode 100644 index 0000000000..4e91324cd8 --- /dev/null +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor/_Imports.razor @@ -0,0 +1,4 @@ +@using Microsoft.AspNetCore.Components.Web +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor +@using MudBlazor +@using Volo.Abp.MudBlazorUI diff --git a/modules/virtual-file-explorer/.abpstudio/state.json b/modules/virtual-file-explorer/.abpstudio/state.json deleted file mode 100644 index b0ef88d2f2..0000000000 --- a/modules/virtual-file-explorer/.abpstudio/state.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "selectedKubernetesProfile": null, - "solutionRunner": { - "selectedProfile": null, - "targetFrameworks": [], - "applicationsStartingWithoutBuild": [], - "applicationsWithoutAutoRefreshBrowserOnRestart": [], - "applicationBatchStartStates": [], - "folderBatchStartStates": [] - } -} \ No newline at end of file diff --git a/modules/virtual-file-explorer/app/package.json b/modules/virtual-file-explorer/app/package.json index 681f89a5ba..fd48d34ef1 100644 --- a/modules/virtual-file-explorer/app/package.json +++ b/modules/virtual-file-explorer/app/package.json @@ -3,7 +3,7 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.3.0", - "@abp/virtual-file-explorer": "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.4.0", + "@abp/virtual-file-explorer": "~10.4.0" } } diff --git a/npm/lerna.json b/npm/lerna.json index acf6a49b45..6f54406585 100644 --- a/npm/lerna.json +++ b/npm/lerna.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "packages": [ "packs/*" ], diff --git a/npm/ng-packs/package.json b/npm/ng-packs/package.json index 8feb2de24c..51a32a2521 100644 --- a/npm/ng-packs/package.json +++ b/npm/ng-packs/package.json @@ -48,8 +48,8 @@ }, "private": true, "devDependencies": { - "@abp/ng.theme.lepton-x": "~5.3.0", - "@abp/utils": "~10.3.0", + "@abp/ng.theme.lepton-x": "~5.4.0", + "@abp/utils": "~10.4.0", "@angular-devkit/build-angular": "~21.2.0", "@angular-devkit/core": "~21.2.0", "@angular-devkit/schematics": "~21.2.0", diff --git a/npm/ng-packs/packages/account-core/package.json b/npm/ng-packs/packages/account-core/package.json index cfbee9058d..3a92359eb5 100644 --- a/npm/ng-packs/packages/account-core/package.json +++ b/npm/ng-packs/packages/account-core/package.json @@ -1,14 +1,14 @@ { "name": "@abp/ng.account.core", - "version": "10.3.0", + "version": "10.4.0", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.core": "~10.3.0", - "@abp/ng.theme.shared": "~10.3.0", + "@abp/ng.core": "~10.4.0", + "@abp/ng.theme.shared": "~10.4.0", "tslib": "^2.0.0" }, "publishConfig": { diff --git a/npm/ng-packs/packages/account/package.json b/npm/ng-packs/packages/account/package.json index bee5f39ff5..494ac14976 100644 --- a/npm/ng-packs/packages/account/package.json +++ b/npm/ng-packs/packages/account/package.json @@ -1,14 +1,14 @@ { "name": "@abp/ng.account", - "version": "10.3.0", + "version": "10.4.0", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.account.core": "~10.3.0", - "@abp/ng.theme.shared": "~10.3.0", + "@abp/ng.account.core": "~10.4.0", + "@abp/ng.theme.shared": "~10.4.0", "tslib": "^2.0.0" }, "publishConfig": { diff --git a/npm/ng-packs/packages/cms-kit/package.json b/npm/ng-packs/packages/cms-kit/package.json index d18df04cd3..e12a12b08a 100644 --- a/npm/ng-packs/packages/cms-kit/package.json +++ b/npm/ng-packs/packages/cms-kit/package.json @@ -1,15 +1,15 @@ { "name": "@abp/ng.cms-kit", - "version": "10.3.0", + "version": "10.4.0", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.components": "~10.3.0", - "@abp/ng.setting-management": "~10.3.0", - "@abp/ng.theme.shared": "~10.3.0", + "@abp/ng.components": "~10.4.0", + "@abp/ng.setting-management": "~10.4.0", + "@abp/ng.theme.shared": "~10.4.0", "@toast-ui/editor": "~3.0.0", "codemirror": "~6.0.0", "tslib": "^2.0.0" diff --git a/npm/ng-packs/packages/components/package.json b/npm/ng-packs/packages/components/package.json index f730716d60..e3a5feeb66 100644 --- a/npm/ng-packs/packages/components/package.json +++ b/npm/ng-packs/packages/components/package.json @@ -1,14 +1,14 @@ { "name": "@abp/ng.components", - "version": "10.3.0", + "version": "10.4.0", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "peerDependencies": { - "@abp/ng.core": ">=10.3.0", - "@abp/ng.theme.shared": ">=10.3.0" + "@abp/ng.core": ">=10.4.0", + "@abp/ng.theme.shared": ">=10.4.0" }, "dependencies": { "chart.js": "^3.5.1", diff --git a/npm/ng-packs/packages/core/package.json b/npm/ng-packs/packages/core/package.json index afc9f2287c..0c590df172 100644 --- a/npm/ng-packs/packages/core/package.json +++ b/npm/ng-packs/packages/core/package.json @@ -1,13 +1,13 @@ { "name": "@abp/ng.core", - "version": "10.3.0", + "version": "10.4.0", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/utils": "~10.3.0", + "@abp/utils": "~10.4.0", "just-clone": "^6.0.0", "just-compare": "^2.0.0", "ts-toolbelt": "^9.0.0", diff --git a/npm/ng-packs/packages/feature-management/package.json b/npm/ng-packs/packages/feature-management/package.json index 867f5ec95c..69219c053c 100644 --- a/npm/ng-packs/packages/feature-management/package.json +++ b/npm/ng-packs/packages/feature-management/package.json @@ -1,13 +1,13 @@ { "name": "@abp/ng.feature-management", - "version": "10.3.0", + "version": "10.4.0", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.theme.shared": "~10.3.0", + "@abp/ng.theme.shared": "~10.4.0", "tslib": "^2.0.0" }, "peerDependencies": { diff --git a/npm/ng-packs/packages/generators/package.json b/npm/ng-packs/packages/generators/package.json index ab07a3cb03..02cceff83b 100644 --- a/npm/ng-packs/packages/generators/package.json +++ b/npm/ng-packs/packages/generators/package.json @@ -1,6 +1,6 @@ { "name": "@abp/nx.generators", - "version": "10.3.0", + "version": "10.4.0", "homepage": "https://abp.io", "generators": "./generators.json", "type": "commonjs", diff --git a/npm/ng-packs/packages/identity/package.json b/npm/ng-packs/packages/identity/package.json index 8826eafeec..9de16b4b54 100644 --- a/npm/ng-packs/packages/identity/package.json +++ b/npm/ng-packs/packages/identity/package.json @@ -1,15 +1,15 @@ { "name": "@abp/ng.identity", - "version": "10.3.0", + "version": "10.4.0", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.components": "~10.3.0", - "@abp/ng.permission-management": "~10.3.0", - "@abp/ng.theme.shared": "~10.3.0", + "@abp/ng.components": "~10.4.0", + "@abp/ng.permission-management": "~10.4.0", + "@abp/ng.theme.shared": "~10.4.0", "tslib": "^2.0.0" }, "publishConfig": { diff --git a/npm/ng-packs/packages/oauth/package.json b/npm/ng-packs/packages/oauth/package.json index d6f455c4cf..5aff284c44 100644 --- a/npm/ng-packs/packages/oauth/package.json +++ b/npm/ng-packs/packages/oauth/package.json @@ -1,14 +1,14 @@ { "name": "@abp/ng.oauth", - "version": "10.3.0", + "version": "10.4.0", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.core": "~10.3.0", - "@abp/utils": "~10.3.0", + "@abp/ng.core": "~10.4.0", + "@abp/utils": "~10.4.0", "angular-oauth2-oidc": "^20.0.0", "just-clone": "^6.0.0", "just-compare": "^2.0.0", diff --git a/npm/ng-packs/packages/permission-management/package.json b/npm/ng-packs/packages/permission-management/package.json index 5295a7fd9f..73c2f067f6 100644 --- a/npm/ng-packs/packages/permission-management/package.json +++ b/npm/ng-packs/packages/permission-management/package.json @@ -1,13 +1,13 @@ { "name": "@abp/ng.permission-management", - "version": "10.3.0", + "version": "10.4.0", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.theme.shared": "~10.3.0", + "@abp/ng.theme.shared": "~10.4.0", "tslib": "^2.0.0" }, "peerDependencies": { diff --git a/npm/ng-packs/packages/schematics/package.json b/npm/ng-packs/packages/schematics/package.json index dbbfe297f6..46ecaef37a 100644 --- a/npm/ng-packs/packages/schematics/package.json +++ b/npm/ng-packs/packages/schematics/package.json @@ -1,6 +1,6 @@ { "name": "@abp/ng.schematics", - "version": "10.3.0", + "version": "10.4.0", "author": "", "schematics": "./collection.json", "dependencies": { diff --git a/npm/ng-packs/packages/setting-management/package.json b/npm/ng-packs/packages/setting-management/package.json index 2635bd99d0..8f42c8e1f7 100644 --- a/npm/ng-packs/packages/setting-management/package.json +++ b/npm/ng-packs/packages/setting-management/package.json @@ -1,14 +1,14 @@ { "name": "@abp/ng.setting-management", - "version": "10.3.0", + "version": "10.4.0", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.components": "~10.3.0", - "@abp/ng.theme.shared": "~10.3.0", + "@abp/ng.components": "~10.4.0", + "@abp/ng.theme.shared": "~10.4.0", "tslib": "^2.0.0" }, "peerDependencies": { diff --git a/npm/ng-packs/packages/tenant-management/package.json b/npm/ng-packs/packages/tenant-management/package.json index ba369306b3..7c1095880f 100644 --- a/npm/ng-packs/packages/tenant-management/package.json +++ b/npm/ng-packs/packages/tenant-management/package.json @@ -1,14 +1,14 @@ { "name": "@abp/ng.tenant-management", - "version": "10.3.0", + "version": "10.4.0", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.feature-management": "~10.3.0", - "@abp/ng.theme.shared": "~10.3.0", + "@abp/ng.feature-management": "~10.4.0", + "@abp/ng.theme.shared": "~10.4.0", "tslib": "^2.0.0" }, "publishConfig": { diff --git a/npm/ng-packs/packages/theme-basic/package.json b/npm/ng-packs/packages/theme-basic/package.json index cb1cffc0e5..4aedfb963f 100644 --- a/npm/ng-packs/packages/theme-basic/package.json +++ b/npm/ng-packs/packages/theme-basic/package.json @@ -1,14 +1,14 @@ { "name": "@abp/ng.theme.basic", - "version": "10.3.0", + "version": "10.4.0", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.account.core": "~10.3.0", - "@abp/ng.theme.shared": "~10.3.0", + "@abp/ng.account.core": "~10.4.0", + "@abp/ng.theme.shared": "~10.4.0", "tslib": "^2.0.0" }, "publishConfig": { diff --git a/npm/ng-packs/packages/theme-shared/package.json b/npm/ng-packs/packages/theme-shared/package.json index 707ab22edc..4d3915f869 100644 --- a/npm/ng-packs/packages/theme-shared/package.json +++ b/npm/ng-packs/packages/theme-shared/package.json @@ -1,13 +1,13 @@ { "name": "@abp/ng.theme.shared", - "version": "10.3.0", + "version": "10.4.0", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.core": "~10.3.0", + "@abp/ng.core": "~10.4.0", "@fortawesome/fontawesome-free": "^6.0.0", "@ng-bootstrap/ng-bootstrap": "~20.0.0", "@ngx-validate/core": "^0.2.0", diff --git a/npm/packs/anchor-js/package.json b/npm/packs/anchor-js/package.json index 1f825c178c..df098a575d 100644 --- a/npm/packs/anchor-js/package.json +++ b/npm/packs/anchor-js/package.json @@ -1,11 +1,11 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/anchor-js", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.3.0", + "@abp/core": "~10.4.0", "anchor-js": "^5.0.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/aspnetcore.components.server.basictheme/package.json b/npm/packs/aspnetcore.components.server.basictheme/package.json index af95ebfbe8..219a3daf76 100644 --- a/npm/packs/aspnetcore.components.server.basictheme/package.json +++ b/npm/packs/aspnetcore.components.server.basictheme/package.json @@ -1,11 +1,11 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/aspnetcore.components.server.basictheme", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/aspnetcore.components.server.theming": "~10.3.0" + "@abp/aspnetcore.components.server.theming": "~10.4.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/aspnetcore.components.server.theming/package.json b/npm/packs/aspnetcore.components.server.theming/package.json index 27df25ce73..4e0417d56a 100644 --- a/npm/packs/aspnetcore.components.server.theming/package.json +++ b/npm/packs/aspnetcore.components.server.theming/package.json @@ -1,12 +1,12 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/aspnetcore.components.server.theming", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/bootstrap": "~10.3.0", - "@abp/font-awesome": "~10.3.0" + "@abp/bootstrap": "~10.4.0", + "@abp/font-awesome": "~10.4.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/aspnetcore.mvc.ui.theme.basic/package.json b/npm/packs/aspnetcore.mvc.ui.theme.basic/package.json index fa174ab32f..755b3200e1 100644 --- a/npm/packs/aspnetcore.mvc.ui.theme.basic/package.json +++ b/npm/packs/aspnetcore.mvc.ui.theme.basic/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/aspnetcore.mvc.ui.theme.basic", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.shared": "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.shared": "~10.4.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/aspnetcore.mvc.ui.theme.shared/package.json b/npm/packs/aspnetcore.mvc.ui.theme.shared/package.json index 2660ac8985..ba7ddefd63 100644 --- a/npm/packs/aspnetcore.mvc.ui.theme.shared/package.json +++ b/npm/packs/aspnetcore.mvc.ui.theme.shared/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/aspnetcore.mvc.ui.theme.shared", "repository": { "type": "git", @@ -10,20 +10,20 @@ "access": "public" }, "dependencies": { - "@abp/aspnetcore.mvc.ui": "~10.3.0", - "@abp/bootstrap": "~10.3.0", - "@abp/bootstrap-datepicker": "~10.3.0", - "@abp/bootstrap-daterangepicker": "~10.3.0", - "@abp/datatables.net-bs5": "~10.3.0", - "@abp/font-awesome": "~10.3.0", - "@abp/jquery-validation-unobtrusive": "~10.3.0", - "@abp/lodash": "~10.3.0", - "@abp/luxon": "~10.3.0", - "@abp/malihu-custom-scrollbar-plugin": "~10.3.0", - "@abp/moment": "~10.3.0", - "@abp/select2": "~10.3.0", - "@abp/sweetalert2": "~10.3.0", - "@abp/timeago": "~10.3.0" + "@abp/aspnetcore.mvc.ui": "~10.4.0", + "@abp/bootstrap": "~10.4.0", + "@abp/bootstrap-datepicker": "~10.4.0", + "@abp/bootstrap-daterangepicker": "~10.4.0", + "@abp/datatables.net-bs5": "~10.4.0", + "@abp/font-awesome": "~10.4.0", + "@abp/jquery-validation-unobtrusive": "~10.4.0", + "@abp/lodash": "~10.4.0", + "@abp/luxon": "~10.4.0", + "@abp/malihu-custom-scrollbar-plugin": "~10.4.0", + "@abp/moment": "~10.4.0", + "@abp/select2": "~10.4.0", + "@abp/sweetalert2": "~10.4.0", + "@abp/timeago": "~10.4.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/aspnetcore.mvc.ui/package-lock.json b/npm/packs/aspnetcore.mvc.ui/package-lock.json index 47569fdc1f..a25febdb8c 100644 --- a/npm/packs/aspnetcore.mvc.ui/package-lock.json +++ b/npm/packs/aspnetcore.mvc.ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "@abp/aspnetcore.mvc.ui", - "version": "10.3.0", + "version": "10.4.0", "lockfileVersion": 1, "requires": true, "packages": { diff --git a/npm/packs/aspnetcore.mvc.ui/package.json b/npm/packs/aspnetcore.mvc.ui/package.json index 2aaa3efce2..6e6897df9f 100644 --- a/npm/packs/aspnetcore.mvc.ui/package.json +++ b/npm/packs/aspnetcore.mvc.ui/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/aspnetcore.mvc.ui", "repository": { "type": "git", diff --git a/npm/packs/blogging/package.json b/npm/packs/blogging/package.json index 96fda325de..e7ddbdc5dc 100644 --- a/npm/packs/blogging/package.json +++ b/npm/packs/blogging/package.json @@ -1,14 +1,14 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/blogging", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.shared": "~10.3.0", - "@abp/owl.carousel": "~10.3.0", - "@abp/prismjs": "~10.3.0", - "@abp/tui-editor": "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.shared": "~10.4.0", + "@abp/owl.carousel": "~10.4.0", + "@abp/prismjs": "~10.4.0", + "@abp/tui-editor": "~10.4.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/bootstrap-datepicker/package.json b/npm/packs/bootstrap-datepicker/package.json index 1274202764..cc64ddcab8 100644 --- a/npm/packs/bootstrap-datepicker/package.json +++ b/npm/packs/bootstrap-datepicker/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/bootstrap-datepicker", "repository": { "type": "git", diff --git a/npm/packs/bootstrap-daterangepicker/package.json b/npm/packs/bootstrap-daterangepicker/package.json index e6aa0182c5..a1e73a6e2b 100644 --- a/npm/packs/bootstrap-daterangepicker/package.json +++ b/npm/packs/bootstrap-daterangepicker/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/bootstrap-daterangepicker", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/moment": "~10.3.0", + "@abp/moment": "~10.4.0", "bootstrap-daterangepicker": "^3.1.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/bootstrap/package.json b/npm/packs/bootstrap/package.json index d153222da9..a67c77d53d 100644 --- a/npm/packs/bootstrap/package.json +++ b/npm/packs/bootstrap/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/bootstrap", "repository": { "type": "git", @@ -10,8 +10,8 @@ "access": "public" }, "dependencies": { - "@abp/core": "~10.3.0", - "@abp/popper.js": "~10.3.0", + "@abp/core": "~10.4.0", + "@abp/popper.js": "~10.4.0", "bootstrap": "^5.3.8" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/chart.js/package.json b/npm/packs/chart.js/package.json index d2963f29a2..cf1ae9b469 100644 --- a/npm/packs/chart.js/package.json +++ b/npm/packs/chart.js/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/chart.js", "publishConfig": { "access": "public" diff --git a/npm/packs/clipboard/package.json b/npm/packs/clipboard/package.json index 4def145537..d88e8abe1b 100644 --- a/npm/packs/clipboard/package.json +++ b/npm/packs/clipboard/package.json @@ -1,11 +1,11 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/clipboard", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.3.0", + "@abp/core": "~10.4.0", "clipboard": "^2.0.11" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/cms-kit.admin/package.json b/npm/packs/cms-kit.admin/package.json index 7b93424a81..06cc7002f3 100644 --- a/npm/packs/cms-kit.admin/package.json +++ b/npm/packs/cms-kit.admin/package.json @@ -1,16 +1,16 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/cms-kit.admin", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/codemirror": "~10.3.0", - "@abp/jstree": "~10.3.0", - "@abp/markdown-it": "~10.3.0", - "@abp/slugify": "~10.3.0", - "@abp/tui-editor": "~10.3.0", - "@abp/uppy": "~10.3.0" + "@abp/codemirror": "~10.4.0", + "@abp/jstree": "~10.4.0", + "@abp/markdown-it": "~10.4.0", + "@abp/slugify": "~10.4.0", + "@abp/tui-editor": "~10.4.0", + "@abp/uppy": "~10.4.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/cms-kit.public/package.json b/npm/packs/cms-kit.public/package.json index ffac8cbabb..2cfed064d1 100644 --- a/npm/packs/cms-kit.public/package.json +++ b/npm/packs/cms-kit.public/package.json @@ -1,12 +1,12 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/cms-kit.public", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/highlight.js": "~10.3.0", - "@abp/star-rating-svg": "~10.3.0" + "@abp/highlight.js": "~10.4.0", + "@abp/star-rating-svg": "~10.4.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/cms-kit/package.json b/npm/packs/cms-kit/package.json index 924a56ca43..7bb036947f 100644 --- a/npm/packs/cms-kit/package.json +++ b/npm/packs/cms-kit/package.json @@ -1,12 +1,12 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/cms-kit", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/cms-kit.admin": "~10.3.0", - "@abp/cms-kit.public": "~10.3.0" + "@abp/cms-kit.admin": "~10.4.0", + "@abp/cms-kit.public": "~10.4.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/codemirror/package.json b/npm/packs/codemirror/package.json index 6dc26a9eb0..7ee5c182db 100644 --- a/npm/packs/codemirror/package.json +++ b/npm/packs/codemirror/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/codemirror", "publishConfig": { "access": "public" @@ -8,7 +8,7 @@ "build": "node ./scripts/build.js" }, "dependencies": { - "@abp/core": "~10.3.0", + "@abp/core": "~10.4.0", "@codemirror/lang-css": "^6.0.0", "@codemirror/lang-javascript": "^6.0.0", "codemirror": "^6.0.2" diff --git a/npm/packs/core/package.json b/npm/packs/core/package.json index fea065d845..7240e0a35c 100644 --- a/npm/packs/core/package.json +++ b/npm/packs/core/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/core", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/utils": "~10.3.0" + "@abp/utils": "~10.4.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/cropperjs/package.json b/npm/packs/cropperjs/package.json index 1a17b70c47..3ac34528c8 100644 --- a/npm/packs/cropperjs/package.json +++ b/npm/packs/cropperjs/package.json @@ -1,11 +1,11 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/cropperjs", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.3.0", + "@abp/core": "~10.4.0", "cropperjs": "^1.6.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/datatables.net-bs4/package.json b/npm/packs/datatables.net-bs4/package.json index 3c03b88c83..2651bacc00 100644 --- a/npm/packs/datatables.net-bs4/package.json +++ b/npm/packs/datatables.net-bs4/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/datatables.net-bs4", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/datatables.net": "~10.3.0", + "@abp/datatables.net": "~10.4.0", "datatables.net-bs4": "^2.3.4" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/datatables.net-bs5/package.json b/npm/packs/datatables.net-bs5/package.json index 8bdc9d9c9b..87e4148b44 100644 --- a/npm/packs/datatables.net-bs5/package.json +++ b/npm/packs/datatables.net-bs5/package.json @@ -1,11 +1,11 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/datatables.net-bs5", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/datatables.net": "~10.3.0", + "@abp/datatables.net": "~10.4.0", "datatables.net-bs5": "^2.3.4" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/datatables.net/package.json b/npm/packs/datatables.net/package.json index 89db065c7a..68ada5243e 100644 --- a/npm/packs/datatables.net/package.json +++ b/npm/packs/datatables.net/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/datatables.net", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/jquery": "~10.3.0", + "@abp/jquery": "~10.4.0", "datatables.net": "^2.3.4" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/docs/package.json b/npm/packs/docs/package.json index 57ddb5e5d3..7dbdbe62c3 100644 --- a/npm/packs/docs/package.json +++ b/npm/packs/docs/package.json @@ -1,15 +1,15 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/docs", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/anchor-js": "~10.3.0", - "@abp/clipboard": "~10.3.0", - "@abp/malihu-custom-scrollbar-plugin": "~10.3.0", - "@abp/popper.js": "~10.3.0", - "@abp/prismjs": "~10.3.0" + "@abp/anchor-js": "~10.4.0", + "@abp/clipboard": "~10.4.0", + "@abp/malihu-custom-scrollbar-plugin": "~10.4.0", + "@abp/popper.js": "~10.4.0", + "@abp/prismjs": "~10.4.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/flag-icon-css/package.json b/npm/packs/flag-icon-css/package.json index 1cc7ffda03..e3150bcce5 100644 --- a/npm/packs/flag-icon-css/package.json +++ b/npm/packs/flag-icon-css/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/flag-icon-css", "publishConfig": { "access": "public" diff --git a/npm/packs/flag-icons/package.json b/npm/packs/flag-icons/package.json index 58b7c08e5e..03d5e89951 100644 --- a/npm/packs/flag-icons/package.json +++ b/npm/packs/flag-icons/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/flag-icons", "publishConfig": { "access": "public" diff --git a/npm/packs/font-awesome/package.json b/npm/packs/font-awesome/package.json index 874a8a9690..d6a63b817b 100644 --- a/npm/packs/font-awesome/package.json +++ b/npm/packs/font-awesome/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/font-awesome", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/core": "~10.3.0", + "@abp/core": "~10.4.0", "@fortawesome/fontawesome-free": "^7.0.1" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/highlight.js/package.json b/npm/packs/highlight.js/package.json index b222cf245a..9905d5157e 100644 --- a/npm/packs/highlight.js/package.json +++ b/npm/packs/highlight.js/package.json @@ -1,11 +1,11 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/highlight.js", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.3.0", + "@abp/core": "~10.4.0", "@highlightjs/cdn-assets": "~11.11.1" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/jquery-form/package.json b/npm/packs/jquery-form/package.json index f11735b972..4df991d2bc 100644 --- a/npm/packs/jquery-form/package.json +++ b/npm/packs/jquery-form/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/jquery-form", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/jquery": "~10.3.0", + "@abp/jquery": "~10.4.0", "jquery-form": "^4.3.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/jquery-validation-unobtrusive/package.json b/npm/packs/jquery-validation-unobtrusive/package.json index 828ff6fc74..0d7b73cd14 100644 --- a/npm/packs/jquery-validation-unobtrusive/package.json +++ b/npm/packs/jquery-validation-unobtrusive/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/jquery-validation-unobtrusive", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/jquery-validation": "~10.3.0", + "@abp/jquery-validation": "~10.4.0", "jquery-validation-unobtrusive": "^4.0.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/jquery-validation/package.json b/npm/packs/jquery-validation/package.json index baab14ce2d..c866883ffd 100644 --- a/npm/packs/jquery-validation/package.json +++ b/npm/packs/jquery-validation/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/jquery-validation", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/jquery": "~10.3.0", + "@abp/jquery": "~10.4.0", "jquery-validation": "^1.21.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/jquery/package.json b/npm/packs/jquery/package.json index 70029c5204..dd8e5421a0 100644 --- a/npm/packs/jquery/package.json +++ b/npm/packs/jquery/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/jquery", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/core": "~10.3.0", + "@abp/core": "~10.4.0", "jquery": "~3.7.1" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/jstree/package.json b/npm/packs/jstree/package.json index fb96448696..a937aa4626 100644 --- a/npm/packs/jstree/package.json +++ b/npm/packs/jstree/package.json @@ -1,11 +1,11 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/jstree", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/jquery": "~10.3.0", + "@abp/jquery": "~10.4.0", "jstree": "^3.3.17" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/lodash/package.json b/npm/packs/lodash/package.json index 28e76c2779..8bc0bb9e65 100644 --- a/npm/packs/lodash/package.json +++ b/npm/packs/lodash/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/lodash", "repository": { "type": "git", @@ -10,8 +10,8 @@ "access": "public" }, "dependencies": { - "@abp/core": "~10.3.0", - "lodash": "^4.17.21" + "@abp/core": "~10.4.0", + "lodash": "^4.18.1" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/luxon/package.json b/npm/packs/luxon/package.json index f2396a199b..8f4a25b404 100644 --- a/npm/packs/luxon/package.json +++ b/npm/packs/luxon/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/luxon", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/core": "~10.3.0", + "@abp/core": "~10.4.0", "luxon": "^3.7.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/malihu-custom-scrollbar-plugin/package.json b/npm/packs/malihu-custom-scrollbar-plugin/package.json index c16ca450dc..34da2724f0 100644 --- a/npm/packs/malihu-custom-scrollbar-plugin/package.json +++ b/npm/packs/malihu-custom-scrollbar-plugin/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/malihu-custom-scrollbar-plugin", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/core": "~10.3.0", + "@abp/core": "~10.4.0", "malihu-custom-scrollbar-plugin": "^3.1.5" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/markdown-it/package.json b/npm/packs/markdown-it/package.json index 8580958a8e..da4b1b42ea 100644 --- a/npm/packs/markdown-it/package.json +++ b/npm/packs/markdown-it/package.json @@ -1,11 +1,11 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/markdown-it", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.3.0", + "@abp/core": "~10.4.0", "markdown-it": "^14.1.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/moment/package.json b/npm/packs/moment/package.json index a13aa8692f..42dc61b15e 100644 --- a/npm/packs/moment/package.json +++ b/npm/packs/moment/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/moment", "repository": { "type": "git", diff --git a/npm/packs/owl.carousel/package.json b/npm/packs/owl.carousel/package.json index 1e6ff74800..b891f5935e 100644 --- a/npm/packs/owl.carousel/package.json +++ b/npm/packs/owl.carousel/package.json @@ -1,11 +1,11 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/owl.carousel", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.3.0", + "@abp/core": "~10.4.0", "owl.carousel": "^2.3.4" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/popper.js/package.json b/npm/packs/popper.js/package.json index d350114bcb..617d7d6c76 100644 --- a/npm/packs/popper.js/package.json +++ b/npm/packs/popper.js/package.json @@ -1,11 +1,11 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/popper.js", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.3.0", + "@abp/core": "~10.4.0", "@popperjs/core": "^2.11.8" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/prismjs/package.json b/npm/packs/prismjs/package.json index 2b7e0336d5..cf688d8983 100644 --- a/npm/packs/prismjs/package.json +++ b/npm/packs/prismjs/package.json @@ -1,12 +1,12 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/prismjs", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/clipboard": "~10.3.0", - "@abp/core": "~10.3.0", + "@abp/clipboard": "~10.4.0", + "@abp/core": "~10.4.0", "prismjs": "^1.30.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/qrcode/package.json b/npm/packs/qrcode/package.json index 7741bb0025..cacad4a3ea 100644 --- a/npm/packs/qrcode/package.json +++ b/npm/packs/qrcode/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/qrcode", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/core": "~10.3.0" + "@abp/core": "~10.4.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/select2/package.json b/npm/packs/select2/package.json index dae26c3e0f..6b542d55f9 100644 --- a/npm/packs/select2/package.json +++ b/npm/packs/select2/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/select2", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/core": "~10.3.0", + "@abp/core": "~10.4.0", "select2": "^4.0.13" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/signalr/package.json b/npm/packs/signalr/package.json index dea95a3d9e..df66a2992d 100644 --- a/npm/packs/signalr/package.json +++ b/npm/packs/signalr/package.json @@ -1,11 +1,11 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/signalr", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.3.0", + "@abp/core": "~10.4.0", "@microsoft/signalr": "~9.0.6" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/slugify/package.json b/npm/packs/slugify/package.json index 6877ce5ee9..987d1db8df 100644 --- a/npm/packs/slugify/package.json +++ b/npm/packs/slugify/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/slugify", "publishConfig": { "access": "public" diff --git a/npm/packs/star-rating-svg/package.json b/npm/packs/star-rating-svg/package.json index 68545837af..c91f372ded 100644 --- a/npm/packs/star-rating-svg/package.json +++ b/npm/packs/star-rating-svg/package.json @@ -1,11 +1,11 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/star-rating-svg", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/jquery": "~10.3.0", + "@abp/jquery": "~10.4.0", "star-rating-svg": "^3.5.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/sweetalert2/package.json b/npm/packs/sweetalert2/package.json index 21ad47efc4..f05e59638e 100644 --- a/npm/packs/sweetalert2/package.json +++ b/npm/packs/sweetalert2/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/sweetalert2", "publishConfig": { "access": "public" @@ -10,7 +10,7 @@ "directory": "npm/packs/sweetalert2" }, "dependencies": { - "@abp/core": "~10.3.0", + "@abp/core": "~10.4.0", "sweetalert2": "^11.23.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/timeago/package.json b/npm/packs/timeago/package.json index b18f80cf35..b82344d248 100644 --- a/npm/packs/timeago/package.json +++ b/npm/packs/timeago/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/timeago", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/jquery": "~10.3.0", + "@abp/jquery": "~10.4.0", "timeago": "^1.6.7" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/toastr/package.json b/npm/packs/toastr/package.json index f93637fbad..791e89876a 100644 --- a/npm/packs/toastr/package.json +++ b/npm/packs/toastr/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/toastr", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/jquery": "~10.3.0", + "@abp/jquery": "~10.4.0", "toastr": "^2.1.4" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/tui-editor/package.json b/npm/packs/tui-editor/package.json index dbf1930750..660f8f2b23 100644 --- a/npm/packs/tui-editor/package.json +++ b/npm/packs/tui-editor/package.json @@ -1,12 +1,12 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/tui-editor", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/jquery": "~10.3.0", - "@abp/prismjs": "~10.3.0" + "@abp/jquery": "~10.4.0", + "@abp/prismjs": "~10.4.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/uppy/package.json b/npm/packs/uppy/package.json index eb662d8db1..62eee541e4 100644 --- a/npm/packs/uppy/package.json +++ b/npm/packs/uppy/package.json @@ -1,11 +1,11 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/uppy", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.3.0", + "@abp/core": "~10.4.0", "uppy": "^5.1.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/utils/package.json b/npm/packs/utils/package.json index 179b467ed6..b707809042 100644 --- a/npm/packs/utils/package.json +++ b/npm/packs/utils/package.json @@ -1,6 +1,6 @@ { "name": "@abp/utils", - "version": "10.3.0", + "version": "10.4.0", "scripts": { "prepublishOnly": "yarn install --ignore-scripts && node prepublish.js", "ng": "ng", diff --git a/npm/packs/vee-validate/package.json b/npm/packs/vee-validate/package.json index 45a83f459c..5b236f5f6c 100644 --- a/npm/packs/vee-validate/package.json +++ b/npm/packs/vee-validate/package.json @@ -1,11 +1,11 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/vee-validate", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/vue": "~10.3.0", + "@abp/vue": "~10.4.0", "vee-validate": "~3.4.4" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/virtual-file-explorer/package.json b/npm/packs/virtual-file-explorer/package.json index a92656f640..eddad1a6eb 100644 --- a/npm/packs/virtual-file-explorer/package.json +++ b/npm/packs/virtual-file-explorer/package.json @@ -1,12 +1,12 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/virtual-file-explorer", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/clipboard": "~10.3.0", - "@abp/prismjs": "~10.3.0" + "@abp/clipboard": "~10.4.0", + "@abp/prismjs": "~10.4.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/vue/package.json b/npm/packs/vue/package.json index 9757819baf..3857af7964 100644 --- a/npm/packs/vue/package.json +++ b/npm/packs/vue/package.json @@ -1,5 +1,5 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/vue", "publishConfig": { "access": "public" diff --git a/npm/packs/zxcvbn/package.json b/npm/packs/zxcvbn/package.json index 760e2fb878..4e241d0d21 100644 --- a/npm/packs/zxcvbn/package.json +++ b/npm/packs/zxcvbn/package.json @@ -1,11 +1,11 @@ { - "version": "10.3.0", + "version": "10.4.0", "name": "@abp/zxcvbn", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.3.0", + "@abp/core": "~10.4.0", "zxcvbn": "^4.4.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/nupkg/common.ps1 b/nupkg/common.ps1 index b64a7cb7fe..9be109e779 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -117,6 +117,14 @@ $projects = ( "framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Bundling", "framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming", "framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.Bundling", + # MudBlazor Theming + "framework/src/Volo.Abp.MudBlazorUI", + "framework/src/Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor", + "framework/src/Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor", + "framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor", + "framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor.Bundling", + "framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor", + "framework/src/Volo.Abp.AspNetCore.Components.MauiBlazor.Theming.MudBlazor.Bundling", "framework/src/Volo.Abp.AspNetCore.MultiTenancy", "framework/src/Volo.Abp.AspNetCore.Mvc.Client", "framework/src/Volo.Abp.AspNetCore.Mvc.Client.Common", @@ -273,6 +281,7 @@ $projects = ( "modules/account/src/Volo.Abp.Account.Web.IdentityServer", "modules/account/src/Volo.Abp.Account.Web.OpenIddict", "modules/account/src/Volo.Abp.Account.Blazor", + "modules/account/src/Volo.Abp.Account.Blazor.MudBlazor", "modules/account/src/Volo.Abp.Account.Installer", "source-code/Volo.Abp.Account.SourceCode", @@ -301,6 +310,12 @@ $projects = ( "modules/basic-theme/src/Volo.Abp.BasicTheme.Installer", "source-code/Volo.Abp.BasicTheme.SourceCode", + # modules/basic-theme (MudBlazor) + "modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme", + "modules/basic-theme/src/Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme", + "modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme", + "modules/basic-theme/src/Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling", + # modules/blogging "modules/blogging/src/Volo.Blogging.Application.Contracts.Shared", "modules/blogging/src/Volo.Blogging.Application.Contracts", @@ -359,6 +374,9 @@ $projects = ( "modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor", "modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.Server", "modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.WebAssembly", + "modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor", + "modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.Server", + "modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor.MudBlazor.WebAssembly", "modules/feature-management/src/Volo.Abp.FeatureManagement.Installer", "source-code/Volo.Abp.FeatureManagement.SourceCode", @@ -376,6 +394,9 @@ $projects = ( "modules/identity/src/Volo.Abp.Identity.Blazor", "modules/identity/src/Volo.Abp.Identity.Blazor.Server", "modules/identity/src/Volo.Abp.Identity.Blazor.WebAssembly", + "modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor", + "modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.Server", + "modules/identity/src/Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly", "modules/identity/src/Volo.Abp.PermissionManagement.Domain.Identity", "modules/identity/src/Volo.Abp.Identity.Installer", "source-code/Volo.Abp.Identity.SourceCode", @@ -412,6 +433,9 @@ $projects = ( "modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor", "modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.Server", "modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.WebAssembly", + "modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor", + "modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.Server", + "modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor.MudBlazor.WebAssembly", "modules/permission-management/src/Volo.Abp.PermissionManagement.Installer", "source-code/Volo.Abp.PermissionManagement.SourceCode", @@ -421,6 +445,9 @@ $projects = ( "modules/setting-management/src/Volo.Abp.SettingManagement.Blazor", "modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.Server", "modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.WebAssembly", + "modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor", + "modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.Server", + "modules/setting-management/src/Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly", "modules/setting-management/src/Volo.Abp.SettingManagement.Domain", "modules/setting-management/src/Volo.Abp.SettingManagement.Domain.Shared", "modules/setting-management/src/Volo.Abp.SettingManagement.EntityFrameworkCore", @@ -437,6 +464,9 @@ $projects = ( "modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor", "modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.Server", "modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.WebAssembly", + "modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor", + "modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.Server", + "modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly", "modules/tenant-management/src/Volo.Abp.TenantManagement.Domain", "modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared", "modules/tenant-management/src/Volo.Abp.TenantManagement.EntityFrameworkCore", diff --git a/nupkg/push_packages.ps1 b/nupkg/push_packages.ps1 index 5b37c22572..f192c2a560 100644 --- a/nupkg/push_packages.ps1 +++ b/nupkg/push_packages.ps1 @@ -13,7 +13,9 @@ $failedPackages = @() $totalProjectsCount = $projects.length $nugetUrl = "https://api.nuget.org/v3/index.json" $maxQuotaRetryCount = 3 -$quotaRetryDelaysInSeconds = @(30, 60, 120) +$defaultQuotaRetryDelaysInSeconds = @(30, 60, 120) +$maxPushesPerHour = 250 # NuGet.org rate limit (conservative). Set 0 to disable. +$pushTimestamps = [System.Collections.Generic.List[datetime]]::new() $failedPackagesFilePath = Join-Path $packFolder "failed-packages.txt" Set-Location $packFolder if (Test-Path $failedPackagesFilePath) { Remove-Item $failedPackagesFilePath -Force } @@ -29,6 +31,25 @@ foreach($project in $projects) { if ($nugetPackageExists) { + # Sliding-window rate limiter: ensure we don't exceed $maxPushesPerHour pushes per hour + if ($maxPushesPerHour -gt 0) + { + $windowStart = (Get-Date).AddHours(-1) + $recentPushes = $pushTimestamps | Where-Object { $_ -gt $windowStart } + $pushTimestamps = [System.Collections.Generic.List[datetime]]$recentPushes + + if ($pushTimestamps.Count -ge $maxPushesPerHour) + { + $waitUntil = $pushTimestamps[0].AddHours(1) + $waitSeconds = [int]([math]::Ceiling(($waitUntil - (Get-Date)).TotalSeconds)) + 1 + if ($waitSeconds -gt 0) + { + Write-Warning "Rate limit: $($pushTimestamps.Count) pushes in the last hour (limit: $maxPushesPerHour). Pausing $waitSeconds seconds until the window resets..." + Start-Sleep -Seconds $waitSeconds + } + } + } + $attempt = 0 $pushSucceeded = $false while ($true) @@ -51,7 +72,17 @@ foreach($project in $projects) { break } - $retryDelay = $quotaRetryDelaysInSeconds[$attempt - 1] + # Parse the retry-after value from the NuGet response if present (e.g. "retry after: 2974s") + $retryAfterMatch = ($pushOutput | Out-String) | Select-String -Pattern 'retry after:\s*(\d+)s' -AllMatches + if ($retryAfterMatch -and $retryAfterMatch.Matches.Count -gt 0) + { + $retryDelay = [int]$retryAfterMatch.Matches[0].Groups[1].Value + } + else + { + $retryDelay = $defaultQuotaRetryDelaysInSeconds[$attempt - 1] + } + Write-Warning "NuGet push returned a 4xx response for $nugetPackageName. Retrying in $retryDelay seconds (retry $attempt/$maxQuotaRetryCount)..." Start-Sleep -Seconds $retryDelay } @@ -62,6 +93,10 @@ foreach($project in $projects) { $errorCount += 1 $failedPackages += $nugetPackageName } + else + { + $pushTimestamps.Add((Get-Date)) + } #Write-Host ("Deleting package from local: " + $nugetPackageName) #Remove-Item $nugetPackageName -Force } diff --git a/source-code/Volo.Abp.Account.SourceCode/Volo.Abp.Account.SourceCode.zip b/source-code/Volo.Abp.Account.SourceCode/Volo.Abp.Account.SourceCode.zip index 8ecc797a1c..3a496157f5 100644 Binary files a/source-code/Volo.Abp.Account.SourceCode/Volo.Abp.Account.SourceCode.zip and b/source-code/Volo.Abp.Account.SourceCode/Volo.Abp.Account.SourceCode.zip differ diff --git a/source-code/Volo.Abp.AuditLogging.SourceCode/Volo.Abp.AuditLogging.SourceCode.zip b/source-code/Volo.Abp.AuditLogging.SourceCode/Volo.Abp.AuditLogging.SourceCode.zip index 167741ae54..e1ca33d049 100644 Binary files a/source-code/Volo.Abp.AuditLogging.SourceCode/Volo.Abp.AuditLogging.SourceCode.zip and b/source-code/Volo.Abp.AuditLogging.SourceCode/Volo.Abp.AuditLogging.SourceCode.zip differ diff --git a/source-code/Volo.Abp.BackgroundJobs.SourceCode/Volo.Abp.BackgroundJobs.SourceCode.zip b/source-code/Volo.Abp.BackgroundJobs.SourceCode/Volo.Abp.BackgroundJobs.SourceCode.zip index e48ebcd319..a426ca0b9d 100644 Binary files a/source-code/Volo.Abp.BackgroundJobs.SourceCode/Volo.Abp.BackgroundJobs.SourceCode.zip and b/source-code/Volo.Abp.BackgroundJobs.SourceCode/Volo.Abp.BackgroundJobs.SourceCode.zip differ diff --git a/source-code/Volo.Abp.BlobStoring.Database.SourceCode/Volo.Abp.BlobStoring.Database.SourceCode.zip b/source-code/Volo.Abp.BlobStoring.Database.SourceCode/Volo.Abp.BlobStoring.Database.SourceCode.zip index 12f83ceff0..ebec6915ef 100644 Binary files a/source-code/Volo.Abp.BlobStoring.Database.SourceCode/Volo.Abp.BlobStoring.Database.SourceCode.zip and b/source-code/Volo.Abp.BlobStoring.Database.SourceCode/Volo.Abp.BlobStoring.Database.SourceCode.zip differ diff --git a/source-code/Volo.Abp.FeatureManagement.SourceCode/Volo.Abp.FeatureManagement.SourceCode.zip b/source-code/Volo.Abp.FeatureManagement.SourceCode/Volo.Abp.FeatureManagement.SourceCode.zip index 7a4df5194e..2c55be8f9e 100644 Binary files a/source-code/Volo.Abp.FeatureManagement.SourceCode/Volo.Abp.FeatureManagement.SourceCode.zip and b/source-code/Volo.Abp.FeatureManagement.SourceCode/Volo.Abp.FeatureManagement.SourceCode.zip differ diff --git a/source-code/Volo.Abp.Identity.SourceCode/Volo.Abp.Identity.SourceCode.zip b/source-code/Volo.Abp.Identity.SourceCode/Volo.Abp.Identity.SourceCode.zip index 4bdbac2591..acda6b15dc 100644 Binary files a/source-code/Volo.Abp.Identity.SourceCode/Volo.Abp.Identity.SourceCode.zip and b/source-code/Volo.Abp.Identity.SourceCode/Volo.Abp.Identity.SourceCode.zip differ diff --git a/source-code/Volo.Abp.IdentityServer.SourceCode/Volo.Abp.IdentityServer.SourceCode.zip b/source-code/Volo.Abp.IdentityServer.SourceCode/Volo.Abp.IdentityServer.SourceCode.zip index 0bba3541e4..b6b148e695 100644 Binary files a/source-code/Volo.Abp.IdentityServer.SourceCode/Volo.Abp.IdentityServer.SourceCode.zip and b/source-code/Volo.Abp.IdentityServer.SourceCode/Volo.Abp.IdentityServer.SourceCode.zip differ diff --git a/source-code/Volo.Abp.OpenIddict.SourceCode/Volo.Abp.OpenIddict.SourceCode.zip b/source-code/Volo.Abp.OpenIddict.SourceCode/Volo.Abp.OpenIddict.SourceCode.zip index 7236a098a8..fd142199d1 100644 Binary files a/source-code/Volo.Abp.OpenIddict.SourceCode/Volo.Abp.OpenIddict.SourceCode.zip and b/source-code/Volo.Abp.OpenIddict.SourceCode/Volo.Abp.OpenIddict.SourceCode.zip differ diff --git a/source-code/Volo.Abp.PermissionManagement.SourceCode/Volo.Abp.PermissionManagement.SourceCode.zip b/source-code/Volo.Abp.PermissionManagement.SourceCode/Volo.Abp.PermissionManagement.SourceCode.zip index af1c59d8b9..fb77821a9d 100644 Binary files a/source-code/Volo.Abp.PermissionManagement.SourceCode/Volo.Abp.PermissionManagement.SourceCode.zip and b/source-code/Volo.Abp.PermissionManagement.SourceCode/Volo.Abp.PermissionManagement.SourceCode.zip differ diff --git a/source-code/Volo.Abp.SettingManagement.SourceCode/Volo.Abp.SettingManagement.SourceCode.zip b/source-code/Volo.Abp.SettingManagement.SourceCode/Volo.Abp.SettingManagement.SourceCode.zip index fb65556ea4..1b781b64ed 100644 Binary files a/source-code/Volo.Abp.SettingManagement.SourceCode/Volo.Abp.SettingManagement.SourceCode.zip and b/source-code/Volo.Abp.SettingManagement.SourceCode/Volo.Abp.SettingManagement.SourceCode.zip differ diff --git a/source-code/Volo.Abp.TenantManagement.SourceCode/Volo.Abp.TenantManagement.SourceCode.zip b/source-code/Volo.Abp.TenantManagement.SourceCode/Volo.Abp.TenantManagement.SourceCode.zip index 61650f54bf..ca84861e1b 100644 Binary files a/source-code/Volo.Abp.TenantManagement.SourceCode/Volo.Abp.TenantManagement.SourceCode.zip and b/source-code/Volo.Abp.TenantManagement.SourceCode/Volo.Abp.TenantManagement.SourceCode.zip differ diff --git a/source-code/Volo.Abp.Users.SourceCode/Volo.Abp.Users.SourceCode.zip b/source-code/Volo.Abp.Users.SourceCode/Volo.Abp.Users.SourceCode.zip index afcf53089a..8287f904b9 100644 Binary files a/source-code/Volo.Abp.Users.SourceCode/Volo.Abp.Users.SourceCode.zip and b/source-code/Volo.Abp.Users.SourceCode/Volo.Abp.Users.SourceCode.zip differ diff --git a/source-code/Volo.Abp.VirtualFileExplorer.SourceCode/Volo.Abp.VirtualFileExplorer.SourceCode.zip b/source-code/Volo.Abp.VirtualFileExplorer.SourceCode/Volo.Abp.VirtualFileExplorer.SourceCode.zip index c68493b9ba..24c7e65fb9 100644 Binary files a/source-code/Volo.Abp.VirtualFileExplorer.SourceCode/Volo.Abp.VirtualFileExplorer.SourceCode.zip and b/source-code/Volo.Abp.VirtualFileExplorer.SourceCode/Volo.Abp.VirtualFileExplorer.SourceCode.zip differ diff --git a/source-code/Volo.Blogging.SourceCode/Volo.Blogging.SourceCode.zip b/source-code/Volo.Blogging.SourceCode/Volo.Blogging.SourceCode.zip index f80adf291b..b54d5a2bc2 100644 Binary files a/source-code/Volo.Blogging.SourceCode/Volo.Blogging.SourceCode.zip and b/source-code/Volo.Blogging.SourceCode/Volo.Blogging.SourceCode.zip differ diff --git a/source-code/Volo.CmsKit.SourceCode/Volo.CmsKit.SourceCode.zip b/source-code/Volo.CmsKit.SourceCode/Volo.CmsKit.SourceCode.zip index a7610d72ec..c79eae7884 100644 Binary files a/source-code/Volo.CmsKit.SourceCode/Volo.CmsKit.SourceCode.zip and b/source-code/Volo.CmsKit.SourceCode/Volo.CmsKit.SourceCode.zip differ diff --git a/source-code/Volo.Docs.SourceCode/Volo.Docs.SourceCode.zip b/source-code/Volo.Docs.SourceCode/Volo.Docs.SourceCode.zip index 8b46011374..085401311d 100644 Binary files a/source-code/Volo.Docs.SourceCode/Volo.Docs.SourceCode.zip and b/source-code/Volo.Docs.SourceCode/Volo.Docs.SourceCode.zip differ diff --git a/templates/app-nolayers/angular/package.json b/templates/app-nolayers/angular/package.json index 85cd237044..43cde95c59 100644 --- a/templates/app-nolayers/angular/package.json +++ b/templates/app-nolayers/angular/package.json @@ -12,15 +12,15 @@ }, "private": true, "dependencies": { - "@abp/ng.account": "~10.3.0", - "@abp/ng.components": "~10.3.0", - "@abp/ng.core": "~10.3.0", - "@abp/ng.identity": "~10.3.0", - "@abp/ng.oauth": "~10.3.0", - "@abp/ng.setting-management": "~10.3.0", - "@abp/ng.tenant-management": "~10.3.0", - "@abp/ng.theme.lepton-x": "~5.3.0", - "@abp/ng.theme.shared": "~10.3.0", + "@abp/ng.account": "~10.4.0", + "@abp/ng.components": "~10.4.0", + "@abp/ng.core": "~10.4.0", + "@abp/ng.identity": "~10.4.0", + "@abp/ng.oauth": "~10.4.0", + "@abp/ng.setting-management": "~10.4.0", + "@abp/ng.tenant-management": "~10.4.0", + "@abp/ng.theme.lepton-x": "~5.4.0", + "@abp/ng.theme.shared": "~10.4.0", "@angular/animations": "~21.2.0", "@angular/aria": "~21.2.0", "@angular/common": "~21.2.0", @@ -37,7 +37,7 @@ "zone.js": "~0.15.0" }, "devDependencies": { - "@abp/ng.schematics": "~10.3.0", + "@abp/ng.schematics": "~10.4.0", "@angular-eslint/builder": "~21.2.0", "@angular-eslint/eslint-plugin": "~21.2.0", "@angular-eslint/eslint-plugin-template": "~21.2.0", diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/Components/App.razor b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/Components/App.razor index 9f9aa51130..8b2ce005e7 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/Components/App.razor +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/Components/App.razor @@ -1,8 +1,8 @@ @using Volo.Abp.Localization @using System.Globalization @using Microsoft.Extensions.Hosting -@using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Bundling -@using Volo.Abp.AspNetCore.Components.Web.Theming.Bundling +@using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Bundling +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Bundling @inject IHostEnvironment Env @{ var rtl = CultureHelper.IsRtl ? "rtl" : string.Empty; @@ -17,7 +17,8 @@ MyCompanyName.MyProjectName.Blazor.Server - + + @@ -26,7 +27,7 @@ - + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/Components/Pages/Index.razor b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/Components/Pages/Index.razor index c67303f497..552e6d89e4 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/Components/Pages/Index.razor +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/Components/Pages/Index.razor @@ -1,281 +1,127 @@ -@page "/" +@page "/" @inherits MyProjectNameComponentBase -
-
-
-
-
-

Getting Started

-

Learn how to create and run - a - new web application using the application startup - template.

- Getting - Started -

Web Application Development Tutorial

-

Learn how to build an ABP based - web - application named Acme.BookStore.

- Explore Tutorial + + + + + @if (!CurrentUser.IsAuthenticated) + { + @L["Welcome"] + @L["LongWelcomeMessage"] + @L["Login"] + } -

Customize Lepton Theme

-

Learn how to customize LeptonX - Theme - as you wish.

- Customize Lepton -
- -
-
-
-
-
-
-
-
- -

You can check for - similar problems and solutions, or open a - new topic to discuss your specific issue.

- Visit Support - -
-
-
-
-
-
- -

You can find content - on .NET development, cross-platform, ASP.NET - application templates, ABP-related news, and - more.

- Visit Blog + Getting Started + Learn how to create and run a new web application using the application startup template. + Getting Started - -
-
-
-
-
-
-
-
- -

A unique community platform - for ABP Lovers!

-

Explore all ABP users' - experiences with the ABP Framework, discover - articles and videos on how to use ABP, and join - raffles for a chance to win surprise gifts!

- Join ABP - Community -
- -
-
-
+ Web Application Development Tutorial + Learn how to build an ABP based web application named Acme.BookStore. + Explore Tutorial + + + + + + + + Community + + A unique community platform for ABP Lovers! + Explore all ABP users' experiences with the ABP Framework, discover articles and videos on how to use ABP, and join raffles for a chance to win surprise gifts! + Join ABP Community -
-
-
-
-
More from ABP.IO
-
-
+ -
- - - - - - - - - - - - - - - - - - - - - - - -
-
-
Latest Release Logs
-
- - - -
-
-
Video Courses
-
- - - -
-
-
Samples
-
- - - -
-
-
Books
-
- - - -
-
-
FAQ
-
- - - -
-
-
-
-
-
-
-
-
-
-

THE OFFICIAL GUIDE

-

Mastering ABP Framework

-

Written by the creator of the ABP - Framework, this book will help you gain a complete - understanding of the framework - and modern web application development techniques.

- -
-
- -
-
+ + Blog + + Find content on .NET development, cross-platform, ASP.NET application templates, ABP-related news, and more. + Visit Blog + + + + -
-
-
-
-
-
-
Follow us on Social Media
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
- -
X.com
-
- - - -
- -
Discord
-
- - - -
- -
Stack Overflow
-
- - - -
- -
YouTube
-
- - - -
- -
Instagram
-
- - - -
-
-
-
-
-
\ No newline at end of file + + + + + More from ABP.IO + + + + + Latest Release Logs + + + Video Courses + + + Samples + + + Books + + + FAQ + + + + + + + + + Follow us on Social Media + + + + + + + X.com (Twitter) + + + Discord + + + Stack Overflow + + + + + + + YouTube + + + Instagram + + + GitHub + + + + + + + + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/Components/Routes.razor b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/Components/Routes.razor index fff90cbc35..9788fed14f 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/Components/Routes.razor +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/Components/Routes.razor @@ -1,5 +1,5 @@ -@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite -@using Volo.Abp.AspNetCore.Components.Web.Theming.Routing +@using Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme.Themes.Basic +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing @using Microsoft.Extensions.Options @inject IOptions RouterOptions diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/Menus/MyProjectNameMenuContributor.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/Menus/MyProjectNameMenuContributor.cs index 2a32b76193..c8c3833fa8 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/Menus/MyProjectNameMenuContributor.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/Menus/MyProjectNameMenuContributor.cs @@ -1,7 +1,7 @@ using MyCompanyName.MyProjectName.Localization; -using Volo.Abp.Identity.Blazor; -using Volo.Abp.SettingManagement.Blazor.Menus; -using Volo.Abp.TenantManagement.Blazor.Navigation; +using Volo.Abp.Identity.Blazor.MudBlazor; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.Menus; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.Navigation; using Volo.Abp.UI.Navigation; namespace MyCompanyName.MyProjectName.Menus; diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj index 8459c80cf0..6ae4b6243b 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyCompanyName.MyProjectName.Blazor.Server.Mongo.csproj @@ -8,8 +8,6 @@ - - @@ -18,11 +16,11 @@ - - + + - - + +
@@ -45,7 +43,7 @@ - + @@ -59,21 +57,21 @@ - + - + - + @@ -81,7 +79,7 @@ - + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs index 151f57de6c..df48e3abe5 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs @@ -1,5 +1,3 @@ -using Blazorise.Bootstrap5; -using Blazorise.Icons.FontAwesome; using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; @@ -14,10 +12,10 @@ using Volo.Abp.Account; using Volo.Abp.Account.Web; using Volo.Abp.AspNetCore.Components.Web; using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite; -using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme; -using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Bundling; +using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme; +using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite.Bundling; -using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; @@ -28,10 +26,10 @@ using Volo.Abp.Mapperly; using Volo.Abp.MongoDB; using Volo.Abp.Emailing; using Volo.Abp.FeatureManagement; -using Volo.Abp.FeatureManagement.Blazor.Server; +using Volo.Abp.FeatureManagement.Blazor.MudBlazor.Server; using Volo.Abp.FeatureManagement.MongoDB; using Volo.Abp.Identity; -using Volo.Abp.Identity.Blazor.Server; +using Volo.Abp.Identity.Blazor.MudBlazor.Server; using Volo.Abp.Identity.MongoDB; using Volo.Abp.Localization; using Volo.Abp.Localization.ExceptionHandling; @@ -43,11 +41,11 @@ using Volo.Abp.PermissionManagement.HttpApi; using Volo.Abp.PermissionManagement.Identity; using Volo.Abp.PermissionManagement.OpenIddict; using Volo.Abp.SettingManagement; -using Volo.Abp.SettingManagement.Blazor.Server; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.Server; using Volo.Abp.SettingManagement.MongoDB; using Volo.Abp.Swashbuckle; using Volo.Abp.TenantManagement; -using Volo.Abp.TenantManagement.Blazor.Server; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.Server; using Volo.Abp.TenantManagement.MongoDB; using Volo.Abp.OpenIddict; using Volo.Abp.Security.Claims; @@ -67,7 +65,7 @@ namespace MyCompanyName.MyProjectName; typeof(AbpSwashbuckleModule), typeof(AbpAspNetCoreSerilogModule), typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule), - typeof(AbpAspNetCoreComponentsServerLeptonXLiteThemeModule), + typeof(AbpAspNetCoreComponentsServerMudBlazorBasicThemeModule), // Account module packages typeof(AbpAccountApplicationModule), @@ -81,7 +79,7 @@ namespace MyCompanyName.MyProjectName; typeof(AbpIdentityHttpApiModule), typeof(AbpIdentityMongoDbModule), typeof(AbpOpenIddictMongoDbModule), - typeof(AbpIdentityBlazorServerModule), + typeof(AbpIdentityBlazorMudBlazorServerModule), // Audit logging module packages typeof(AbpAuditLoggingMongoDbModule), @@ -95,19 +93,19 @@ namespace MyCompanyName.MyProjectName; typeof(AbpTenantManagementApplicationModule), typeof(AbpTenantManagementHttpApiModule), typeof(AbpTenantManagementMongoDbModule), - typeof(AbpTenantManagementBlazorServerModule), + typeof(AbpTenantManagementBlazorMudBlazorServerModule), // Feature Management module packages typeof(AbpFeatureManagementApplicationModule), typeof(AbpFeatureManagementMongoDbModule), typeof(AbpFeatureManagementHttpApiModule), - typeof(AbpFeatureManagementBlazorServerModule), + typeof(AbpFeatureManagementBlazorMudBlazorServerModule), // Setting Management module packages typeof(AbpSettingManagementApplicationModule), typeof(AbpSettingManagementMongoDbModule), typeof(AbpSettingManagementHttpApiModule), - typeof(AbpSettingManagementBlazorServerModule) + typeof(AbpSettingManagementBlazorMudBlazorServerModule) )] public class MyProjectNameModule : AbpModule { @@ -181,7 +179,6 @@ public class MyProjectNameModule : AbpModule ConfigureSwaggerServices(context.Services); ConfigureNavigationServices(); ConfigureAutoApiControllers(); - ConfigureBlazorise(context); ConfigureRouter(context); ConfigureMongoDB(context); @@ -221,7 +218,7 @@ public class MyProjectNameModule : AbpModule //BLAZOR UI options.StyleBundles.Configure( - BlazorLeptonXLiteThemeBundles.Styles.Global, + BlazorMudBlazorBasicThemeBundles.Styles.Global, bundle => { bundle.AddFiles("/blazor-global-styles.css"); @@ -296,13 +293,6 @@ public class MyProjectNameModule : AbpModule ); } - private void ConfigureBlazorise(ServiceConfigurationContext context) - { - context.Services - .AddBootstrap5Providers() - .AddFontAwesomeIcons(); - } - private void ConfigureRouter(ServiceConfigurationContext context) { Configure(options => diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/_Imports.razor b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/_Imports.razor index ad711d0c79..063a0594e4 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/_Imports.razor +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/_Imports.razor @@ -8,7 +8,6 @@ @using static Microsoft.AspNetCore.Components.Web.RenderMode @using Microsoft.JSInterop @using MyCompanyName.MyProjectName -@using Blazorise -@using Blazorise.DataGrid -@using Volo.Abp.BlazoriseUI -@using Volo.Abp.BlazoriseUI.Components +@using global::MudBlazor +@using Volo.Abp.MudBlazorUI +@using Volo.Abp.MudBlazorUI.Components diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/package.json b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/package.json index 234a873cb7..1ddb8af741 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/package.json +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/package.json @@ -3,7 +3,7 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.3.0", - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.3.0" + "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.4.0", + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.4.0" } } diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Components/App.razor b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Components/App.razor index 9f9aa51130..8b2ce005e7 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Components/App.razor +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Components/App.razor @@ -1,8 +1,8 @@ @using Volo.Abp.Localization @using System.Globalization @using Microsoft.Extensions.Hosting -@using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Bundling -@using Volo.Abp.AspNetCore.Components.Web.Theming.Bundling +@using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Bundling +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Bundling @inject IHostEnvironment Env @{ var rtl = CultureHelper.IsRtl ? "rtl" : string.Empty; @@ -17,7 +17,8 @@ MyCompanyName.MyProjectName.Blazor.Server - + + @@ -26,7 +27,7 @@ - + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Components/Pages/Index.razor b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Components/Pages/Index.razor index c67303f497..552e6d89e4 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Components/Pages/Index.razor +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Components/Pages/Index.razor @@ -1,281 +1,127 @@ -@page "/" +@page "/" @inherits MyProjectNameComponentBase -
-
-
-
-
-

Getting Started

-

Learn how to create and run - a - new web application using the application startup - template.

- Getting - Started -

Web Application Development Tutorial

-

Learn how to build an ABP based - web - application named Acme.BookStore.

- Explore Tutorial + + + + + @if (!CurrentUser.IsAuthenticated) + { + @L["Welcome"] + @L["LongWelcomeMessage"] + @L["Login"] + } -

Customize Lepton Theme

-

Learn how to customize LeptonX - Theme - as you wish.

- Customize Lepton -
- -
-
-
-
-
-
-
-
- -

You can check for - similar problems and solutions, or open a - new topic to discuss your specific issue.

- Visit Support - -
-
-
-
-
-
- -

You can find content - on .NET development, cross-platform, ASP.NET - application templates, ABP-related news, and - more.

- Visit Blog + Getting Started + Learn how to create and run a new web application using the application startup template. + Getting Started - -
-
-
-
-
-
-
-
- -

A unique community platform - for ABP Lovers!

-

Explore all ABP users' - experiences with the ABP Framework, discover - articles and videos on how to use ABP, and join - raffles for a chance to win surprise gifts!

- Join ABP - Community -
- -
-
-
+ Web Application Development Tutorial + Learn how to build an ABP based web application named Acme.BookStore. + Explore Tutorial + + + + + + + + Community + + A unique community platform for ABP Lovers! + Explore all ABP users' experiences with the ABP Framework, discover articles and videos on how to use ABP, and join raffles for a chance to win surprise gifts! + Join ABP Community -
-
-
-
-
More from ABP.IO
-
-
+ -
- - - - - - - - - - - - - - - - - - - - - - - -
-
-
Latest Release Logs
-
- - - -
-
-
Video Courses
-
- - - -
-
-
Samples
-
- - - -
-
-
Books
-
- - - -
-
-
FAQ
-
- - - -
-
-
-
-
-
-
-
-
-
-

THE OFFICIAL GUIDE

-

Mastering ABP Framework

-

Written by the creator of the ABP - Framework, this book will help you gain a complete - understanding of the framework - and modern web application development techniques.

- -
-
- -
-
+ + Blog + + Find content on .NET development, cross-platform, ASP.NET application templates, ABP-related news, and more. + Visit Blog + + + + -
-
-
-
-
-
-
Follow us on Social Media
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
- -
X.com
-
- - - -
- -
Discord
-
- - - -
- -
Stack Overflow
-
- - - -
- -
YouTube
-
- - - -
- -
Instagram
-
- - - -
-
-
-
-
-
\ No newline at end of file + + + + + More from ABP.IO + + + + + Latest Release Logs + + + Video Courses + + + Samples + + + Books + + + FAQ + + + + + + + + + Follow us on Social Media + + + + + + + X.com (Twitter) + + + Discord + + + Stack Overflow + + + + + + + YouTube + + + Instagram + + + GitHub + + + + + + + + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Components/Routes.razor b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Components/Routes.razor index fff90cbc35..9788fed14f 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Components/Routes.razor +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Components/Routes.razor @@ -1,5 +1,5 @@ -@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite -@using Volo.Abp.AspNetCore.Components.Web.Theming.Routing +@using Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme.Themes.Basic +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing @using Microsoft.Extensions.Options @inject IOptions RouterOptions diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Menus/MyProjectNameMenuContributor.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Menus/MyProjectNameMenuContributor.cs index 2a32b76193..c8c3833fa8 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Menus/MyProjectNameMenuContributor.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/Menus/MyProjectNameMenuContributor.cs @@ -1,7 +1,7 @@ using MyCompanyName.MyProjectName.Localization; -using Volo.Abp.Identity.Blazor; -using Volo.Abp.SettingManagement.Blazor.Menus; -using Volo.Abp.TenantManagement.Blazor.Navigation; +using Volo.Abp.Identity.Blazor.MudBlazor; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.Menus; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.Navigation; using Volo.Abp.UI.Navigation; namespace MyCompanyName.MyProjectName.Menus; diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj index 20d63dd3ab..f72a142579 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj @@ -8,8 +8,6 @@ - - @@ -18,11 +16,11 @@ - - + + - - + +
@@ -46,7 +44,7 @@ - + @@ -60,21 +58,21 @@ - + - + - + @@ -82,11 +80,11 @@ - + - + runtime; build; native; contentfiles; analyzers compile; contentFiles; build; buildMultitargeting; buildTransitive; analyzers; native diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs index 8bf5f371fe..fdadb06ae6 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs @@ -1,5 +1,3 @@ -using Blazorise.Bootstrap5; -using Blazorise.Icons.FontAwesome; using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; @@ -15,10 +13,10 @@ using Volo.Abp.Account; using Volo.Abp.Account.Web; using Volo.Abp.AspNetCore.Components.Web; using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite; -using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme; -using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Bundling; +using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme; +using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite.Bundling; -using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; @@ -30,10 +28,10 @@ using Volo.Abp.Emailing; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore.SqlServer; using Volo.Abp.FeatureManagement; -using Volo.Abp.FeatureManagement.Blazor.Server; +using Volo.Abp.FeatureManagement.Blazor.MudBlazor.Server; using Volo.Abp.FeatureManagement.EntityFrameworkCore; using Volo.Abp.Identity; -using Volo.Abp.Identity.Blazor.Server; +using Volo.Abp.Identity.Blazor.MudBlazor.Server; using Volo.Abp.Identity.EntityFrameworkCore; using Volo.Abp.Localization; using Volo.Abp.Localization.ExceptionHandling; @@ -45,11 +43,11 @@ using Volo.Abp.PermissionManagement.HttpApi; using Volo.Abp.PermissionManagement.Identity; using Volo.Abp.PermissionManagement.OpenIddict; using Volo.Abp.SettingManagement; -using Volo.Abp.SettingManagement.Blazor.Server; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.Server; using Volo.Abp.SettingManagement.EntityFrameworkCore; using Volo.Abp.Swashbuckle; using Volo.Abp.TenantManagement; -using Volo.Abp.TenantManagement.Blazor.Server; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.Server; using Volo.Abp.TenantManagement.EntityFrameworkCore; using Volo.Abp.OpenIddict; using Volo.Abp.Security.Claims; @@ -69,7 +67,7 @@ namespace MyCompanyName.MyProjectName; typeof(AbpSwashbuckleModule), typeof(AbpAspNetCoreSerilogModule), typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule), - typeof(AbpAspNetCoreComponentsServerLeptonXLiteThemeModule), + typeof(AbpAspNetCoreComponentsServerMudBlazorBasicThemeModule), // Account module packages typeof(AbpAccountApplicationModule), @@ -83,7 +81,7 @@ namespace MyCompanyName.MyProjectName; typeof(AbpIdentityHttpApiModule), typeof(AbpIdentityEntityFrameworkCoreModule), typeof(AbpOpenIddictEntityFrameworkCoreModule), - typeof(AbpIdentityBlazorServerModule), + typeof(AbpIdentityBlazorMudBlazorServerModule), // Audit logging module packages typeof(AbpAuditLoggingEntityFrameworkCoreModule), @@ -97,19 +95,19 @@ namespace MyCompanyName.MyProjectName; typeof(AbpTenantManagementApplicationModule), typeof(AbpTenantManagementHttpApiModule), typeof(AbpTenantManagementEntityFrameworkCoreModule), - typeof(AbpTenantManagementBlazorServerModule), + typeof(AbpTenantManagementBlazorMudBlazorServerModule), // Feature Management module packages typeof(AbpFeatureManagementApplicationModule), typeof(AbpFeatureManagementEntityFrameworkCoreModule), typeof(AbpFeatureManagementHttpApiModule), - typeof(AbpFeatureManagementBlazorServerModule), + typeof(AbpFeatureManagementBlazorMudBlazorServerModule), // Setting Management module packages typeof(AbpSettingManagementApplicationModule), typeof(AbpSettingManagementEntityFrameworkCoreModule), typeof(AbpSettingManagementHttpApiModule), - typeof(AbpSettingManagementBlazorServerModule) + typeof(AbpSettingManagementBlazorMudBlazorServerModule) )] public class MyProjectNameModule : AbpModule { @@ -184,7 +182,6 @@ public class MyProjectNameModule : AbpModule ConfigureSwaggerServices(context.Services); ConfigureNavigationServices(); ConfigureAutoApiControllers(); - ConfigureBlazorise(context); ConfigureRouter(context); ConfigureEfCore(context); @@ -224,7 +221,7 @@ public class MyProjectNameModule : AbpModule //BLAZOR UI options.StyleBundles.Configure( - BlazorLeptonXLiteThemeBundles.Styles.Global, + BlazorMudBlazorBasicThemeBundles.Styles.Global, bundle => { bundle.AddFiles("/blazor-global-styles.css"); @@ -299,13 +296,6 @@ public class MyProjectNameModule : AbpModule ); } - private void ConfigureBlazorise(ServiceConfigurationContext context) - { - context.Services - .AddBootstrap5Providers() - .AddFontAwesomeIcons(); - } - private void ConfigureRouter(ServiceConfigurationContext context) { Configure(options => diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/_Imports.razor b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/_Imports.razor index ad711d0c79..063a0594e4 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/_Imports.razor +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/_Imports.razor @@ -8,7 +8,6 @@ @using static Microsoft.AspNetCore.Components.Web.RenderMode @using Microsoft.JSInterop @using MyCompanyName.MyProjectName -@using Blazorise -@using Blazorise.DataGrid -@using Volo.Abp.BlazoriseUI -@using Volo.Abp.BlazoriseUI.Components +@using global::MudBlazor +@using Volo.Abp.MudBlazorUI +@using Volo.Abp.MudBlazorUI.Components diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/package.json b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/package.json index 0479161a12..964755e648 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/package.json +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/package.json @@ -3,7 +3,7 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.3.0", - "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.3.0" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.4.0", + "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.4.0" } } diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/Menus/MyProjectNameMenuContributor.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/Menus/MyProjectNameMenuContributor.cs index efd3a3393c..f7c3af5da5 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/Menus/MyProjectNameMenuContributor.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/Menus/MyProjectNameMenuContributor.cs @@ -3,9 +3,9 @@ using MyCompanyName.MyProjectName.Localization; using MyCompanyName.MyProjectName.MultiTenancy; using Volo.Abp.Account.Localization; using Volo.Abp.Authorization.Permissions; -using Volo.Abp.Identity.Blazor; -using Volo.Abp.SettingManagement.Blazor.Menus; -using Volo.Abp.TenantManagement.Blazor.Navigation; +using Volo.Abp.Identity.Blazor.MudBlazor; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.Menus; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.Navigation; using Volo.Abp.UI.Navigation; namespace MyCompanyName.MyProjectName.Menus; diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyCompanyName.MyProjectName.Blazor.WebAssembly.Client.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyCompanyName.MyProjectName.Blazor.WebAssembly.Client.csproj index 4d6e8b02e8..766d307e57 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyCompanyName.MyProjectName.Blazor.WebAssembly.Client.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyCompanyName.MyProjectName.Blazor.WebAssembly.Client.csproj @@ -9,19 +9,17 @@ - - - - + + - + - + - + @@ -29,9 +27,9 @@ - - - + + + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyProjectNameBlazorModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyProjectNameBlazorModule.cs index aeeec93107..0cf8f0e637 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyProjectNameBlazorModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/MyProjectNameBlazorModule.cs @@ -1,26 +1,22 @@ -using Blazorise.Bootstrap5; -using Blazorise.Icons.FontAwesome; -using Microsoft.AspNetCore.Components.Web; -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using MyCompanyName.MyProjectName.Menus; using MyCompanyName.MyProjectName; using OpenIddict.Abstractions; using Volo.Abp.Account; -using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite; -using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; -using Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; +using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme; using Volo.Abp.Autofac.WebAssembly; using Volo.Abp.Mapperly; using Volo.Abp.FeatureManagement; using Volo.Abp.Identity; -using Volo.Abp.Identity.Blazor.WebAssembly; +using Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly; using Volo.Abp.Modularity; using Volo.Abp.OpenIddict; using Volo.Abp.PermissionManagement; using Volo.Abp.SettingManagement; -using Volo.Abp.SettingManagement.Blazor.WebAssembly; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly; using Volo.Abp.TenantManagement; -using Volo.Abp.TenantManagement.Blazor.WebAssembly; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly; using Volo.Abp.UI.Navigation; namespace MyCompanyName.MyProjectName; @@ -30,14 +26,14 @@ namespace MyCompanyName.MyProjectName; // ABP Framework packages typeof(AbpAutofacWebAssemblyModule), - typeof(AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeModule), + typeof(AbpAspNetCoreComponentsWebAssemblyMudBlazorBasicThemeModule), // Account module packages typeof(AbpAccountHttpApiClientModule), // Identity module packages typeof(AbpIdentityHttpApiClientModule), - typeof(AbpIdentityBlazorWebAssemblyModule), + typeof(AbpIdentityBlazorMudBlazorWebAssemblyModule), typeof(AbpOpenIddictDomainSharedModule), // Permission Management module packages @@ -45,14 +41,14 @@ namespace MyCompanyName.MyProjectName; // Tenant Management module packages typeof(AbpTenantManagementHttpApiClientModule), - typeof(AbpTenantManagementBlazorWebAssemblyModule), + typeof(AbpTenantManagementBlazorMudBlazorWebAssemblyModule), // Feature Management module packages typeof(AbpFeatureManagementHttpApiClientModule), // Setting Management module packages typeof(AbpSettingManagementHttpApiClientModule), - typeof(AbpSettingManagementBlazorWebAssemblyModule) + typeof(AbpSettingManagementBlazorMudBlazorWebAssemblyModule) )] public class MyProjectNameBlazorModule : AbpModule { @@ -65,7 +61,6 @@ public class MyProjectNameBlazorModule : AbpModule ConfigureAuthentication(builder); ConfigureHttpClient(context, environment); - ConfigureBlazorise(context); ConfigureRouter(context); ConfigureMenu(context); ConfigureHttpClientProxies(context); @@ -89,13 +84,6 @@ public class MyProjectNameBlazorModule : AbpModule }); } - private void ConfigureBlazorise(ServiceConfigurationContext context) - { - context.Services - .AddBootstrap5Providers() - .AddFontAwesomeIcons(); - } - private void ConfigureHttpClientProxies(ServiceConfigurationContext context) { context.Services.AddHttpClientProxies( @@ -119,12 +107,6 @@ public class MyProjectNameBlazorModule : AbpModule }); } - private static void ConfigureUI(WebAssemblyHostBuilder builder) - { - builder.RootComponents.Add("#ApplicationContainer"); - builder.RootComponents.Add("head::after"); - } - private static void ConfigureHttpClient(ServiceConfigurationContext context, IWebAssemblyHostEnvironment environment) { context.Services.AddTransient(sp => new HttpClient diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/Pages/Index.razor b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/Pages/Index.razor index c67303f497..fc3312004d 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/Pages/Index.razor +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/Pages/Index.razor @@ -1,281 +1,127 @@ -@page "/" +@page "/" @inherits MyProjectNameComponentBase -
-
-
-
-
-

Getting Started

-

Learn how to create and run - a - new web application using the application startup - template.

- Getting - Started -

Web Application Development Tutorial

-

Learn how to build an ABP based - web - application named Acme.BookStore.

- Explore Tutorial + + + + + @if (!CurrentUser.IsAuthenticated) + { + @L["Welcome"] + @L["LongWelcomeMessage"] + @L["Login"] + } -

Customize Lepton Theme

-

Learn how to customize LeptonX - Theme - as you wish.

- Customize Lepton -
- -
-
-
-
-
-
-
-
- -

You can check for - similar problems and solutions, or open a - new topic to discuss your specific issue.

- Visit Support - -
-
-
-
-
-
- -

You can find content - on .NET development, cross-platform, ASP.NET - application templates, ABP-related news, and - more.

- Visit Blog + Getting Started + Learn how to create and run a new web application using the application startup template. + Getting Started - -
-
-
-
-
-
-
-
- -

A unique community platform - for ABP Lovers!

-

Explore all ABP users' - experiences with the ABP Framework, discover - articles and videos on how to use ABP, and join - raffles for a chance to win surprise gifts!

- Join ABP - Community -
- -
-
-
+ Web Application Development Tutorial + Learn how to build an ABP based web application named Acme.BookStore. + Explore Tutorial + + + + + + + + Community + + A unique community platform for ABP Lovers! + Explore all ABP users' experiences with the ABP Framework, discover articles and videos on how to use ABP, and join raffles for a chance to win surprise gifts! + Join ABP Community -
-
-
-
-
More from ABP.IO
-
-
+ -
- - - - - - - - - - - - - - - - - - - - - - - -
-
-
Latest Release Logs
-
- - - -
-
-
Video Courses
-
- - - -
-
-
Samples
-
- - - -
-
-
Books
-
- - - -
-
-
FAQ
-
- - - -
-
-
-
-
-
-
-
-
-
-

THE OFFICIAL GUIDE

-

Mastering ABP Framework

-

Written by the creator of the ABP - Framework, this book will help you gain a complete - understanding of the framework - and modern web application development techniques.

- -
-
- -
-
+ + Blog + + Find content on .NET development, cross-platform, ASP.NET application templates, ABP-related news, and more. + Visit Blog + + + + -
-
-
-
-
-
-
Follow us on Social Media
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
- -
X.com
-
- - - -
- -
Discord
-
- - - -
- -
Stack Overflow
-
- - - -
- -
YouTube
-
- - - -
- -
Instagram
-
- - - -
-
-
-
-
-
\ No newline at end of file + + + + + More from ABP.IO + + + + + Latest Release Logs + + + Video Courses + + + Samples + + + Books + + + FAQ + + + + + + + + + Follow us on Social Media + + + + + + + X.com (Twitter) + + + Discord + + + Stack Overflow + + + + + + + YouTube + + + Instagram + + + GitHub + + + + + + + + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/Routes.razor b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/Routes.razor index 12cf8988f1..c2cefa6cff 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/Routes.razor +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/Routes.razor @@ -1,5 +1,5 @@ @using Volo.Abp.AspNetCore.Components.WebAssembly.WebApp -@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite +@using Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme.Themes.Basic diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/_Imports.razor b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/_Imports.razor index cf5e05c00a..50b30731d1 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/_Imports.razor +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Client/_Imports.razor @@ -8,7 +8,6 @@ @using Microsoft.JSInterop @using Volo.Abp.AspNetCore.Components.Web @using MyCompanyName.MyProjectName -@using Blazorise -@using Blazorise.DataGrid -@using Volo.Abp.BlazoriseUI -@using Volo.Abp.BlazoriseUI.Components +@using global::MudBlazor +@using Volo.Abp.MudBlazorUI +@using Volo.Abp.MudBlazorUI.Components diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/Components/App.razor b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/Components/App.razor index 9f55084e64..05dfe4359c 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/Components/App.razor +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/Components/App.razor @@ -10,7 +10,7 @@ - + @@ -26,10 +26,6 @@
-
-
-
-
diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.Mongo.csproj index 4cfca64cd8..123afd3d34 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.Mongo.csproj @@ -8,7 +8,7 @@ - + @@ -73,12 +73,12 @@ - - + + - + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyProjectNameHostModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyProjectNameHostModule.cs index 4985e565ba..607e26d596 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyProjectNameHostModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyProjectNameHostModule.cs @@ -11,7 +11,7 @@ using OpenIddict.Validation.AspNetCore; using Volo.Abp; using Volo.Abp.Account; using Volo.Abp.Account.Web; -using Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme.Bundling; +using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling; using Volo.Abp.AspNetCore.Components.WebAssembly.WebApp; using Volo.Abp.AspNetCore.MultiTenancy; using Volo.Abp.AspNetCore.Mvc; @@ -60,7 +60,7 @@ namespace MyCompanyName.MyProjectName; typeof(AbpAutofacModule), typeof(AbpMapperlyModule), typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule), - typeof(AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeBundlingModule), + typeof(AbpAspNetCoreComponentsWebAssemblyMudBlazorBasicThemeBundlingModule), typeof(AbpSwashbuckleModule), typeof(AbpAspNetCoreSerilogModule), diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/package.json b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/package.json index 066b3d8c0f..0fe746056e 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/package.json +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.3.0" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.4.0" } } diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Components/App.razor b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Components/App.razor index 9f55084e64..23ef58e599 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Components/App.razor +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/Components/App.razor @@ -10,7 +10,7 @@ - + @@ -26,10 +26,6 @@
-
-
-
-
diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.csproj index d2dcc81851..ded34186ad 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyCompanyName.MyProjectName.Blazor.WebAssembly.Server.csproj @@ -8,7 +8,7 @@ - + @@ -74,16 +74,16 @@ - - + + - + - + runtime; build; native; contentfiles; analyzers compile; contentFiles; build; buildMultitargeting; buildTransitive; analyzers; native diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyProjectNameHostModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyProjectNameHostModule.cs index c4dade54cc..0753f5581d 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyProjectNameHostModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyProjectNameHostModule.cs @@ -11,7 +11,7 @@ using OpenIddict.Validation.AspNetCore; using Volo.Abp; using Volo.Abp.Account; using Volo.Abp.Account.Web; -using Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme.Bundling; +using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling; using Volo.Abp.AspNetCore.Components.WebAssembly.WebApp; using Volo.Abp.AspNetCore.MultiTenancy; using Volo.Abp.AspNetCore.Mvc; @@ -63,7 +63,7 @@ namespace MyCompanyName.MyProjectName; typeof(AbpMapperlyModule), typeof(AbpEntityFrameworkCoreSqlServerModule), typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule), - typeof(AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeBundlingModule), + typeof(AbpAspNetCoreComponentsWebAssemblyMudBlazorBasicThemeBundlingModule), typeof(AbpSwashbuckleModule), typeof(AbpAspNetCoreSerilogModule), diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/package.json b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/package.json index 066b3d8c0f..0fe746056e 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/package.json +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.3.0" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.4.0" } } diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Shared/MyCompanyName.MyProjectName.Blazor.WebAssembly.Shared.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Shared/MyCompanyName.MyProjectName.Blazor.WebAssembly.Shared.csproj index f47e419fd4..e6e6844dfa 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Shared/MyCompanyName.MyProjectName.Blazor.WebAssembly.Shared.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Shared/MyCompanyName.MyProjectName.Blazor.WebAssembly.Shared.csproj @@ -29,7 +29,7 @@ - + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyCompanyName.MyProjectName.Host.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyCompanyName.MyProjectName.Host.Mongo.csproj index 481266b6ce..1c968973ec 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyCompanyName.MyProjectName.Host.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyCompanyName.MyProjectName.Host.Mongo.csproj @@ -69,11 +69,11 @@ - + - + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/package.json b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/package.json index 066b3d8c0f..0fe746056e 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/package.json +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.3.0" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.4.0" } } diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj index c8c8c9b534..0e8f220689 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyCompanyName.MyProjectName.Host.csproj @@ -70,15 +70,15 @@ - + - + - + runtime; build; native; contentfiles; analyzers compile; contentFiles; build; buildMultitargeting; buildTransitive; analyzers; native diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/package.json b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/package.json index 066b3d8c0f..0fe746056e 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/package.json +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.3.0" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.4.0" } } diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyCompanyName.MyProjectName.Mvc.Mongo.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyCompanyName.MyProjectName.Mvc.Mongo.csproj index 22ddbecc6c..e48c588324 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyCompanyName.MyProjectName.Mvc.Mongo.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyCompanyName.MyProjectName.Mvc.Mongo.csproj @@ -17,7 +17,7 @@ - + @@ -76,7 +76,7 @@ - + diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/package.json b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/package.json index 066b3d8c0f..0fe746056e 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/package.json +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.3.0" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.4.0" } } diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyCompanyName.MyProjectName.Mvc.csproj b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyCompanyName.MyProjectName.Mvc.csproj index 6eb8bd408e..700d497e7e 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyCompanyName.MyProjectName.Mvc.csproj +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyCompanyName.MyProjectName.Mvc.csproj @@ -17,7 +17,7 @@ - + @@ -77,11 +77,11 @@ - + - + runtime; build; native; contentfiles; analyzers compile; contentFiles; build; buildMultitargeting; buildTransitive; analyzers; native diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/package.json b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/package.json index 066b3d8c0f..0fe746056e 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/package.json +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.3.0" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.4.0" } } diff --git a/templates/app/angular/package.json b/templates/app/angular/package.json index 2d0f96e204..70d14326ac 100644 --- a/templates/app/angular/package.json +++ b/templates/app/angular/package.json @@ -12,15 +12,15 @@ }, "private": true, "dependencies": { - "@abp/ng.account": "~10.3.0", - "@abp/ng.components": "~10.3.0", - "@abp/ng.core": "~10.3.0", - "@abp/ng.identity": "~10.3.0", - "@abp/ng.oauth": "~10.3.0", - "@abp/ng.setting-management": "~10.3.0", - "@abp/ng.tenant-management": "~10.3.0", - "@abp/ng.theme.lepton-x": "~5.3.0", - "@abp/ng.theme.shared": "~10.3.0", + "@abp/ng.account": "~10.4.0", + "@abp/ng.components": "~10.4.0", + "@abp/ng.core": "~10.4.0", + "@abp/ng.identity": "~10.4.0", + "@abp/ng.oauth": "~10.4.0", + "@abp/ng.setting-management": "~10.4.0", + "@abp/ng.tenant-management": "~10.4.0", + "@abp/ng.theme.lepton-x": "~5.4.0", + "@abp/ng.theme.shared": "~10.4.0", "@angular/animations": "~21.2.0", "@angular/aria": "~21.2.0", "@angular/common": "~21.2.0", @@ -37,7 +37,7 @@ "zone.js": "~0.15.0" }, "devDependencies": { - "@abp/ng.schematics": "~10.3.0", + "@abp/ng.schematics": "~10.4.0", "@angular-eslint/builder": "~21.2.0", "@angular-eslint/eslint-plugin": "~21.2.0", "@angular-eslint/eslint-plugin-template": "~21.2.0", diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/MyCompanyName.MyProjectName.AuthServer.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/MyCompanyName.MyProjectName.AuthServer.csproj index 06c7bbfb73..f538b21b54 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/MyCompanyName.MyProjectName.AuthServer.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/MyCompanyName.MyProjectName.AuthServer.csproj @@ -41,7 +41,7 @@ - + @@ -51,7 +51,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/package.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/package.json index 5fc9b7e2f5..4f52fa5c73 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/package.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/package.json @@ -3,6 +3,6 @@ "name": "my-app-authserver", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.3.0" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.4.0" } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/Menus/MyProjectNameMenuContributor.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/Menus/MyProjectNameMenuContributor.cs index fdfdc05cf0..27a5a4f8a2 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/Menus/MyProjectNameMenuContributor.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/Menus/MyProjectNameMenuContributor.cs @@ -5,9 +5,9 @@ using MyCompanyName.MyProjectName.Localization; using MyCompanyName.MyProjectName.MultiTenancy; using Volo.Abp.Account.Localization; using Volo.Abp.Authorization.Permissions; -using Volo.Abp.Identity.Blazor; -using Volo.Abp.SettingManagement.Blazor.Menus; -using Volo.Abp.TenantManagement.Blazor.Navigation; +using Volo.Abp.Identity.Blazor.MudBlazor; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.Menus; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.Navigation; using Volo.Abp.UI.Navigation; namespace MyCompanyName.MyProjectName.Blazor.Client.Menus; diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyCompanyName.MyProjectName.Blazor.Client.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyCompanyName.MyProjectName.Blazor.Client.csproj index 0879feee0e..0c21fc98d5 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyCompanyName.MyProjectName.Blazor.Client.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyCompanyName.MyProjectName.Blazor.Client.csproj @@ -12,19 +12,17 @@ - - - - + + - + - + - + @@ -32,9 +30,9 @@ - - - + + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyProjectNameBlazorClientModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyProjectNameBlazorClientModule.cs index 13a9d2694b..96d1707d77 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyProjectNameBlazorClientModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/MyProjectNameBlazorClientModule.cs @@ -1,20 +1,18 @@ -using System; +using System; using System.Net.Http; -using Blazorise.Bootstrap5; -using Blazorise.Icons.FontAwesome; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using MyCompanyName.MyProjectName.Blazor.Client.Menus; using OpenIddict.Abstractions; -using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; -using Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; +using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme; using Volo.Abp.Autofac.WebAssembly; using Volo.Abp.Mapperly; -using Volo.Abp.Identity.Blazor.WebAssembly; +using Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly; using Volo.Abp.Modularity; -using Volo.Abp.SettingManagement.Blazor.WebAssembly; -using Volo.Abp.TenantManagement.Blazor.WebAssembly; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly; using Volo.Abp.UI.Navigation; namespace MyCompanyName.MyProjectName.Blazor.Client; @@ -22,10 +20,10 @@ namespace MyCompanyName.MyProjectName.Blazor.Client; [DependsOn( typeof(AbpAutofacWebAssemblyModule), typeof(MyProjectNameHttpApiClientModule), - typeof(AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeModule), - typeof(AbpIdentityBlazorWebAssemblyModule), - typeof(AbpTenantManagementBlazorWebAssemblyModule), - typeof(AbpSettingManagementBlazorWebAssemblyModule) + typeof(AbpAspNetCoreComponentsWebAssemblyMudBlazorBasicThemeModule), + typeof(AbpIdentityBlazorMudBlazorWebAssemblyModule), + typeof(AbpTenantManagementBlazorMudBlazorWebAssemblyModule), + typeof(AbpSettingManagementBlazorMudBlazorWebAssemblyModule) )] public class MyProjectNameBlazorClientModule : AbpModule { @@ -36,7 +34,6 @@ public class MyProjectNameBlazorClientModule : AbpModule ConfigureAuthentication(builder); ConfigureHttpClient(context, environment); - ConfigureBlazorise(context); ConfigureRouter(context); ConfigureMenu(context); @@ -59,12 +56,6 @@ public class MyProjectNameBlazorClientModule : AbpModule }); } - private void ConfigureBlazorise(ServiceConfigurationContext context) - { - context.Services - .AddBootstrap5Providers() - .AddFontAwesomeIcons(); - } private static void ConfigureAuthentication(WebAssemblyHostBuilder builder) { diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/Pages/Index.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/Pages/Index.razor index a932861897..fc3312004d 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/Pages/Index.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/Pages/Index.razor @@ -1,87 +1,127 @@ @page "/" -@using Volo.Abp.MultiTenancy @inherits MyProjectNameComponentBase -@inject AuthenticationStateProvider AuthenticationStateProvider -
-
-
-
-
-

Getting Started

-

Learn how to create and run - a new web application using the application startup template.

- Getting Started + + + + + @if (!CurrentUser.IsAuthenticated) + { + @L["Welcome"] + @L["LongWelcomeMessage"] + @L["Login"] + } -

Web Application Development Tutorial

-

Learn how to build an ABP based web application named Acme.BookStore.

- Explore Tutorial + Getting Started + Learn how to create and run a new web application using the application startup template. + Getting Started -

Customize Lepton Theme

-

Learn how to customize LeptonX Theme as you wish.

- Customize Lepton -
- -
-
-
+ Web Application Development Tutorial + Learn how to build an ABP based web application named Acme.BookStore. + Explore Tutorial + + + + + + + + Community + + A unique community platform for ABP Lovers! + Explore all ABP users' experiences with the ABP Framework, discover articles and videos on how to use ABP, and join raffles for a chance to win surprise gifts! + Join ABP Community -
-
-
-
-
- -

You can check for similar problems and solutions, or open a new topic to discuss your specific issue.

- Visit Support - -
-
-
-
-
-
- -

You can find content on .NET development, cross-platform, ASP.NET application templates, ABP-related news, and more.

- Visit Blog - -
-
-
-
-
+ -
-

Let's improve your application!

-

Here are some links to help you get started:

-
+ + Blog + + Find content on .NET development, cross-platform, ASP.NET application templates, ABP-related news, and more. + Visit Blog +
+
+
+ -
-
-
-
-
-
Learn the ABP Framework
-

Explore the comprehensive documentation to learn how to build a modern web application.

- See Documents -
-
-
-
-
Samples
-

See the example projects built with the ABP Framework.

- All samples -
-
-
-
-
ABP Community
-

Get involved with a vibrant community and become a contributor.

- Community - Contribute -
-
-
-
-
-
+ + + + + More from ABP.IO + + + + + Latest Release Logs + + + Video Courses + + + Samples + + + Books + + + FAQ + + + + + + + + + Follow us on Social Media + + + + + + + X.com (Twitter) + + + Discord + + + Stack Overflow + + + + + + + YouTube + + + Instagram + + + GitHub + + + + + + + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/Routes.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/Routes.razor index 455a0ffcb7..1961b26b07 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/Routes.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/Routes.razor @@ -1,4 +1,4 @@ -@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite +@using Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme.Themes.Basic @using Volo.Abp.AspNetCore.Components.WebAssembly.WebApp diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/_Imports.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/_Imports.razor index d83d7089ef..e15ae5ff69 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/_Imports.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Client/_Imports.razor @@ -1,4 +1,4 @@ -@using System.Net.Http +@using System.Net.Http @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Forms @@ -9,7 +9,6 @@ @using Microsoft.JSInterop @using Volo.Abp.AspNetCore.Components.Web @using MyCompanyName.MyProjectName.Blazor -@using Blazorise -@using Blazorise.DataGrid -@using Volo.Abp.BlazoriseUI -@using Volo.Abp.BlazoriseUI.Components +@using MudBlazor +@using Volo.Abp.MudBlazorUI +@using Volo.Abp.MudBlazorUI.Components diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Components/App.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Components/App.razor index 73ed17cb7d..fa004fffc2 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Components/App.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Components/App.razor @@ -1,8 +1,8 @@ -@using Volo.Abp.Localization +@using Volo.Abp.Localization @using System.Globalization @using Microsoft.Extensions.Hosting -@using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Bundling -@using Volo.Abp.AspNetCore.Components.Web.Theming.Bundling +@using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Bundling +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Bundling @inject IHostEnvironment Env @{ var rtl = CultureHelper.IsRtl ? "rtl" : string.Empty; @@ -17,7 +17,8 @@ MyCompanyName.MyProjectName.Blazor.Server - + + @@ -26,7 +27,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Components/Pages/Index.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Components/Pages/Index.razor index d0a8d0ac7b..552e6d89e4 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Components/Pages/Index.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Components/Pages/Index.razor @@ -1,284 +1,127 @@ -@page "/" -@using Volo.Abp.MultiTenancy +@page "/" @inherits MyProjectNameComponentBase -@inject AuthenticationStateProvider AuthenticationStateProvider -
-
-
-
-
-

Getting Started

-

Learn how to create and run - a - new web application using the application startup - template.

- Getting - Started + + + + + @if (!CurrentUser.IsAuthenticated) + { + @L["Welcome"] + @L["LongWelcomeMessage"] + @L["Login"] + } -

Web Application Development Tutorial

-

Learn how to build an ABP based - web - application named Acme.BookStore.

- Explore Tutorial + Getting Started + Learn how to create and run a new web application using the application startup template. + Getting Started -

Customize Lepton Theme

-

Learn how to customize LeptonX - Theme - as you wish.

- Customize Lepton -
- -
-
-
-
-
-
-
-
- -

You can check for - similar problems and solutions, or open a - new topic to discuss your specific issue.

- Visit Support - -
-
-
-
-
-
- -

You can find content - on .NET development, cross-platform, ASP.NET - application templates, ABP-related news, and - more.

- Visit Blog + Web Application Development Tutorial + Learn how to build an ABP based web application named Acme.BookStore. + Explore Tutorial + + + + + + + + Community + + A unique community platform for ABP Lovers! + Explore all ABP users' experiences with the ABP Framework, discover articles and videos on how to use ABP, and join raffles for a chance to win surprise gifts! + Join ABP Community - -
-
-
-
-
-
-
-
- -

A unique community platform - for ABP Lovers!

-

Explore all ABP users' - experiences with the ABP Framework, discover - articles and videos on how to use ABP, and join - raffles for a chance to win surprise gifts!

- Join ABP - Community -
- -
-
-
+ -
-
-
-
-
More from ABP.IO
-
-
+ + Blog + + Find content on .NET development, cross-platform, ASP.NET application templates, ABP-related news, and more. + Visit Blog + + + + -
- - - - - - - - - - - - - - - - - - - - - - - -
-
-
Latest Release Logs
-
- - - -
-
-
Video Courses
-
- - - -
-
-
Samples
-
- - - -
-
-
Books
-
- - - -
-
-
FAQ
-
- - - -
-
-
-
-
-
-
-
-
-
-

THE OFFICIAL GUIDE

-

Mastering ABP Framework

-

Written by the creator of the ABP - Framework, this book will help you gain a complete - understanding of the framework - and modern web application development techniques.

- -
-
- -
-
- -
-
-
-
-
-
-
Follow us on Social Media
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
- -
X.com
-
- - - -
- -
Discord
-
- - - -
- -
Stack Overflow
-
- - - -
- -
YouTube
-
- - - -
- -
Instagram
-
- - - -
-
-
-
-
-
\ No newline at end of file + + + + + More from ABP.IO + + + + + Latest Release Logs + + + Video Courses + + + Samples + + + Books + + + FAQ + + + + + + + + + Follow us on Social Media + + + + + + + X.com (Twitter) + + + Discord + + + Stack Overflow + + + + + + + YouTube + + + Instagram + + + GitHub + + + + + + + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Components/Routes.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Components/Routes.razor index d2b6a9be7b..70a90d221c 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Components/Routes.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Components/Routes.razor @@ -1,5 +1,5 @@ -@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite -@using Volo.Abp.AspNetCore.Components.Web.Theming.Routing +@using Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme.Themes.Basic +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing @using Microsoft.Extensions.Options @inject IOptions RouterOptions diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Menus/MyProjectNameMenuContributor.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Menus/MyProjectNameMenuContributor.cs index 48d545868f..a8df05eb56 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Menus/MyProjectNameMenuContributor.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/Menus/MyProjectNameMenuContributor.cs @@ -1,16 +1,14 @@ using System; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using MyCompanyName.MyProjectName.Localization; using MyCompanyName.MyProjectName.MultiTenancy; using Volo.Abp.Account.Localization; -using Volo.Abp.Identity.Blazor; using Volo.Abp.Authorization.Permissions; -using Volo.Abp.SettingManagement.Blazor.Menus; -using Volo.Abp.TenantManagement.Blazor.Navigation; +using Volo.Abp.Identity.Blazor.MudBlazor; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.Menus; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.Navigation; using Volo.Abp.UI.Navigation; -using Volo.Abp.Users; namespace MyCompanyName.MyProjectName.Blazor.Server.Tiered.Menus; diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj index f8cc56776e..355fc507fb 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyCompanyName.MyProjectName.Blazor.Server.Tiered.csproj @@ -14,11 +14,9 @@ - - - + @@ -27,11 +25,11 @@ - - + + - - + +
@@ -44,9 +42,9 @@ - - - + + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs index 325ee6805b..e98ea8659c 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs @@ -1,7 +1,5 @@ -using System; +using System; using System.IO; -using Blazorise.Bootstrap5; -using Blazorise.Icons.FontAwesome; using Medallion.Threading; using Medallion.Threading.Redis; using Microsoft.AspNetCore.Authentication.Cookies; @@ -23,11 +21,11 @@ using StackExchange.Redis; using Volo.Abp; using Volo.Abp.AspNetCore.Authentication.OpenIdConnect; using Volo.Abp.AspNetCore.Components.Web; -using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme; -using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Bundling; +using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme; +using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite; using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite.Bundling; -using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; using Volo.Abp.AspNetCore.Mvc.Client; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI; @@ -43,14 +41,14 @@ using Volo.Abp.Caching; using Volo.Abp.Caching.StackExchangeRedis; using Volo.Abp.DistributedLocking; using Volo.Abp.Http.Client.IdentityModel.Web; -using Volo.Abp.Identity.Blazor.Server; +using Volo.Abp.Identity.Blazor.MudBlazor.Server; using Volo.Abp.Localization; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.Security.Claims; -using Volo.Abp.SettingManagement.Blazor.Server; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.Server; using Volo.Abp.Swashbuckle; -using Volo.Abp.TenantManagement.Blazor.Server; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.Server; using Volo.Abp.UI; using Volo.Abp.UI.Navigation; using Volo.Abp.UI.Navigation.Urls; @@ -65,14 +63,14 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Tiered; typeof(AbpAspNetCoreMvcClientModule), typeof(AbpAspNetCoreAuthenticationOpenIdConnectModule), typeof(AbpHttpClientIdentityModelWebModule), - typeof(AbpAspNetCoreComponentsServerLeptonXLiteThemeModule), + typeof(AbpAspNetCoreComponentsServerMudBlazorBasicThemeModule), typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule), typeof(AbpAutofacModule), typeof(AbpSwashbuckleModule), typeof(AbpAspNetCoreSerilogModule), - typeof(AbpIdentityBlazorServerModule), - typeof(AbpTenantManagementBlazorServerModule), - typeof(AbpSettingManagementBlazorServerModule) + typeof(AbpIdentityBlazorMudBlazorServerModule), + typeof(AbpTenantManagementBlazorMudBlazorServerModule), + typeof(AbpSettingManagementBlazorMudBlazorServerModule) )] public class MyProjectNameBlazorModule : AbpModule { @@ -109,7 +107,6 @@ public class MyProjectNameBlazorModule : AbpModule ConfigureMultiTenancy(); ConfigureAuthentication(context, configuration); ConfigureVirtualFileSystem(hostingEnvironment); - ConfigureBlazorise(context); ConfigureRouter(context); ConfigureMenu(configuration); ConfigureDataProtection(context, configuration, hostingEnvironment); @@ -150,7 +147,7 @@ public class MyProjectNameBlazorModule : AbpModule //BLAZOR UI options.StyleBundles.Configure( - BlazorLeptonXLiteThemeBundles.Styles.Global, + BlazorMudBlazorBasicThemeBundles.Styles.Global, bundle => { bundle.AddFiles("/blazor-global-styles.css"); @@ -272,12 +269,6 @@ public class MyProjectNameBlazorModule : AbpModule } } - private void ConfigureBlazorise(ServiceConfigurationContext context) - { - context.Services - .AddBootstrap5Providers() - .AddFontAwesomeIcons(); - } private void ConfigureMenu(IConfiguration configuration) { diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/_Imports.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/_Imports.razor index 3bed94b983..7a1153d81d 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/_Imports.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/_Imports.razor @@ -1,4 +1,4 @@ -@using System.Net.Http +@using System.Net.Http @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Forms @@ -8,7 +8,6 @@ @using static Microsoft.AspNetCore.Components.Web.RenderMode @using Microsoft.JSInterop @using MyCompanyName.MyProjectName.Blazor.Server.Tiered -@using Blazorise -@using Blazorise.DataGrid -@using Volo.Abp.BlazoriseUI -@using Volo.Abp.BlazoriseUI.Components +@using MudBlazor +@using Volo.Abp.MudBlazorUI +@using Volo.Abp.MudBlazorUI.Components diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/package.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/package.json index 0479161a12..964755e648 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/package.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/package.json @@ -3,7 +3,7 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.3.0", - "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.3.0" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.4.0", + "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.4.0" } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Components/App.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Components/App.razor index 73ed17cb7d..fa004fffc2 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Components/App.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Components/App.razor @@ -1,8 +1,8 @@ -@using Volo.Abp.Localization +@using Volo.Abp.Localization @using System.Globalization @using Microsoft.Extensions.Hosting -@using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Bundling -@using Volo.Abp.AspNetCore.Components.Web.Theming.Bundling +@using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Bundling +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Bundling @inject IHostEnvironment Env @{ var rtl = CultureHelper.IsRtl ? "rtl" : string.Empty; @@ -17,7 +17,8 @@ MyCompanyName.MyProjectName.Blazor.Server - + + @@ -26,7 +27,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Components/Pages/Index.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Components/Pages/Index.razor index 008b98b46d..552e6d89e4 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Components/Pages/Index.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Components/Pages/Index.razor @@ -1,100 +1,127 @@ @page "/" -@using Volo.Abp.MultiTenancy @inherits MyProjectNameComponentBase -@inject AuthenticationStateProvider AuthenticationStateProvider -
-
-
-
-
-

Getting Started

-

Learn how to create and run a new web application using the application startup template.

- Getting Started + + + + + @if (!CurrentUser.IsAuthenticated) + { + @L["Welcome"] + @L["LongWelcomeMessage"] + @L["Login"] + } -

Web Application Development Tutorial

-

Learn how to build an ABP based web application named Acme.BookStore.

- Explore Tutorial + Getting Started + Learn how to create and run a new web application using the application startup template. + Getting Started -

Customize Lepton Theme

-

Learn how to customize LeptonX Theme as you wish.

- Customize Lepton -
- -
-
-
+ Web Application Development Tutorial + Learn how to build an ABP based web application named Acme.BookStore. + Explore Tutorial + + + + + + + + Community + + A unique community platform for ABP Lovers! + Explore all ABP users' experiences with the ABP Framework, discover articles and videos on how to use ABP, and join raffles for a chance to win surprise gifts! + Join ABP Community -
-
-
- -

You can check for similar problems and solutions, or open a new topic to discuss your specific issue.

- Visit Support - -
-
-
+ -
-
-
- -

A unique community platform for ABP Lovers!

-

Explore all ABP users' experiences with the ABP Framework, discover articles and videos on how to use ABP, and join raffles for a chance to win surprise gifts!

- Join ABP Community -
- -
-
-
+ + Blog + + Find content on .NET development, cross-platform, ASP.NET application templates, ABP-related news, and more. + Visit Blog +
+
+
+ -
-

Let's improve your application!

-

Here are some links to help you get started:

-
- -
-
-
-
-
-
Learn the ABP Framework
-

Explore the comprehensive documentation to learn how to build a modern web application.

- See Documents -
-
-
-
-
Samples
-

See the example projects built with the ABP Framework.

- All samples -
-
-
-
-
ABP Community
-

Get involved with a vibrant community and become a contributor.

- Community - Contribute -
-
-
-
-
-
-
ABP Blog
-

Take a look at our recently published articles.

- See Blog -
-
-
-
-
Github
-

Do you love the ABP Framework? Please give a star to support it!

- See Open Source Framework -
-
-
-
-
+ + + + + More from ABP.IO + + + + + Latest Release Logs + + + Video Courses + + + Samples + + + Books + + + FAQ + + + + + + + + + Follow us on Social Media + + + + + + + X.com (Twitter) + + + Discord + + + Stack Overflow + + + + + + + YouTube + + + Instagram + + + GitHub + + + + + + + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Components/Routes.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Components/Routes.razor index d2b6a9be7b..70a90d221c 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Components/Routes.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Components/Routes.razor @@ -1,5 +1,5 @@ -@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite -@using Volo.Abp.AspNetCore.Components.Web.Theming.Routing +@using Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme.Themes.Basic +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing @using Microsoft.Extensions.Options @inject IOptions RouterOptions diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Menus/MyProjectNameMenuContributor.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Menus/MyProjectNameMenuContributor.cs index eeabef69b1..266e0bddda 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Menus/MyProjectNameMenuContributor.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/Menus/MyProjectNameMenuContributor.cs @@ -1,9 +1,9 @@ using System.Threading.Tasks; using MyCompanyName.MyProjectName.Localization; using MyCompanyName.MyProjectName.MultiTenancy; -using Volo.Abp.Identity.Blazor; -using Volo.Abp.SettingManagement.Blazor.Menus; -using Volo.Abp.TenantManagement.Blazor.Navigation; +using Volo.Abp.Identity.Blazor.MudBlazor; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.Menus; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.Navigation; using Volo.Abp.UI.Navigation; namespace MyCompanyName.MyProjectName.Blazor.Server.Menus; diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj index be90ab1a5f..1b9444006c 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj @@ -14,9 +14,7 @@ - - - + @@ -26,11 +24,11 @@ - - + + - - + +
@@ -41,9 +39,9 @@ - - - + + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs index 658d6d80ca..68946c4493 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs @@ -1,7 +1,5 @@ using System; using System.IO; -using Blazorise.Bootstrap5; -using Blazorise.Icons.FontAwesome; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.AspNetCore.Hosting; @@ -19,11 +17,11 @@ using OpenIddict.Validation.AspNetCore; using Volo.Abp; using Volo.Abp.Account.Web; using Volo.Abp.AspNetCore.Components.Web; -using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme; -using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Bundling; +using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme; +using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite; using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite.Bundling; -using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI; @@ -33,12 +31,12 @@ using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.Autofac; using Volo.Abp.Mapperly; -using Volo.Abp.Identity.Blazor.Server; +using Volo.Abp.Identity.Blazor.MudBlazor.Server; using Volo.Abp.Modularity; using Volo.Abp.Security.Claims; -using Volo.Abp.SettingManagement.Blazor.Server; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.Server; using Volo.Abp.Swashbuckle; -using Volo.Abp.TenantManagement.Blazor.Server; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.Server; using Volo.Abp.OpenIddict; using Volo.Abp.UI; using Volo.Abp.UI.Navigation; @@ -55,11 +53,11 @@ namespace MyCompanyName.MyProjectName.Blazor.Server; typeof(AbpSwashbuckleModule), typeof(AbpAspNetCoreSerilogModule), typeof(AbpAccountWebOpenIddictModule), - typeof(AbpAspNetCoreComponentsServerLeptonXLiteThemeModule), + typeof(AbpAspNetCoreComponentsServerMudBlazorBasicThemeModule), typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule), - typeof(AbpIdentityBlazorServerModule), - typeof(AbpTenantManagementBlazorServerModule), - typeof(AbpSettingManagementBlazorServerModule) + typeof(AbpIdentityBlazorMudBlazorServerModule), + typeof(AbpTenantManagementBlazorMudBlazorServerModule), + typeof(AbpSettingManagementBlazorMudBlazorServerModule) )] public class MyProjectNameBlazorModule : AbpModule { @@ -124,7 +122,6 @@ public class MyProjectNameBlazorModule : AbpModule ConfigureVirtualFileSystem(hostingEnvironment); ConfigureSwaggerServices(context.Services); ConfigureAutoApiControllers(); - ConfigureBlazorise(context); ConfigureRouter(context); ConfigureMenu(context); @@ -164,7 +161,7 @@ public class MyProjectNameBlazorModule : AbpModule //BLAZOR UI options.StyleBundles.Configure( - BlazorLeptonXLiteThemeBundles.Styles.Global, + BlazorMudBlazorBasicThemeBundles.Styles.Global, bundle => { bundle.AddFiles("/blazor-global-styles.css"); @@ -214,12 +211,6 @@ public class MyProjectNameBlazorModule : AbpModule ); } - private void ConfigureBlazorise(ServiceConfigurationContext context) - { - context.Services - .AddBootstrap5Providers() - .AddFontAwesomeIcons(); - } private void ConfigureMenu(ServiceConfigurationContext context) { diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/_Imports.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/_Imports.razor index 82279dd28a..c8d3d02e79 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/_Imports.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/_Imports.razor @@ -1,4 +1,4 @@ -@using System.Net.Http +@using System.Net.Http @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Forms @@ -8,7 +8,6 @@ @using static Microsoft.AspNetCore.Components.Web.RenderMode @using Microsoft.JSInterop @using MyCompanyName.MyProjectName.Blazor.Server -@using Blazorise -@using Blazorise.DataGrid -@using Volo.Abp.BlazoriseUI -@using Volo.Abp.BlazoriseUI.Components +@using MudBlazor +@using Volo.Abp.MudBlazorUI +@using Volo.Abp.MudBlazorUI.Components diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/package.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/package.json index 0479161a12..964755e648 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/package.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/package.json @@ -3,7 +3,7 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.3.0", - "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.3.0" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.4.0", + "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.4.0" } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/Menus/MyProjectNameMenuContributor.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/Menus/MyProjectNameMenuContributor.cs index 93067729fa..0b5af29792 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/Menus/MyProjectNameMenuContributor.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/Menus/MyProjectNameMenuContributor.cs @@ -5,9 +5,9 @@ using MyCompanyName.MyProjectName.Localization; using MyCompanyName.MyProjectName.MultiTenancy; using Volo.Abp.Account.Localization; using Volo.Abp.Authorization.Permissions; -using Volo.Abp.Identity.Blazor; -using Volo.Abp.SettingManagement.Blazor.Menus; -using Volo.Abp.TenantManagement.Blazor.Navigation; +using Volo.Abp.Identity.Blazor.MudBlazor; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.Menus; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.Navigation; using Volo.Abp.UI.Navigation; namespace MyCompanyName.MyProjectName.Blazor.WebApp.Client.Menus; diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Client.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Client.csproj index d8e91d2319..654506d73c 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Client.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Client.csproj @@ -13,19 +13,17 @@ - - - - + + - + - + - + @@ -33,9 +31,9 @@ - - - + + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyProjectNameBlazorClientModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyProjectNameBlazorClientModule.cs index d3e28b8e23..b696e312cb 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyProjectNameBlazorClientModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/MyProjectNameBlazorClientModule.cs @@ -1,21 +1,19 @@ -using System; +using System; using System.Net.Http; -using Blazorise.Bootstrap5; -using Blazorise.Icons.FontAwesome; using Microsoft.AspNetCore.Components.WebAssembly.Authentication; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using MyCompanyName.MyProjectName.Blazor.WebApp.Client.Menus; using Volo.Abp.AspNetCore.Components.Web; -using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; -using Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; +using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme; using Volo.Abp.Autofac.WebAssembly; using Volo.Abp.Mapperly; -using Volo.Abp.Identity.Blazor.WebAssembly; +using Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly; using Volo.Abp.Modularity; -using Volo.Abp.SettingManagement.Blazor.WebAssembly; -using Volo.Abp.TenantManagement.Blazor.WebAssembly; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly; using Volo.Abp.UI.Navigation; namespace MyCompanyName.MyProjectName.Blazor.WebApp.Client; @@ -23,10 +21,10 @@ namespace MyCompanyName.MyProjectName.Blazor.WebApp.Client; [DependsOn( typeof(AbpAutofacWebAssemblyModule), typeof(MyProjectNameHttpApiClientModule), - typeof(AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeModule), - typeof(AbpIdentityBlazorWebAssemblyModule), - typeof(AbpTenantManagementBlazorWebAssemblyModule), - typeof(AbpSettingManagementBlazorWebAssemblyModule) + typeof(AbpAspNetCoreComponentsWebAssemblyMudBlazorBasicThemeModule), + typeof(AbpIdentityBlazorMudBlazorWebAssemblyModule), + typeof(AbpTenantManagementBlazorMudBlazorWebAssemblyModule), + typeof(AbpSettingManagementBlazorMudBlazorWebAssemblyModule) )] public class MyProjectNameBlazorClientModule : AbpModule { @@ -45,7 +43,6 @@ public class MyProjectNameBlazorClientModule : AbpModule ConfigureAuthentication(builder); ConfigureHttpClient(context, environment); - ConfigureBlazorise(context); ConfigureRouter(context); ConfigureMenu(context); @@ -69,12 +66,6 @@ public class MyProjectNameBlazorClientModule : AbpModule }); } - private void ConfigureBlazorise(ServiceConfigurationContext context) - { - context.Services - .AddBootstrap5Providers() - .AddFontAwesomeIcons(); - } private static void ConfigureAuthentication(WebAssemblyHostBuilder builder) { diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/Pages/Index.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/Pages/Index.razor index b98f41c5c4..552e6d89e4 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/Pages/Index.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/Pages/Index.razor @@ -1,153 +1,127 @@ @page "/" @inherits MyProjectNameComponentBase -@inject AuthenticationStateProvider AuthenticationStateProvider -
-
-
-
-
-

Getting Started

-

Learn how to create and run a new web application using the application startup template.

- Getting Started + + + + + @if (!CurrentUser.IsAuthenticated) + { + @L["Welcome"] + @L["LongWelcomeMessage"] + @L["Login"] + } -

Web Application Development Tutorial

-

Learn how to build an ABP based web application named Acme.BookStore.

- Explore Tutorial + Getting Started + Learn how to create and run a new web application using the application startup template. + Getting Started -

Customize Lepton Theme

-

Learn how to customize LeptonX Theme as you wish.

- Customize Lepton -
- -
-
-
+ Web Application Development Tutorial + Learn how to build an ABP based web application named Acme.BookStore. + Explore Tutorial + + + + + + + + Community + + A unique community platform for ABP Lovers! + Explore all ABP users' experiences with the ABP Framework, discover articles and videos on how to use ABP, and join raffles for a chance to win surprise gifts! + Join ABP Community -
-

Let's improve your application!

-

Here are some links to help you get started:

-
+ -
-
-
-
-
-
Learn the ABP Framework
-

Explore the comprehensive documentation to learn how to build a modern web application.

- See Documents -
-
-
-
-
Samples
-

See the example projects built with the ABP Framework.

- All samples -
-
-
-
-
ABP Community
-

Get involved with a vibrant community and become a contributor.

- Community - Contribute -
-
-
-
-
-
-
ABP Blog
-

Take a look at our recently published articles.

- See Blog -
-
-
-
-
Github
-

Do you love the ABP Framework? Please give a star to support it!

- GitHub -
-
-
-
-
+ + Blog + + Find content on .NET development, cross-platform, ASP.NET application templates, ABP-related news, and more. + Visit Blog +
+
+
+ -
-
-
-
-
- -

You can check for similar problems and solutions, or open a new topic to discuss your specific issue.

- Visit Support - -
-
-
-
-
-
- -

You can find content on .NET development, cross-platform, ASP.NET application templates, ABP-related news, and more.

- Visit Blog - -
-
-
-
-
-
- -
-
-

- ABP is a platform based on the open source ABP framework. It provides pre-built application modules, - rapid application development tooling, professional UI themes, premium support, and more. -

- -
-
-
-
- Startup Templates - Details -
-
-
-
-
-
- Application Modules - Details -
-
-
-
-
-
- Developer
Tools - Details -
-
-
-
-
-
- UI Themes - Details -
-
-
-
-
-
- Support - Details -
-
-
-
-
-
+ + + + + More from ABP.IO + + + + + Latest Release Logs + + + Video Courses + + + Samples + + + Books + + + FAQ + + + + + + + + + Follow us on Social Media + + + + + + + X.com (Twitter) + + + Discord + + + Stack Overflow + + + + + + + YouTube + + + Instagram + + + GitHub + + + + + + + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/Routes.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/Routes.razor index d2b6a9be7b..70a90d221c 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/Routes.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/Routes.razor @@ -1,5 +1,5 @@ -@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite -@using Volo.Abp.AspNetCore.Components.Web.Theming.Routing +@using Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme.Themes.Basic +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing @using Microsoft.Extensions.Options @inject IOptions RouterOptions diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/_Imports.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/_Imports.razor index 66c9c872fc..a31e1e7424 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/_Imports.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Client/_Imports.razor @@ -1,4 +1,4 @@ -@using System.Net.Http +@using System.Net.Http @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Forms @@ -8,7 +8,6 @@ @using static Microsoft.AspNetCore.Components.Web.RenderMode @using Microsoft.JSInterop @using Volo.Abp.AspNetCore.Components.Web -@using Blazorise -@using Blazorise.DataGrid -@using Volo.Abp.BlazoriseUI -@using Volo.Abp.BlazoriseUI.Components +@using MudBlazor +@using Volo.Abp.MudBlazorUI +@using Volo.Abp.MudBlazorUI.Components diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/Menus/MyProjectNameMenuContributor.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/Menus/MyProjectNameMenuContributor.cs index 75dd7244ef..238a9f8f9f 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/Menus/MyProjectNameMenuContributor.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/Menus/MyProjectNameMenuContributor.cs @@ -5,9 +5,9 @@ using MyCompanyName.MyProjectName.Localization; using MyCompanyName.MyProjectName.MultiTenancy; using Volo.Abp.Account.Localization; using Volo.Abp.Authorization.Permissions; -using Volo.Abp.Identity.Blazor; -using Volo.Abp.SettingManagement.Blazor.Menus; -using Volo.Abp.TenantManagement.Blazor.Navigation; +using Volo.Abp.Identity.Blazor.MudBlazor; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.Menus; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.Navigation; using Volo.Abp.UI.Navigation; namespace MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client.Menus; @@ -66,22 +66,26 @@ public class MyProjectNameMenuContributor : IMenuContributor private Task ConfigureUserMenuAsync(MenuConfigurationContext context) { - if (!OperatingSystem.IsBrowser()) - { - return Task.CompletedTask; - } - - var authServerUrl = _configuration["AuthServer:Authority"] ?? ""; var accountStringLocalizer = context.GetLocalizer(); - context.Menu.AddItem(new ApplicationMenuItem( - "Account.Manage", - accountStringLocalizer["MyAccount"], - $"{authServerUrl.EnsureEndsWith('/')}Account/Manage", - icon: "fa fa-cog", - order: 1000, - target: "_blank") - .RequireAuthenticated()); + if (OperatingSystem.IsBrowser()) + { + var authServerUrl = _configuration["AuthServer:Authority"] ?? ""; + + context.Menu.AddItem(new ApplicationMenuItem( + "Account.Manage", + accountStringLocalizer["MyAccount"], + $"{authServerUrl.EnsureEndsWith('/')}Account/Manage", + icon: "fa fa-cog", + order: 1000, + target: "_blank") + .RequireAuthenticated()); + } + else + { + context.Menu.AddItem(new ApplicationMenuItem("Account.Manage", accountStringLocalizer["MyAccount"], "/Account/Manage", icon: "fa fa-cog", order: 1000).RequireAuthenticated()); + context.Menu.AddItem(new ApplicationMenuItem("Account.Logout", accountStringLocalizer["Logout"], url: "/Account/Logout", icon: "fa fa-power-off", order: int.MaxValue - 1000).RequireAuthenticated()); + } return Task.CompletedTask; } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client.csproj index ceef0368bb..3623ef9af4 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client.csproj @@ -13,19 +13,17 @@ - - - - + + - + - + - + @@ -33,9 +31,9 @@ - - - + + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyProjectNameBlazorClientModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyProjectNameBlazorClientModule.cs index 7f1f464aac..26b8b8af18 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyProjectNameBlazorClientModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/MyProjectNameBlazorClientModule.cs @@ -1,21 +1,19 @@ -using System; +using System; using System.Net.Http; -using Blazorise.Bootstrap5; -using Blazorise.Icons.FontAwesome; using Microsoft.AspNetCore.Components.WebAssembly.Authentication; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client.Menus; using Volo.Abp.AspNetCore.Components.Web; -using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; -using Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; +using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme; using Volo.Abp.Autofac.WebAssembly; using Volo.Abp.Mapperly; -using Volo.Abp.Identity.Blazor.WebAssembly; +using Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly; using Volo.Abp.Modularity; -using Volo.Abp.SettingManagement.Blazor.WebAssembly; -using Volo.Abp.TenantManagement.Blazor.WebAssembly; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly; using Volo.Abp.UI.Navigation; namespace MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client; @@ -23,10 +21,10 @@ namespace MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client; [DependsOn( typeof(AbpAutofacWebAssemblyModule), typeof(MyProjectNameHttpApiClientModule), - typeof(AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeModule), - typeof(AbpIdentityBlazorWebAssemblyModule), - typeof(AbpTenantManagementBlazorWebAssemblyModule), - typeof(AbpSettingManagementBlazorWebAssemblyModule) + typeof(AbpAspNetCoreComponentsWebAssemblyMudBlazorBasicThemeModule), + typeof(AbpIdentityBlazorMudBlazorWebAssemblyModule), + typeof(AbpTenantManagementBlazorMudBlazorWebAssemblyModule), + typeof(AbpSettingManagementBlazorMudBlazorWebAssemblyModule) )] public class MyProjectNameBlazorClientModule : AbpModule { @@ -45,7 +43,6 @@ public class MyProjectNameBlazorClientModule : AbpModule ConfigureAuthentication(builder); ConfigureHttpClient(context, environment); - ConfigureBlazorise(context); ConfigureRouter(context); ConfigureMenu(context); @@ -68,12 +65,6 @@ public class MyProjectNameBlazorClientModule : AbpModule }); } - private void ConfigureBlazorise(ServiceConfigurationContext context) - { - context.Services - .AddBootstrap5Providers() - .AddFontAwesomeIcons(); - } private static void ConfigureAuthentication(WebAssemblyHostBuilder builder) { diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/Pages/Index.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/Pages/Index.razor index d0a8d0ac7b..552e6d89e4 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/Pages/Index.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/Pages/Index.razor @@ -1,284 +1,127 @@ -@page "/" -@using Volo.Abp.MultiTenancy +@page "/" @inherits MyProjectNameComponentBase -@inject AuthenticationStateProvider AuthenticationStateProvider -
-
-
-
-
-

Getting Started

-

Learn how to create and run - a - new web application using the application startup - template.

- Getting - Started + + + + + @if (!CurrentUser.IsAuthenticated) + { + @L["Welcome"] + @L["LongWelcomeMessage"] + @L["Login"] + } -

Web Application Development Tutorial

-

Learn how to build an ABP based - web - application named Acme.BookStore.

- Explore Tutorial + Getting Started + Learn how to create and run a new web application using the application startup template. + Getting Started -

Customize Lepton Theme

-

Learn how to customize LeptonX - Theme - as you wish.

- Customize Lepton -
- -
-
-
-
-
-
-
-
- -

You can check for - similar problems and solutions, or open a - new topic to discuss your specific issue.

- Visit Support - -
-
-
-
-
-
- -

You can find content - on .NET development, cross-platform, ASP.NET - application templates, ABP-related news, and - more.

- Visit Blog + Web Application Development Tutorial + Learn how to build an ABP based web application named Acme.BookStore. + Explore Tutorial + + + + + + + + Community + + A unique community platform for ABP Lovers! + Explore all ABP users' experiences with the ABP Framework, discover articles and videos on how to use ABP, and join raffles for a chance to win surprise gifts! + Join ABP Community - -
-
-
-
-
-
-
-
- -

A unique community platform - for ABP Lovers!

-

Explore all ABP users' - experiences with the ABP Framework, discover - articles and videos on how to use ABP, and join - raffles for a chance to win surprise gifts!

- Join ABP - Community -
- -
-
-
+ -
-
-
-
-
More from ABP.IO
-
-
+ + Blog + + Find content on .NET development, cross-platform, ASP.NET application templates, ABP-related news, and more. + Visit Blog + + + + -
- - - - - - - - - - - - - - - - - - - - - - - -
-
-
Latest Release Logs
-
- - - -
-
-
Video Courses
-
- - - -
-
-
Samples
-
- - - -
-
-
Books
-
- - - -
-
-
FAQ
-
- - - -
-
-
-
-
-
-
-
-
-
-

THE OFFICIAL GUIDE

-

Mastering ABP Framework

-

Written by the creator of the ABP - Framework, this book will help you gain a complete - understanding of the framework - and modern web application development techniques.

- -
-
- -
-
- -
-
-
-
-
-
-
Follow us on Social Media
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - -
- -
X.com
-
- - - -
- -
Discord
-
- - - -
- -
Stack Overflow
-
- - - -
- -
YouTube
-
- - - -
- -
Instagram
-
- - - -
-
-
-
-
-
\ No newline at end of file + + + + + More from ABP.IO + + + + + Latest Release Logs + + + Video Courses + + + Samples + + + Books + + + FAQ + + + + + + + + + Follow us on Social Media + + + + + + + X.com (Twitter) + + + Discord + + + Stack Overflow + + + + + + + YouTube + + + Instagram + + + GitHub + + + + + + + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/Routes.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/Routes.razor index d2b6a9be7b..70a90d221c 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/Routes.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/Routes.razor @@ -1,5 +1,5 @@ -@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite -@using Volo.Abp.AspNetCore.Components.Web.Theming.Routing +@using Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme.Themes.Basic +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing @using Microsoft.Extensions.Options @inject IOptions RouterOptions diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/_Imports.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/_Imports.razor index d83d7089ef..e15ae5ff69 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/_Imports.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client/_Imports.razor @@ -1,4 +1,4 @@ -@using System.Net.Http +@using System.Net.Http @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Forms @@ -9,7 +9,6 @@ @using Microsoft.JSInterop @using Volo.Abp.AspNetCore.Components.Web @using MyCompanyName.MyProjectName.Blazor -@using Blazorise -@using Blazorise.DataGrid -@using Volo.Abp.BlazoriseUI -@using Volo.Abp.BlazoriseUI.Components +@using MudBlazor +@using Volo.Abp.MudBlazorUI +@using Volo.Abp.MudBlazorUI.Components diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/Components/App.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/Components/App.razor index 06cdb41c0f..73b58df41b 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/Components/App.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/Components/App.razor @@ -1,10 +1,10 @@ -@using Volo.Abp.Localization +@using Volo.Abp.Localization @using System.Globalization @using Microsoft.AspNetCore.Authentication @using Microsoft.AspNetCore.Http @using Microsoft.Extensions.Hosting -@using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Bundling -@using Volo.Abp.AspNetCore.Components.Web.Theming.Bundling +@using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Bundling +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Bundling @using Volo.Abp.AspNetCore.Components.WebAssembly.WebApp @inject IHostEnvironment Env @{ @@ -20,7 +20,8 @@ MyCompanyName.MyProjectName.Blazor.Server - + + @@ -29,7 +30,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.csproj index 60ef9ab125..f2d15c4652 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.csproj @@ -15,12 +15,10 @@ - - - + - + @@ -29,11 +27,12 @@ - - + + - - + + +
@@ -47,9 +46,9 @@ - - - + + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyProjectNameBlazorModule.cs index 2a31f2a763..736b7ef7b3 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyProjectNameBlazorModule.cs @@ -1,7 +1,5 @@ -using System; +using System; using System.IO; -using Blazorise.Bootstrap5; -using Blazorise.Icons.FontAwesome; using Medallion.Threading; using Medallion.Threading.Redis; using Microsoft.AspNetCore.Authentication.Cookies; @@ -25,10 +23,10 @@ using StackExchange.Redis; using Volo.Abp; using Volo.Abp.AspNetCore.Authentication.OpenIdConnect; using Volo.Abp.AspNetCore.Components.Web; -using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme; -using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Bundling; -using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; -using Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme.Bundling; +using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme; +using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Bundling; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; +using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling; using Volo.Abp.AspNetCore.Mvc.Client; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI; @@ -46,13 +44,13 @@ using Volo.Abp.Caching; using Volo.Abp.Caching.StackExchangeRedis; using Volo.Abp.DistributedLocking; using Volo.Abp.Http.Client.IdentityModel.Web; -using Volo.Abp.Identity.Blazor.Server; +using Volo.Abp.Identity.Blazor.MudBlazor.Server; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.Security.Claims; -using Volo.Abp.SettingManagement.Blazor.Server; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.Server; using Volo.Abp.Swashbuckle; -using Volo.Abp.TenantManagement.Blazor.Server; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.Server; using Volo.Abp.UI; using Volo.Abp.UI.Navigation; using Volo.Abp.UI.Navigation.Urls; @@ -67,15 +65,15 @@ namespace MyCompanyName.MyProjectName.Blazor.WebApp.Tiered; typeof(AbpAspNetCoreMvcClientModule), typeof(AbpAspNetCoreAuthenticationOpenIdConnectModule), typeof(AbpHttpClientIdentityModelWebModule), - typeof(AbpAspNetCoreComponentsServerLeptonXLiteThemeModule), - typeof(AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeBundlingModule), + typeof(AbpAspNetCoreComponentsServerMudBlazorBasicThemeModule), + typeof(AbpAspNetCoreComponentsWebAssemblyMudBlazorBasicThemeBundlingModule), typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule), typeof(AbpAutofacModule), typeof(AbpSwashbuckleModule), typeof(AbpAspNetCoreSerilogModule), - typeof(AbpIdentityBlazorServerModule), - typeof(AbpTenantManagementBlazorServerModule), - typeof(AbpSettingManagementBlazorServerModule) + typeof(AbpIdentityBlazorMudBlazorServerModule), + typeof(AbpTenantManagementBlazorMudBlazorServerModule), + typeof(AbpSettingManagementBlazorMudBlazorServerModule) )] public class MyProjectNameBlazorModule : AbpModule { @@ -113,7 +111,6 @@ public class MyProjectNameBlazorModule : AbpModule ConfigureMultiTenancy(); ConfigureAuthentication(context, configuration); ConfigureVirtualFileSystem(hostingEnvironment); - ConfigureBlazorise(context); ConfigureRouter(context); ConfigureMenu(configuration); ConfigureDataProtection(context, configuration, hostingEnvironment); @@ -155,7 +152,7 @@ public class MyProjectNameBlazorModule : AbpModule //BLAZOR UI options.StyleBundles.Configure( - BlazorLeptonXLiteThemeBundles.Styles.Global, + BlazorMudBlazorBasicThemeBundles.Styles.Global, bundle => { bundle.AddFiles("/blazor-global-styles.css"); @@ -277,12 +274,6 @@ public class MyProjectNameBlazorModule : AbpModule } } - private void ConfigureBlazorise(ServiceConfigurationContext context) - { - context.Services - .AddBootstrap5Providers() - .AddFontAwesomeIcons(); - } private void ConfigureMenu(IConfiguration configuration) { diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/_Imports.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/_Imports.razor index b272603927..c88befe37d 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/_Imports.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/_Imports.razor @@ -1,4 +1,4 @@ -@using System.Net.Http +@using System.Net.Http @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Forms @@ -7,8 +7,7 @@ @using Microsoft.AspNetCore.Components.Web.Virtualization @using static Microsoft.AspNetCore.Components.Web.RenderMode @using Microsoft.JSInterop -@using Blazorise -@using Blazorise.DataGrid -@using Volo.Abp.BlazoriseUI -@using Volo.Abp.BlazoriseUI.Components +@using MudBlazor +@using Volo.Abp.MudBlazorUI +@using Volo.Abp.MudBlazorUI.Components @using MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/package.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/package.json index 0479161a12..964755e648 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/package.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/package.json @@ -3,7 +3,7 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.3.0", - "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.3.0" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.4.0", + "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.4.0" } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/Components/App.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/Components/App.razor index 78b81092ca..d0e68d9c2d 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/Components/App.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/Components/App.razor @@ -1,8 +1,8 @@ -@using Volo.Abp.Localization +@using Volo.Abp.Localization @using System.Globalization @using Microsoft.Extensions.Hosting -@using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Bundling -@using Volo.Abp.AspNetCore.Components.Web.Theming.Bundling +@using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Bundling +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Bundling @inject IHostEnvironment Env @{ var rtl = CultureHelper.IsRtl ? "rtl" : string.Empty; @@ -17,7 +17,8 @@ MyCompanyName.MyProjectName.Blazor.Server - + + @@ -26,7 +27,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyCompanyName.MyProjectName.Blazor.WebApp.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyCompanyName.MyProjectName.Blazor.WebApp.csproj index 0b476c8adb..1ce1e196e9 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyCompanyName.MyProjectName.Blazor.WebApp.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyCompanyName.MyProjectName.Blazor.WebApp.csproj @@ -15,9 +15,7 @@ - - - + @@ -27,12 +25,12 @@ - - + + - - - + + + @@ -44,9 +42,9 @@ - - - + + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyProjectNameBlazorModule.cs index 3a7b435244..f89ba68756 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyProjectNameBlazorModule.cs @@ -1,7 +1,5 @@ using System; using System.IO; -using Blazorise.Bootstrap5; -using Blazorise.Icons.FontAwesome; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.AspNetCore.Hosting; @@ -20,10 +18,10 @@ using OpenIddict.Validation.AspNetCore; using Volo.Abp; using Volo.Abp.Account.Web; using Volo.Abp.AspNetCore.Components.Web; -using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme; -using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Bundling; -using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; -using Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme.Bundling; +using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme; +using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Bundling; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; +using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI; @@ -35,13 +33,13 @@ using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite.Bundling; using Volo.Abp.AspNetCore.Serilog; using Volo.Abp.Autofac; using Volo.Abp.Mapperly; -using Volo.Abp.Identity.Blazor.Server; +using Volo.Abp.Identity.Blazor.MudBlazor.Server; using Volo.Abp.Modularity; using Volo.Abp.OpenIddict; using Volo.Abp.Security.Claims; -using Volo.Abp.SettingManagement.Blazor.Server; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.Server; using Volo.Abp.Swashbuckle; -using Volo.Abp.TenantManagement.Blazor.Server; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.Server; using Volo.Abp.UI; using Volo.Abp.UI.Navigation; using Volo.Abp.UI.Navigation.Urls; @@ -57,12 +55,12 @@ namespace MyCompanyName.MyProjectName.Blazor.WebApp; typeof(AbpSwashbuckleModule), typeof(AbpAspNetCoreSerilogModule), typeof(AbpAccountWebOpenIddictModule), - typeof(AbpAspNetCoreComponentsServerLeptonXLiteThemeModule), - typeof(AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeBundlingModule), + typeof(AbpAspNetCoreComponentsServerMudBlazorBasicThemeModule), + typeof(AbpAspNetCoreComponentsWebAssemblyMudBlazorBasicThemeBundlingModule), typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule), - typeof(AbpIdentityBlazorServerModule), - typeof(AbpTenantManagementBlazorServerModule), - typeof(AbpSettingManagementBlazorServerModule) + typeof(AbpIdentityBlazorMudBlazorServerModule), + typeof(AbpTenantManagementBlazorMudBlazorServerModule), + typeof(AbpSettingManagementBlazorMudBlazorServerModule) )] public class MyProjectNameBlazorModule : AbpModule { @@ -128,7 +126,6 @@ public class MyProjectNameBlazorModule : AbpModule ConfigureVirtualFileSystem(hostingEnvironment); ConfigureSwaggerServices(context.Services); ConfigureAutoApiControllers(); - ConfigureBlazorise(context); ConfigureRouter(context); ConfigureMenu(context); @@ -171,7 +168,7 @@ public class MyProjectNameBlazorModule : AbpModule //BLAZOR UI options.StyleBundles.Configure( - BlazorLeptonXLiteThemeBundles.Styles.Global, + BlazorMudBlazorBasicThemeBundles.Styles.Global, bundle => { bundle.AddFiles("/blazor-global-styles.css"); @@ -221,12 +218,6 @@ public class MyProjectNameBlazorModule : AbpModule ); } - private void ConfigureBlazorise(ServiceConfigurationContext context) - { - context.Services - .AddBootstrap5Providers() - .AddFontAwesomeIcons(); - } private void ConfigureMenu(ServiceConfigurationContext context) { diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/_Imports.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/_Imports.razor index 34452bb9c3..868150cfa0 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/_Imports.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/_Imports.razor @@ -1,4 +1,4 @@ -@using System.Net.Http +@using System.Net.Http @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Forms @@ -8,7 +8,6 @@ @using static Microsoft.AspNetCore.Components.Web.RenderMode @using Microsoft.JSInterop @using MyCompanyName.MyProjectName.Blazor.WebApp.Client -@using Blazorise -@using Blazorise.DataGrid -@using Volo.Abp.BlazoriseUI -@using Volo.Abp.BlazoriseUI.Components +@using MudBlazor +@using Volo.Abp.MudBlazorUI +@using Volo.Abp.MudBlazorUI.Components diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/package.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/package.json index 0479161a12..964755e648 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/package.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/package.json @@ -3,7 +3,7 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.3.0", - "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.3.0" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.4.0", + "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.4.0" } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/App.razor b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/App.razor index ea01bcd33b..d2193e8333 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/App.razor +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/App.razor @@ -7,6 +7,7 @@ MyCompanyName.MyProjectName.Blazor + @@ -20,15 +21,11 @@ - +
-
-
-
-
diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj index b42902d66c..edd7df7c33 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj @@ -13,10 +13,10 @@
- + - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs index dee666c107..feded4eff7 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs @@ -4,7 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using MyCompanyName.MyProjectName.Blazor.Client; using Volo.Abp; -using Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme.Bundling; +using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme.Bundling; using Volo.Abp.AspNetCore.Components.WebAssembly.WebApp; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.Autofac; @@ -15,7 +15,7 @@ namespace MyCompanyName.MyProjectName.Blazor; [DependsOn( typeof(AbpAutofacModule), typeof(AbpAspNetCoreMvcUiBundlingModule), - typeof(AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeBundlingModule) + typeof(AbpAspNetCoreComponentsWebAssemblyMudBlazorBasicThemeBundlingModule) )] public class MyProjectNameBlazorModule : AbpModule { diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/MyCompanyName.MyProjectName.DbMigrator.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/MyCompanyName.MyProjectName.DbMigrator.csproj index 8e30d5120e..0bfa28550b 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/MyCompanyName.MyProjectName.DbMigrator.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/MyCompanyName.MyProjectName.DbMigrator.csproj @@ -22,7 +22,7 @@ - +
diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj index 5bff54da57..879a0288ff 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj @@ -26,7 +26,7 @@ - +
diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/MyCompanyName.MyProjectName.EntityFrameworkCore.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/MyCompanyName.MyProjectName.EntityFrameworkCore.csproj index 3d77bbf91a..1dd5916158 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/MyCompanyName.MyProjectName.EntityFrameworkCore.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/MyCompanyName.MyProjectName.EntityFrameworkCore.csproj @@ -22,7 +22,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj index 558f1085e8..b1c3a233ac 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj index bff7c6c558..b516d5b878 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyCompanyName.MyProjectName.HttpApi.HostWithIds.csproj @@ -26,7 +26,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/package.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/package.json index 066b3d8c0f..0fe746056e 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/package.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.3.0" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.4.0" } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj index fd67ed8d0b..ca2d593362 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj @@ -19,7 +19,7 @@ - + @@ -28,7 +28,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/package.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/package.json index 066b3d8c0f..0fe746056e 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/package.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.3.0" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.4.0" } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj index cf8dd3d500..ab8a7afddd 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj @@ -49,7 +49,7 @@ - + diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/package.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/package.json index 066b3d8c0f..0fe746056e 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/package.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.3.0" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.4.0" } } diff --git a/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj b/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj index 8d93386afe..ccf0be58ed 100644 --- a/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj +++ b/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj @@ -22,8 +22,8 @@ - - + +
diff --git a/templates/console/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj b/templates/console/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj index 491b99fe17..9091e83b7b 100644 --- a/templates/console/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj +++ b/templates/console/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj @@ -13,7 +13,7 @@ - + diff --git a/templates/maui/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj b/templates/maui/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj index 6cbeb43b83..1793171fde 100644 --- a/templates/maui/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj +++ b/templates/maui/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj @@ -35,7 +35,7 @@ - + diff --git a/templates/module/angular/package.json b/templates/module/angular/package.json index 2b71f1a57d..a0be416c22 100644 --- a/templates/module/angular/package.json +++ b/templates/module/angular/package.json @@ -13,15 +13,15 @@ }, "private": true, "dependencies": { - "@abp/ng.account": "~10.3.0", - "@abp/ng.components": "~10.3.0", - "@abp/ng.core": "~10.3.0", - "@abp/ng.identity": "~10.3.0", - "@abp/ng.oauth": "~10.3.0", - "@abp/ng.setting-management": "~10.3.0", - "@abp/ng.tenant-management": "~10.3.0", - "@abp/ng.theme.basic": "~10.3.0", - "@abp/ng.theme.shared": "~10.3.0", + "@abp/ng.account": "~10.4.0", + "@abp/ng.components": "~10.4.0", + "@abp/ng.core": "~10.4.0", + "@abp/ng.identity": "~10.4.0", + "@abp/ng.oauth": "~10.4.0", + "@abp/ng.setting-management": "~10.4.0", + "@abp/ng.tenant-management": "~10.4.0", + "@abp/ng.theme.basic": "~10.4.0", + "@abp/ng.theme.shared": "~10.4.0", "@angular/animations": "~21.2.0", "@angular/aria": "~21.2.0", "@angular/common": "~21.2.0", @@ -37,7 +37,7 @@ "zone.js": "~0.15.0" }, "devDependencies": { - "@abp/ng.schematics": "~10.3.0", + "@abp/ng.schematics": "~10.4.0", "@angular-eslint/builder": "~21.2.0", "@angular-eslint/eslint-plugin": "~21.2.0", "@angular-eslint/eslint-plugin-template": "~21.2.0", diff --git a/templates/module/angular/projects/my-project-name/package.json b/templates/module/angular/projects/my-project-name/package.json index 7b649d403b..e87456cc64 100644 --- a/templates/module/angular/projects/my-project-name/package.json +++ b/templates/module/angular/projects/my-project-name/package.json @@ -4,8 +4,8 @@ "peerDependencies": { "@angular/common": "~21.2.0", "@angular/core": "~21.2.0", - "@abp/ng.core": "~10.3.0", - "@abp/ng.theme.shared": "~10.3.0" + "@abp/ng.core": "~10.4.0", + "@abp/ng.theme.shared": "~10.4.0" }, "dependencies": { "tslib": "^2.1.0" diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/MyCompanyName.MyProjectName.AuthServer.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/MyCompanyName.MyProjectName.AuthServer.csproj index 6978bafb8b..3ca83faaf4 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/MyCompanyName.MyProjectName.AuthServer.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/MyCompanyName.MyProjectName.AuthServer.csproj @@ -13,8 +13,8 @@ - - + + all runtime; build; native; contentfiles; analyzers diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/package.json b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/package.json index 1b82136b68..d15c5dfb60 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/package.json +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/package.json @@ -3,6 +3,6 @@ "name": "my-app-authserver", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.4.0" } } diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyCompanyName.MyProjectName.Blazor.Host.Client.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyCompanyName.MyProjectName.Blazor.Host.Client.csproj index 851f327132..cbe69b3e2c 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyCompanyName.MyProjectName.Blazor.Host.Client.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyCompanyName.MyProjectName.Blazor.Host.Client.csproj @@ -10,22 +10,20 @@ - - - - + + - + - - - + + + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyProjectNameBlazorHostClientModule.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyProjectNameBlazorHostClientModule.cs index f8f1805ea1..0f7f3a58b8 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyProjectNameBlazorHostClientModule.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/MyProjectNameBlazorHostClientModule.cs @@ -1,32 +1,30 @@ using System; using System.Net.Http; -using Blazorise.Bootstrap5; -using Blazorise.Icons.FontAwesome; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using MyCompanyName.MyProjectName.Blazor.WebAssembly; using Volo.Abp.Account; -using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; -using Volo.Abp.AspNetCore.Components.WebAssembly.BasicTheme; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; +using Volo.Abp.AspNetCore.Components.WebAssembly.MudBlazorBasicTheme; using Volo.Abp.Autofac.WebAssembly; using Volo.Abp.Mapperly; -using Volo.Abp.Identity.Blazor.WebAssembly; +using Volo.Abp.Identity.Blazor.MudBlazor.WebAssembly; using Volo.Abp.Modularity; -using Volo.Abp.SettingManagement.Blazor.WebAssembly; -using Volo.Abp.TenantManagement.Blazor.WebAssembly; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.WebAssembly; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.WebAssembly; using Volo.Abp.UI.Navigation; namespace MyCompanyName.MyProjectName.Blazor.Host.Client; [DependsOn( typeof(AbpAutofacWebAssemblyModule), - typeof(AbpAspNetCoreComponentsWebAssemblyBasicThemeModule), + typeof(AbpAspNetCoreComponentsWebAssemblyMudBlazorBasicThemeModule), typeof(AbpAccountApplicationContractsModule), - typeof(AbpIdentityBlazorWebAssemblyModule), - typeof(AbpTenantManagementBlazorWebAssemblyModule), - typeof(AbpSettingManagementBlazorWebAssemblyModule), + typeof(AbpIdentityBlazorMudBlazorWebAssemblyModule), + typeof(AbpTenantManagementBlazorMudBlazorWebAssemblyModule), + typeof(AbpSettingManagementBlazorMudBlazorWebAssemblyModule), typeof(MyProjectNameBlazorWebAssemblyModule) )] public class MyProjectNameBlazorHostClientModule : AbpModule @@ -38,7 +36,6 @@ public class MyProjectNameBlazorHostClientModule : AbpModule ConfigureAuthentication(builder); ConfigureHttpClient(context, environment); - ConfigureBlazorise(context); ConfigureRouter(context); ConfigureMenu(context); @@ -61,13 +58,6 @@ public class MyProjectNameBlazorHostClientModule : AbpModule }); } - private void ConfigureBlazorise(ServiceConfigurationContext context) - { - context.Services - .AddBootstrap5Providers() - .AddFontAwesomeIcons(); - } - private static void ConfigureAuthentication(WebAssemblyHostBuilder builder) { builder.Services.AddOidcAuthentication(options => diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/Routes.razor b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/Routes.razor index d7cc3acec7..61577721a5 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/Routes.razor +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/Routes.razor @@ -1,5 +1,5 @@ @using Volo.Abp.AspNetCore.Components.WebAssembly.WebApp -@using Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic +@using Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme.Themes.Basic diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/_Imports.razor b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/_Imports.razor index b1ade9c628..4c76904aee 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/_Imports.razor +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host.Client/_Imports.razor @@ -7,5 +7,6 @@ @using Microsoft.JSInterop @using Volo.Abp.AspNetCore.Components.WebAssembly @using MyCompanyName.MyProjectName.Blazor -@using Blazorise -@using Blazorise.DataGrid +@using MudBlazor +@using Volo.Abp.MudBlazorUI +@using Volo.Abp.MudBlazorUI.Components diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host/App.razor b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host/App.razor index d964f8098a..980c1f10f9 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host/App.razor +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host/App.razor @@ -20,10 +20,6 @@
-
-
-
-
diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host/MyCompanyName.MyProjectName.Blazor.Host.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host/MyCompanyName.MyProjectName.Blazor.Host.csproj index 2b54b7e2b9..3503cd12c6 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host/MyCompanyName.MyProjectName.Blazor.Host.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Host/MyCompanyName.MyProjectName.Blazor.Host.csproj @@ -13,10 +13,10 @@
- + - + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Components/App.razor b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Components/App.razor index 1a129ae90c..0339a6507d 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Components/App.razor +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Components/App.razor @@ -1,8 +1,8 @@ @using Volo.Abp.Localization @using System.Globalization @using Microsoft.Extensions.Hosting -@using Volo.Abp.AspNetCore.Components.Server.BasicTheme.Bundling -@using Volo.Abp.AspNetCore.Components.Web.Theming.Bundling +@using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Bundling +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Bundling @inject IHostEnvironment Env @{ var rtl = CultureHelper.IsRtl ? "rtl" : string.Empty; @@ -17,7 +17,8 @@ MyCompanyName.MyProjectName.Blazor.Server - + + @@ -26,7 +27,7 @@ - + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Components/Routes.razor b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Components/Routes.razor index b2251911fa..edf07d0af5 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Components/Routes.razor +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Components/Routes.razor @@ -1,6 +1,6 @@ -@using Volo.Abp.AspNetCore.Components.Web.Theming.Routing +@using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing @using Microsoft.Extensions.Options -@using Volo.Abp.AspNetCore.Components.Web.BasicTheme.Themes.Basic +@using Volo.Abp.AspNetCore.Components.Web.MudBlazorBasicTheme.Themes.Basic @inject IOptions RouterOptions diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Menus/MyProjectNameMenuContributor.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Menus/MyProjectNameMenuContributor.cs index 50b2e293d3..8ddab69d9e 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Menus/MyProjectNameMenuContributor.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/Menus/MyProjectNameMenuContributor.cs @@ -1,8 +1,8 @@ using System.Threading.Tasks; using MyCompanyName.MyProjectName.MultiTenancy; -using Volo.Abp.Identity.Blazor; -using Volo.Abp.SettingManagement.Blazor.Menus; -using Volo.Abp.TenantManagement.Blazor.Navigation; +using Volo.Abp.Identity.Blazor.MudBlazor; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.Menus; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.Navigation; using Volo.Abp.UI.Navigation; namespace MyCompanyName.MyProjectName.Blazor.Server.Host.Menus; diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyCompanyName.MyProjectName.Blazor.Server.Host.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyCompanyName.MyProjectName.Blazor.Server.Host.csproj index efcf43c5fa..34b63a7da3 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyCompanyName.MyProjectName.Blazor.Server.Host.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyCompanyName.MyProjectName.Blazor.Server.Host.csproj @@ -13,11 +13,9 @@ - - - + @@ -27,20 +25,20 @@ - + - + - + - + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyProjectNameBlazorHostModule.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyProjectNameBlazorHostModule.cs index 452eb58b56..17ab4f1ee0 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyProjectNameBlazorHostModule.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyProjectNameBlazorHostModule.cs @@ -1,8 +1,6 @@ using System; using System.IO; using System.Threading.Tasks; -using Blazorise.Bootstrap5; -using Blazorise.Icons.FontAwesome; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection; @@ -20,9 +18,9 @@ using Volo.Abp; using Volo.Abp.Account; using Volo.Abp.Account.Web; using Volo.Abp.AspNetCore.Components.Web; -using Volo.Abp.AspNetCore.Components.Server.BasicTheme; -using Volo.Abp.AspNetCore.Components.Server.BasicTheme.Bundling; -using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; +using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme; +using Volo.Abp.AspNetCore.Components.Server.MudBlazorBasicTheme.Bundling; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic; @@ -36,7 +34,7 @@ using Volo.Abp.EntityFrameworkCore; using Volo.Abp.FeatureManagement; using Volo.Abp.FeatureManagement.EntityFrameworkCore; using Volo.Abp.Identity; -using Volo.Abp.Identity.Blazor.Server; +using Volo.Abp.Identity.Blazor.MudBlazor.Server; using Volo.Abp.Identity.EntityFrameworkCore; using Volo.Abp.Localization; using Volo.Abp.Modularity; @@ -45,11 +43,11 @@ using Volo.Abp.PermissionManagement; using Volo.Abp.PermissionManagement.EntityFrameworkCore; using Volo.Abp.PermissionManagement.Identity; using Volo.Abp.SettingManagement; -using Volo.Abp.SettingManagement.Blazor.Server; +using Volo.Abp.SettingManagement.Blazor.MudBlazor.Server; using Volo.Abp.SettingManagement.EntityFrameworkCore; using Volo.Abp.Swashbuckle; using Volo.Abp.TenantManagement; -using Volo.Abp.TenantManagement.Blazor.Server; +using Volo.Abp.TenantManagement.Blazor.MudBlazor.Server; using Volo.Abp.TenantManagement.EntityFrameworkCore; using Volo.Abp.UI.Navigation; using Volo.Abp.UI.Navigation.Urls; @@ -68,20 +66,20 @@ namespace MyCompanyName.MyProjectName.Blazor.Server.Host; typeof(AbpAccountWebOpenIddictModule), typeof(AbpAccountApplicationModule), typeof(AbpAccountHttpApiModule), - typeof(AbpAspNetCoreComponentsServerBasicThemeModule), + typeof(AbpAspNetCoreComponentsServerMudBlazorBasicThemeModule), typeof(AbpIdentityApplicationModule), typeof(AbpIdentityEntityFrameworkCoreModule), typeof(AbpAuditLoggingEntityFrameworkCoreModule), - typeof(AbpIdentityBlazorServerModule), + typeof(AbpIdentityBlazorMudBlazorServerModule), typeof(AbpFeatureManagementApplicationModule), typeof(AbpFeatureManagementEntityFrameworkCoreModule), - typeof(AbpTenantManagementBlazorServerModule), + typeof(AbpTenantManagementBlazorMudBlazorServerModule), typeof(AbpTenantManagementApplicationModule), typeof(AbpTenantManagementEntityFrameworkCoreModule), typeof(AbpPermissionManagementEntityFrameworkCoreModule), typeof(AbpPermissionManagementDomainIdentityModule), typeof(AbpPermissionManagementApplicationModule), - typeof(AbpSettingManagementBlazorServerModule), + typeof(AbpSettingManagementBlazorMudBlazorServerModule), typeof(AbpSettingManagementApplicationModule), typeof(AbpSettingManagementEntityFrameworkCoreModule), typeof(MyProjectNameBlazorServerModule) @@ -152,7 +150,7 @@ public class MyProjectNameBlazorHostModule : AbpModule //BLAZOR UI options.StyleBundles.Configure( - BlazorBasicThemeBundles.Styles.Global, + BlazorMudBlazorBasicThemeBundles.Styles.Global, bundle => { bundle.AddFiles("/blazor-global-styles.css"); @@ -210,10 +208,6 @@ public class MyProjectNameBlazorHostModule : AbpModule options.IsEnabled = MultiTenancyConsts.IsEnabled; }); - context.Services - .AddBootstrap5Providers() - .AddFontAwesomeIcons(); - Configure(options => { options.MenuContributors.Add(new MyProjectNameMenuContributor()); diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/_Imports.razor b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/_Imports.razor index 82279dd28a..26389b17ac 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/_Imports.razor +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/_Imports.razor @@ -8,7 +8,6 @@ @using static Microsoft.AspNetCore.Components.Web.RenderMode @using Microsoft.JSInterop @using MyCompanyName.MyProjectName.Blazor.Server -@using Blazorise -@using Blazorise.DataGrid -@using Volo.Abp.BlazoriseUI -@using Volo.Abp.BlazoriseUI.Components +@using MudBlazor +@using Volo.Abp.MudBlazorUI +@using Volo.Abp.MudBlazorUI.Components diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/package.json b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/package.json index 9dd0d6884d..9a6e08f751 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/package.json +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/package.json @@ -3,7 +3,7 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.3.0", - "@abp/aspnetcore.components.server.basictheme": "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.4.0", + "@abp/aspnetcore.components.server.basictheme": "~10.4.0" } } diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj index 6413362b0b..41842c45fe 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyCompanyName.MyProjectName.HttpApi.Host.csproj @@ -14,9 +14,9 @@ - - - + + + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj index 5511bf8218..16b981a6cc 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyCompanyName.MyProjectName.Web.Host.csproj @@ -13,7 +13,7 @@ - + diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/package.json b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/package.json index cdee4f88a8..2d75677571 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/package.json +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.4.0" } } diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/MyCompanyName.MyProjectName.Web.Unified.csproj b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/MyCompanyName.MyProjectName.Web.Unified.csproj index 8c51a41819..33e4b0d85e 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/MyCompanyName.MyProjectName.Web.Unified.csproj +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/MyCompanyName.MyProjectName.Web.Unified.csproj @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/package.json b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/package.json index cdee4f88a8..2d75677571 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/package.json +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.3.0" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.4.0" } } diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj index debf160188..de882113dd 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyCompanyName.MyProjectName.Blazor.Server.csproj @@ -8,7 +8,7 @@ - + diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorServerModule.cs b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorServerModule.cs index 42d94ec462..cb9703695d 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorServerModule.cs +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorServerModule.cs @@ -1,10 +1,10 @@ -using Volo.Abp.AspNetCore.Components.Server.Theming; +using Volo.Abp.AspNetCore.Components.Server.Theming.MudBlazor; using Volo.Abp.Modularity; namespace MyCompanyName.MyProjectName.Blazor.Server; [DependsOn( - typeof(AbpAspNetCoreComponentsServerThemingModule), + typeof(AbpAspNetCoreComponentsServerThemingMudBlazorModule), typeof(MyProjectNameBlazorModule) )] public class MyProjectNameBlazorServerModule : AbpModule diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebAssembly/MyCompanyName.MyProjectName.Blazor.WebAssembly.csproj b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebAssembly/MyCompanyName.MyProjectName.Blazor.WebAssembly.csproj index 0eb1577f2d..f764d2736e 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebAssembly/MyCompanyName.MyProjectName.Blazor.WebAssembly.csproj +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebAssembly/MyCompanyName.MyProjectName.Blazor.WebAssembly.csproj @@ -8,7 +8,7 @@ - + diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebAssembly/MyProjectNameBlazorWebAssemblyModule.cs b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebAssembly/MyProjectNameBlazorWebAssemblyModule.cs index e705cbabc3..703ecbb221 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebAssembly/MyProjectNameBlazorWebAssemblyModule.cs +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebAssembly/MyProjectNameBlazorWebAssemblyModule.cs @@ -1,4 +1,4 @@ -using Volo.Abp.AspNetCore.Components.WebAssembly.Theming; +using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.MudBlazor; using Volo.Abp.Modularity; namespace MyCompanyName.MyProjectName.Blazor.WebAssembly; @@ -6,7 +6,7 @@ namespace MyCompanyName.MyProjectName.Blazor.WebAssembly; [DependsOn( typeof(MyProjectNameBlazorModule), typeof(MyProjectNameHttpApiClientModule), - typeof(AbpAspNetCoreComponentsWebAssemblyThemingModule) + typeof(AbpAspNetCoreComponentsWebAssemblyThemingMudBlazorModule) )] public class MyProjectNameBlazorWebAssemblyModule : AbpModule { diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj index 682ca7eaa3..6ec1b992d6 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyCompanyName.MyProjectName.Blazor.csproj @@ -9,7 +9,7 @@ - + diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs index 1dcb64b5c5..7fa03428f9 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/MyProjectNameBlazorModule.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using MyCompanyName.MyProjectName.Blazor.Menus; -using Volo.Abp.AspNetCore.Components.Web.Theming; -using Volo.Abp.AspNetCore.Components.Web.Theming.Routing; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor; +using Volo.Abp.AspNetCore.Components.Web.Theming.MudBlazor.Routing; using Volo.Abp.Mapperly; using Volo.Abp.Modularity; using Volo.Abp.UI.Navigation; @@ -10,7 +10,7 @@ namespace MyCompanyName.MyProjectName.Blazor; [DependsOn( typeof(MyProjectNameApplicationContractsModule), - typeof(AbpAspNetCoreComponentsWebThemingModule), + typeof(AbpAspNetCoreComponentsWebThemingMudBlazorModule), typeof(AbpMapperlyModule) )] public class MyProjectNameBlazorModule : AbpModule diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/_Imports.razor b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/_Imports.razor index 43b30f8498..690be9c8cf 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/_Imports.razor +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Blazor/_Imports.razor @@ -1,5 +1,4 @@ @using Microsoft.AspNetCore.Components.Web @using Volo.Abp.AspNetCore.Components.Web -@using Volo.Abp.BlazoriseUI -@using Blazorise -@using Blazorise.DataGrid \ No newline at end of file +@using Volo.Abp.MudBlazorUI +@using global::MudBlazor \ No newline at end of file diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj index 245a02ab0b..a284171d92 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Domain.Shared/MyCompanyName.MyProjectName.Domain.Shared.csproj @@ -15,7 +15,7 @@ - + diff --git a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj index 73c445396f..65e952c61e 100644 --- a/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj +++ b/templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyCompanyName.MyProjectName.Web.csproj @@ -22,7 +22,7 @@ - + diff --git a/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests.csproj b/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests.csproj index 738eb9185a..a9ec1ccb44 100644 --- a/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests.csproj +++ b/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests/MyCompanyName.MyProjectName.EntityFrameworkCore.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj b/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj index d979841c6a..c657d63704 100644 --- a/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj +++ b/templates/module/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp.csproj @@ -23,7 +23,7 @@ - +
diff --git a/templates/wpf/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj b/templates/wpf/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj index c578865d23..8275968e93 100644 --- a/templates/wpf/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj +++ b/templates/wpf/src/MyCompanyName.MyProjectName/MyCompanyName.MyProjectName.csproj @@ -14,7 +14,7 @@ - +