From cfe513ed068f907dad1519e0bdd8dbe5520022ee Mon Sep 17 00:00:00 2001 From: Gornytskyi Maxim <50423072+MaxymGorn@users.noreply.github.com> Date: Sun, 4 Aug 2024 14:37:21 +0300 Subject: [PATCH] Feature/update NuGet to 1.3.3v (#357) ## Update NuGet to 1.3.3v ## Checklist - [x] Tests cover new or modified code - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the site documentation - [x] I have made corresponding changes to the README, NuGet README file - [x] My changes generate no new warnings - [x] New dependencies added or updated - [ ] Includes breaking changes - [x] Version bumped --------- Co-authored-by: ColdForeign Co-authored-by: George Radchuk <38187349+ColdForeign@users.noreply.github.com> Co-authored-by: Xander Van Boom <49340095+C0lpy@users.noreply.github.com> --- .editorconfig | 102 ++++ .gitattributes | 8 + .github/PULL_REQUEST_TEMPLATE.md | 6 +- .github/dependabot.yml | 4 + .github/linters/.checkov.yaml | 3 + .github/linters/.jscpd.json | 10 +- .github/linters/.stylelintrc.json | 18 +- .github/workflows/build-test-template.yml | 61 ++ .github/workflows/cd.yml | 215 +++---- .github/workflows/ci.yml | 99 ++- .github/workflows/dependabot-ci.yml | 17 + .github/workflows/release.yml | 69 +-- NuGet_README.md | 2 +- README.md | 2 +- .../Cropper.Blazor.MAUI.Net7.csproj | 2 +- .../Components/Layout/MainLayout.razor | 1 + .../Components/Pages/Home.razor.cs | 2 +- .../Cropper.Blazor.MAUI.Net8.csproj | 8 +- .../Cropper.Blazor.Server.Net6.csproj | 2 +- .../Cropper.Blazor.Server.Net7.csproj | 2 +- .../Cropper.Blazor.Server.Net8.csproj | 2 +- .../Pages/Index.razor.cs | 2 +- .../Shared/MainLayout.razor | 1 + .../Cropper.Blazor.WebView.Net6.csproj | 4 +- ...Cropper.MVC.With.Blazor.Server.Net7.csproj | 2 +- .../Client/.config/dotnet-tools.json | 2 +- .../Components/AspectRatioSettings.razor.cs | 2 +- .../Components/CropperDataPreview.razor.cs | 2 +- .../Client/Components/Docs/ApiLink.cs | 22 +- .../Client/Components/Docs/DocsApi.razor | 44 +- .../Client/Components/Docs/DocsApi.razor.cs | 6 +- .../Client/Components/Docs/DocsTypeInfo.razor | 2 +- .../Components/Docs/SectionContent.razor.cs | 32 +- .../Client/Cropper.Blazor.Client.csproj | 14 +- .../Examples/CropPolygonImage_ScriptCode.html | 52 +- .../Examples/CropRoundImage_ScriptCode.html | 40 +- .../Examples/MinMaxZoomRatio_ScriptCode.html | 4 +- .../Client/Pages/CropperDemo.razor.cs | 2 +- .../Client/Pages/CropperDemo.razor.css | 2 +- .../Client/Pages/DataContract.razor | 1 + .../InstallationManualCssFontsExample.razor | 2 +- src/Cropper.Blazor/Client/Pages/Index.razor | 2 +- .../InstallScriptManualCode.html | 6 +- ...tallServicesForBlazorServerManualCode.html | 18 +- .../InstallServicesManualCode.html | 8 +- .../InstallServicesOverrideGlobalCode.html | 6 +- .../InstallServicesOverrideInternalCode.html | 4 +- ...cInputReplaceImageWithNewSizeExample.razor | 7 + ...InputReplaceImageWithSameSizeExample.razor | 7 + .../BasicReplaceImageWithNewSizeExample.razor | 7 + ...BasicReplaceImageWithSameSizeExample.razor | 7 + src/Cropper.Blazor/Client/Program.cs | 2 +- .../Client/Services/MenuService.cs | 1 + .../Client/Shared/AppbarButtons.razor.cs | 2 +- .../Shared/CroppedCanvasDialog.razor.cs | 4 +- .../Client/Shared/DocsLayout.razor.cs | 2 +- .../Client/Shared/SeoHeader.razor.cs | 21 +- .../Shared/Utilities/CropperBlazorLogo.razor | 50 +- .../Client/Styles/Cropper.Blazor.Client.scss | 27 +- .../Client/Styles/components/docspage.scss | 10 +- .../Client/Styles/components/docssection.scss | 32 - .../{_mainlayout.scss => mainlayout.scss} | 10 +- .../layout/{_markdown.scss => markdown.scss} | 56 +- ...tector.scss => updateAvaibleDetector.scss} | 0 src/Cropper.Blazor/Client/wwwroot/helper.js | 94 +-- src/Cropper.Blazor/Client/wwwroot/index.html | 110 ++-- .../Client/wwwroot/jsObjectModule.js | 47 +- .../wwwroot/overrideCropperJsInteropModule.js | 27 +- .../wwwroot/resizeWindowEventListener.js | 34 +- .../Client/wwwroot/service-worker.js | 2 +- .../wwwroot/service-worker.published.js | 116 ++-- .../Client/wwwroot/sw-registrator.js | 52 +- .../CodeSnippetsCompiler.cs | 12 +- .../Cropper.Blazor.Client.Compiler.csproj | 11 +- .../DocStrings.cs | 20 +- .../ExamplesMarkup.cs | 62 +- .../Cropper.Blazor.Client.Compiler/Paths.cs | 42 +- .../Cropper.Blazor.Client.Compiler/Program.cs | 6 +- .../StyleDictionary.KebabCase.cs | 53 ++ .../Cropper.Blazor.Shared.csproj | 16 +- .../Extensions/MethodInfoExtensions.cs | 25 +- .../Extensions/TypeNameHelper.cs | 4 +- .../Extensions/XmlDocumentationExtension.cs | 6 +- .../Cropper.Blazor.Sitemap.Generator.csproj | 18 +- .../Components/CropperComponent_Should.cs | 474 ++++++++++++++- .../Cropper.Blazor.UnitTests.csproj | 40 +- .../Services/CropperJsInterop_Should.cs | 2 +- src/Cropper.Blazor/Cropper.Blazor.sln | 5 + .../Cropper.Blazor/.config/dotnet-tools.json | 2 +- .../Components/CropperComponent.razor | 39 +- .../Components/CropperComponent.razor.cs | 59 +- .../Cropper.Blazor/Cropper.Blazor.csproj | 8 +- .../Models/CropperComponentType.cs | 22 + .../Models/GetCroppedCanvasOptions.cs | 2 +- .../Services/CropperJsInterop.cs | 2 +- .../Services/ICropperJsInterop.cs | 2 +- .../Cropper.Blazor/wwwroot/cropper.min.js | 6 +- .../wwwroot/cropperJsInterop.js | 568 +++++++++--------- .../Server/Cropper.Blazor.Server.csproj | 3 +- .../Server/Pages/Error.cshtml.cs | 2 +- 100 files changed, 1939 insertions(+), 1216 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/linters/.checkov.yaml create mode 100644 .github/workflows/build-test-template.yml create mode 100644 .github/workflows/dependabot-ci.yml rename src/Cropper.Blazor/Client/Styles/layout/{_mainlayout.scss => mainlayout.scss} (87%) rename src/Cropper.Blazor/Client/Styles/layout/{_markdown.scss => markdown.scss} (80%) rename src/Cropper.Blazor/Client/Styles/layout/{_updateAvaibleDetector.scss => updateAvaibleDetector.scss} (100%) create mode 100644 src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/StyleDictionary.KebabCase.cs create mode 100644 src/Cropper.Blazor/Cropper.Blazor/Models/CropperComponentType.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..53536631 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,102 @@ +root = true + +# All files +[*] +indent_style = space +csharp_indent_labels = no_change +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent + +[*.{csproj,sln,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] +indent_size = 4 +tab_width = 4 +indent_style = tab + +# Xml build files +[*.builds] +indent_size = 2 + +# Xml files +[*.{xml,stylecop,resx,ruleset}] +indent_size = 2 + +# Xml config files +[*.{props,targets,config,nuspec}] +indent_size = 2 + +# YML files +[*.yml] +indent_size = 2 +indent_style = space + +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf +dotnet_style_coalesce_expression = true:suggestion +insert_final_newline = true + +[*.html] +tab_width = 4 +indent_size = 4 +indent_style = tab diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..f52991a3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +* text=auto eol=crlf + +# Declare files that will always have LF line endings on checkout. +*.sh text eol=lf + +# Don't check these into the repo as LF to work around TeamCity bug +*.xml -text +*.targets -text diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6f792f92..853d37b3 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -12,13 +12,13 @@ -- [ ] Documentation updated - [ ] Tests cover new or modified code - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas -- [ ] I have made corresponding changes to the documentation +- [ ] I have made corresponding changes to the site documentation +- [ ] I have made corresponding changes to the README, NuGet README file - [ ] My changes generate no new warnings -- [ ] New dependencies added +- [ ] New dependencies added or updated - [ ] Includes breaking changes - [ ] Version bumped diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0089655d..62f7c776 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,3 +5,7 @@ updates: target-branch: "dev" # Location of package manifests schedule: interval: "monthly" + groups: + dotnet: + patterns: + - "*" diff --git a/.github/linters/.checkov.yaml b/.github/linters/.checkov.yaml new file mode 100644 index 00000000..85a09f4e --- /dev/null +++ b/.github/linters/.checkov.yaml @@ -0,0 +1,3 @@ +quiet: true +skip-check: + - CKV2_GHA_1 diff --git a/.github/linters/.jscpd.json b/.github/linters/.jscpd.json index e8fd3bc5..9f757df0 100644 --- a/.github/linters/.jscpd.json +++ b/.github/linters/.jscpd.json @@ -4,7 +4,11 @@ "consoleFull" ], "ignore": [ - "**/src/Cropper.Blazor/Cropper.Blazor.UnitTests/**" + "**/src/Cropper.Blazor/Cropper.Blazor.UnitTests/**", + "**/examples/**", + "**/*.md", + "**/*excubowebcompiler.json" ], - "absolute": true -} \ No newline at end of file + "absolute": true, + "minTokens": 75 +} diff --git a/.github/linters/.stylelintrc.json b/.github/linters/.stylelintrc.json index f866bd1d..0d0e2cd0 100644 --- a/.github/linters/.stylelintrc.json +++ b/.github/linters/.stylelintrc.json @@ -1,12 +1,12 @@ { - "extends": "stylelint-config-standard", + "extends": [ "stylelint-config-standard", "stylelint-config-standard-scss" ], "rules": { - "at-rule-no-unknown": [true, { - "ignoreAtRules": ["function", "if", "each", "include", "mixin"] - }], - "selector-list-comma-newline-after": "always-multi-line", - "selector-list-comma-newline-before": "never-multi-line", - "block-closing-brace-newline-before": "always", + "at-rule-no-unknown": [ + true, + { + "ignoreAtRules": [ "function", "if", "each", "include", "mixin" ] + } + ], "selector-pseudo-class-no-unknown": [ true, { @@ -19,6 +19,6 @@ "ignorePseudoElements": [ "deep" ] } ], - "indentation": 4 + "media-feature-range-notation": "prefix" } -} \ No newline at end of file +} diff --git a/.github/workflows/build-test-template.yml b/.github/workflows/build-test-template.yml new file mode 100644 index 00000000..79d7dadd --- /dev/null +++ b/.github/workflows/build-test-template.yml @@ -0,0 +1,61 @@ +name: Build and run tests + +on: + workflow_call: + inputs: + configuration: + description: 'dotnet build configuration' + required: false + type: string + default: 'Debug' + publish-coverage: + description: 'publish coverage to codecov' + required: false + type: boolean + default: true + +jobs: + build_and_test: + name: Build & Test + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup .NET 8, 7, 6 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 6.0.x + 7.0.x + 8.0.x + + - name: Install WASM tools Workloads + run: | + dotnet workload install wasm-tools --ignore-failed-sources + + - name: Restore dependencies + run: dotnet restore + working-directory: src/Cropper.Blazor + + - name: Restore dotnet tool + run: dotnet tool restore + working-directory: src/Cropper.Blazor/Cropper.Blazor + + - name: DotNet Build + run: dotnet build -c ${{ inputs.configuration }} --no-restore + working-directory: src/Cropper.Blazor + + - name: Test + run: dotnet test -c ${{ inputs.configuration }} --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:ExcludeByAttribute='ExcludeFromCodeCoverage' /p:SkipAutoProps=true /p:Exclude="[*]Cropper.Blazor.Testing.*" + working-directory: src/Cropper.Blazor/Cropper.Blazor.UnitTests + + - name: Coverage + if: ${{ inputs.publish-coverage == true }} + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: coverage.net6.0.cobertura.xml, coverage.net7.0.cobertura.xml, coverage.net8.0.cobertura.xml + fail_ci_if_error: true + verbose: true diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 8a3b7c5f..0e6c1cc0 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -4,7 +4,6 @@ concurrency: group: github-pages cancel-in-progress: true -# Run workflow on every push to the master branch on: push: branches: [ dev ] @@ -12,138 +11,114 @@ on: - '**.md' jobs: + code-quality-check: + name: Check code quality + uses: CropperBlazor/Cropper.Blazor/.github/workflows/build-test-template.yml@dev + secrets: inherit + deploy-to-github-pages: - # use ubuntu-latest image to run steps on runs-on: ubuntu-latest environment: github-pages - needs: demo-projects-build + needs: [code-quality-check, demo-projects-build] steps: - # uses GitHub's checkout action to checkout code form the master branch - - uses: actions/checkout@v3 - - # sets up .NET Core SDK 8, 7, 6 - - name: Setup .NET 8, 7, 6 - uses: actions/setup-dotnet@v3 - with: - dotnet-version: | - 6.0.x - 7.0.x - 8.0.x - - - name: Install WASM tools Workloads - run: | - dotnet workload install wasm-tools --ignore-failed-sources - - - name: Restore dependencies - run: dotnet restore - working-directory: src/Cropper.Blazor - - - name: Restore dotnet tool - run: dotnet tool restore - working-directory: src/Cropper.Blazor/Cropper.Blazor - - - name: Build - run: dotnet build --no-restore - working-directory: src/Cropper.Blazor - - - name: Test - run: dotnet test --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:ExcludeByAttribute='ExcludeFromCodeCoverage' /p:SkipAutoProps=true /p:Exclude="[*]Cropper.Blazor.Testing.*" - working-directory: src/Cropper.Blazor/Cropper.Blazor.UnitTests - - - name: Coverage - uses: codecov/codecov-action@v3.1.4 - with: - files: coverage.net6.0.cobertura.xml, coverage.net7.0.cobertura.xml, coverage.net8.0.cobertura.xml - fail_ci_if_error: true - verbose: true - - - name: Wait for Exit Code 1 for Cropper.Blazor.Sitemap.Generator project - run: | - dotnet run -c Release - working-directory: src/Cropper.Blazor/Cropper.Blazor.Sitemap.Generator - - - name: Check if sitemap.xml File Exists - run: | - filePath="./sitemap.xml" - if [ -f "$filePath" ]; then - echo "sitemap.xml file exists" - else - echo "File does not exist" - exit 1 - fi - working-directory: src/Cropper.Blazor/Client/wwwroot - - # publishes Blazor project to the release-folder - - name: Publish .NET Core Project - run: dotnet publish ./src/Cropper.Blazor/Client/Cropper.Blazor.Client.csproj -c Release --output release --nologo - - # changes the base-tag in index.html from '/' to 'Cropper.Blazor' to match GitHub Pages repository subdirectory - #- name: Change base-tag in index.html from / to Cropper.Blazor - # run: sed -i 's///g' release/wwwroot/index.html - - - name: Fix service-worker-assets.js hashes - working-directory: release/wwwroot - run: | - jsFile=$( service-worker-assets.js - - # copy index.html to 404.html to serve the same file when a file is not found - - name: copy index.html to 404.html - run: cp release/wwwroot/index.html release/wwwroot/404.html - - # add .nojekyll file to tell GitHub pages to not treat this as a Jekyll project. (Allow files and folders starting with an underscore) - - name: Add .nojekyll file - run: touch release/wwwroot/.nojekyll - - - name: Uploading files to gh-pages branch - uses: JamesIves/github-pages-deploy-action@v4.4.0 - with: - token: ${{ secrets.DEPLOY_KEY }} - branch: gh-pages - folder: release/wwwroot - repository-name: CropperBlazor/CropperBlazor.github.io + working-directory: src/Cropper.Blazor/Client/wwwroot + + # publishes Blazor project to the release-folder + - name: Publish .NET Core Project + run: dotnet publish ./src/Cropper.Blazor/Client/Cropper.Blazor.Client.csproj -c Release --output release --nologo + + # changes the base-tag in index.html from '/' to 'Cropper.Blazor' to match GitHub Pages repository subdirectory + #- name: Change base-tag in index.html from / to Cropper.Blazor + # run: sed -i 's///g' release/wwwroot/index.html + + - name: Fix service-worker-assets.js hashes + working-directory: release/wwwroot + run: | + jsFile=$( service-worker-assets.js + + # copy index.html to 404.html to serve the same file when a file is not found + - name: copy index.html to 404.html + run: cp release/wwwroot/index.html release/wwwroot/404.html + + # add .nojekyll file to tell GitHub pages to not treat this as a Jekyll project. (Allow files and folders starting with an underscore) + - name: Add .nojekyll file + run: touch release/wwwroot/.nojekyll + + - name: Uploading files to gh-pages branch + uses: JamesIves/github-pages-deploy-action@v4.4.0 + with: + token: ${{ secrets.DEPLOY_KEY }} + branch: gh-pages + folder: release/wwwroot + repository-name: CropperBlazor/CropperBlazor.github.io demo-projects-build: name: Build Demo Projects runs-on: windows-latest + needs: code-quality-check env: JAVA_HOME: '/home/runner/android-sdk' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - # sets up .NET Core SDK 8, 7, 6 - name: Setup .NET 8, 7, 6 - uses: actions/setup-dotnet@v3.2.0 + uses: actions/setup-dotnet@v4 with: dotnet-version: | 6.0.x @@ -152,7 +127,7 @@ jobs: - name: Cache JDK id: cache-jdk - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.JAVA_HOME }} key: ${{ runner.os }}-jdk-11 @@ -161,7 +136,7 @@ jobs: - name: Setup Java JDK if: steps.cache-jdk.outputs.cache-hit != 'true' - uses: actions/setup-java@v3.13.0 + uses: actions/setup-java@v4 with: distribution: 'microsoft' java-version: 11 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09b60040..06c3edec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,76 +1,45 @@ -name: Build and run tests +name: Build, run tests, code linting concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true on: - push: - branches: - - fix/* - - feature/* pull_request: - branches: - - fix/* - - feature/* + branches-ignore: + - master + - dependabot/* jobs: - build: - name: Build & Test - runs-on: ubuntu-latest + code-quality-check: + name: Check code quality + uses: CropperBlazor/Cropper.Blazor/.github/workflows/build-test-template.yml@dev + secrets: inherit + code-linting: + name: Code linting + runs-on: ubuntu-latest + needs: code-quality-check steps: - - uses: actions/checkout@v3 - - # sets up .NET Core SDK 8, 7, 6 - - name: Setup .NET 8, 7, 6 - uses: actions/setup-dotnet@v3.2.0 - with: - dotnet-version: | - 6.0.x - 7.0.x - 8.0.x - - - name: Install WASM tools Workloads - run: | - dotnet workload install wasm-tools --ignore-failed-sources - - - name: Restore dependencies - run: dotnet restore - working-directory: src/Cropper.Blazor - - - name: Restore dotnet tool - run: dotnet tool restore - working-directory: src/Cropper.Blazor/Cropper.Blazor - - - name: DotNet Build - run: dotnet build --no-restore - working-directory: src/Cropper.Blazor - - - name: Test - run: dotnet test --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:ExcludeByAttribute='ExcludeFromCodeCoverage' /p:SkipAutoProps=true /p:Exclude="[*]Cropper.Blazor.Testing.*" - working-directory: src/Cropper.Blazor/Cropper.Blazor.UnitTests - - - name: Coverage - uses: codecov/codecov-action@v3.1.4 - with: - files: coverage.net6.0.cobertura.xml, coverage.net7.0.cobertura.xml, coverage.net8.0.cobertura.xml - fail_ci_if_error: true - verbose: true - - - name: Super-Linter - uses: github/super-linter@v4.8.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OUTPUT_FOLDER: Reports - OUTPUT_DETAILS: detailed - ERROR_ON_MISSING_EXEC_BIT: true - VALIDATE_ALL_CODEBASE: true - VALIDATE_MARKDOWN: false - VALIDATE_JAVASCRIPT_STANDARD: false - LOG_LEVEL: WARN - FILTER_REGEX_EXCLUDE: '(\W|^)(.*([.]min[.]css))($)|(\W|^)(.*([.]min[.]js))($)' - FILTER_REGEX_INCLUDE: /github/workspace/src/Cropper.Blazor/.* - JSCPD_CONFIG_FILE: '.jscpd.json' - HTML_FILE_NAME: '.htmlhintrc' - CSS_FILE_NAME: '.stylelintrc.json' + - name: Checkout code + uses: actions/checkout@v4 + with: + # super-linter needs the full git history to get the + # list of files that changed across commits + fetch-depth: 0 + + - name: Super-Linter + uses: super-linter/super-linter@v6.8.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OUTPUT_FOLDER: Reports + OUTPUT_DETAILS: detailed + VALIDATE_ALL_CODEBASE: true + VALIDATE_MARKDOWN: false + VALIDATE_JAVASCRIPT_PRETTIER: false + LOG_LEVEL: WARN + FILTER_REGEX_EXCLUDE: '(\W|^)(.*([.]min[.]css))($)|(\W|^)(.*([.]min[.]js))($)' + FILTER_REGEX_INCLUDE: /github/workspace/src/Cropper.Blazor/.* + JSCPD_CONFIG_FILE: '.jscpd.json' + HTML_FILE_NAME: '.htmlhintrc' + CSS_FILE_NAME: '.stylelintrc.json' diff --git a/.github/workflows/dependabot-ci.yml b/.github/workflows/dependabot-ci.yml new file mode 100644 index 00000000..00764677 --- /dev/null +++ b/.github/workflows/dependabot-ci.yml @@ -0,0 +1,17 @@ +name: Build & Test + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +on: + push: + branches: + - dependabot/* + +jobs: + build-and-test: + name: Build & Test + uses: CropperBlazor/Cropper.Blazor/.github/workflows/build-test-template.yml@dev + with: + publish-coverage: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aef79afa..676b2b00 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,61 +1,38 @@ name: Deploy to NuGet -# Run workflow on every push to the master branch on: push: branches: - master jobs: - deploy-to-nuget: + build-and-test: + name: Build & Test + uses: CropperBlazor/Cropper.Blazor/.github/workflows/build-test-template.yml@dev + secrets: inherit + with: + configuration: 'Release' + publish-coverage: true - # use ubuntu-latest image to run steps on + deploy-to-nuget: + name: Deploy to NuGet runs-on: ubuntu-latest + needs: build-and-test steps: + - name: Checkout code + uses: actions/checkout@v4 - # uses GitHub's checkout action to checkout code form the master branch - - uses: actions/checkout@v3 - - # sets up .NET Core SDK 8, 7, 6 - - name: Setup .NET 8, 7, 6 - uses: actions/setup-dotnet@v3.2.0 - with: - dotnet-version: | - 6.0.x - 7.0.x - 8.0.x - - - name: Install WASM tools Workloads - run: | - dotnet workload install wasm-tools --ignore-failed-sources - - - name: Restore dependencies - run: dotnet restore - working-directory: src/Cropper.Blazor - - - name: Restore dotnet tool - run: dotnet tool restore - working-directory: src/Cropper.Blazor/Cropper.Blazor - - - name: Build - run: dotnet build -c Release --no-restore - working-directory: src/Cropper.Blazor - - - name: Test - run: dotnet test -c Release --no-build --no-restore --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:ExcludeByAttribute='ExcludeFromCodeCoverage' /p:SkipAutoProps=true /p:Exclude="[*]Cropper.Blazor.Testing.*" - working-directory: src/Cropper.Blazor/Cropper.Blazor.UnitTests - - - name: Pack package - run: dotnet pack -c Release - working-directory: src/Cropper.Blazor/Cropper.Blazor + - name: Pack package + run: dotnet pack -c Release + working-directory: src/Cropper.Blazor/Cropper.Blazor - - name: Push to NuGet Gallery - run: dotnet nuget push bin/Release/*.nupkg --source "https://api.nuget.org/v3/index.json" --api-key ${{secrets.NUGET_API_KEY}} --skip-duplicate - working-directory: src/Cropper.Blazor/Cropper.Blazor + - name: Push to NuGet Gallery + run: dotnet nuget push bin/Release/*.nupkg --source "https://api.nuget.org/v3/index.json" --api-key ${{secrets.NUGET_API_KEY}} --skip-duplicate + working-directory: src/Cropper.Blazor/Cropper.Blazor - - name: Add NuGet repository source - run: dotnet nuget add source --username CropperBlazor --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/CropperBlazor/index.json" + - name: Add NuGet repository source + run: dotnet nuget add source --username CropperBlazor --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/CropperBlazor/index.json" - - name: Publish NuGet package to GitHub packages - run: dotnet nuget push bin/Release/*.nupkg --api-key ${{ secrets.GITHUB_TOKEN }} --source "github" --skip-duplicate - working-directory: src/Cropper.Blazor/Cropper.Blazor + - name: Publish NuGet package to GitHub packages + run: dotnet nuget push bin/Release/*.nupkg --api-key ${{ secrets.GITHUB_TOKEN }} --source "github" --skip-duplicate + working-directory: src/Cropper.Blazor/Cropper.Blazor diff --git a/NuGet_README.md b/NuGet_README.md index 59d79fc9..f82d9c85 100644 --- a/NuGet_README.md +++ b/NuGet_README.md @@ -1,4 +1,4 @@ -## Cropper.Blazor is a component that wraps around Cropper.js version 1.6.1 +## Cropper.Blazor is a component that wraps around Cropper.js version 1.6.2 [![Build and run test](https://github.com/CropperBlazor/Cropper.Blazor/actions/workflows/ci.yml/badge.svg?event=push)](https://github.com/CropperBlazor/Cropper.Blazor/actions/workflows/ci.yml) [![Deploy to GitHub Pages](https://github.com/CropperBlazor/Cropper.Blazor/actions/workflows/cd.yml/badge.svg?event=push)](https://github.com/CropperBlazor/Cropper.Blazor/actions/workflows/cd.yml) diff --git a/README.md b/README.md index 9a66d16f..dafe1769 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Cropper.Blazor

- Cropper.Blazor is a component that wraps around Cropper.js version 1.6.1 + Cropper.Blazor is a component that wraps around Cropper.js version 1.6.2

diff --git a/examples/Cropper.Blazor.MAUI.Net7/Cropper.Blazor.MAUI.Net7.csproj b/examples/Cropper.Blazor.MAUI.Net7/Cropper.Blazor.MAUI.Net7.csproj index 7c673f4f..c0a175ea 100644 --- a/examples/Cropper.Blazor.MAUI.Net7/Cropper.Blazor.MAUI.Net7.csproj +++ b/examples/Cropper.Blazor.MAUI.Net7/Cropper.Blazor.MAUI.Net7.csproj @@ -52,7 +52,7 @@ - + diff --git a/examples/Cropper.Blazor.MAUI.Net8/Components/Layout/MainLayout.razor b/examples/Cropper.Blazor.MAUI.Net8/Components/Layout/MainLayout.razor index ef644faa..f7dbbc34 100644 --- a/examples/Cropper.Blazor.MAUI.Net8/Components/Layout/MainLayout.razor +++ b/examples/Cropper.Blazor.MAUI.Net8/Components/Layout/MainLayout.razor @@ -3,6 +3,7 @@ +
diff --git a/src/Cropper.Blazor/Client/Pages/ManualMarkdown/InstallServicesOverrideInternalCode.html b/src/Cropper.Blazor/Client/Pages/ManualMarkdown/InstallServicesOverrideInternalCode.html index fc50fc6e..56f136c4 100644 --- a/src/Cropper.Blazor/Client/Pages/ManualMarkdown/InstallServicesOverrideInternalCode.html +++ b/src/Cropper.Blazor/Client/Pages/ManualMarkdown/InstallServicesOverrideInternalCode.html @@ -4,9 +4,9 @@ using Cropper.Blazor.Extensions; // Override internal path to cropperJSInterop.min.js module -builder.Services.AddCropper(new CropperJsInteropOptions() +builder.Services.AddCropper(new CropperJsInteropOptions() { - DefaultInternalPathToCropperModule = "{YourPath}/_content/Cropper.Blazor/cropperJsInterop.min.js" + DefaultInternalPathToCropperModule = "{YourPath}/_content/Cropper.Blazor/cropperJsInterop.min.js" }); diff --git a/src/Cropper.Blazor/Client/Pages/Replace/Examples/BasicInputReplaceImageWithNewSizeExample.razor b/src/Cropper.Blazor/Client/Pages/Replace/Examples/BasicInputReplaceImageWithNewSizeExample.razor index 81e21f9b..c4f6db94 100644 --- a/src/Cropper.Blazor/Client/Pages/Replace/Examples/BasicInputReplaceImageWithNewSizeExample.razor +++ b/src/Cropper.Blazor/Client/Pages/Replace/Examples/BasicInputReplaceImageWithNewSizeExample.razor @@ -80,6 +80,13 @@ IsAvailableInitCropper = false; } + // In the .NET 8 with new render modes, SDK parameters components are not updated timely when using 'blazor.web.js'. + // We strongly recommend using StateHasChanged() method after changing a component's parameter, + // but be careful with this because you're updating the entire component where there may be more than one component, + // which can also affect performance a bit when there are too many components on the page or animations. + // As an option, make a separate wrapper with minimal functionality in this case. + //StateHasChanged(); + await Task.WhenAll( CropperComponent!.ReplaceAsync(newSrc, false).AsTask() // For certain platforms based on WebView in MAUI, Windows Forms and WPF Blazor Hybrids, the 'RevokeObjectUrlAsync(OldSrc)' method does not work correctly in this place. diff --git a/src/Cropper.Blazor/Client/Pages/Replace/Examples/BasicInputReplaceImageWithSameSizeExample.razor b/src/Cropper.Blazor/Client/Pages/Replace/Examples/BasicInputReplaceImageWithSameSizeExample.razor index 6203a51a..bb097977 100644 --- a/src/Cropper.Blazor/Client/Pages/Replace/Examples/BasicInputReplaceImageWithSameSizeExample.razor +++ b/src/Cropper.Blazor/Client/Pages/Replace/Examples/BasicInputReplaceImageWithSameSizeExample.razor @@ -80,6 +80,13 @@ IsAvailableInitCropper = false; } + // In the .NET 8 with new render modes, SDK parameters components are not updated timely when using 'blazor.web.js'. + // We strongly recommend using StateHasChanged() method after changing a component's parameter, + // but be careful with this because you're updating the entire component where there may be more than one component, + // which can also affect performance a bit when there are too many components on the page or animations. + // As an option, make a separate wrapper with minimal functionality in this case. + //StateHasChanged(); + await Task.WhenAll( CropperComponent!.ReplaceAsync(newSrc, true).AsTask() // For certain platforms based on WebView in MAUI, Windows Forms and WPF Blazor Hybrids, the 'RevokeObjectUrlAsync(OldSrc)' method does not work correctly in this place. diff --git a/src/Cropper.Blazor/Client/Pages/Replace/Examples/BasicReplaceImageWithNewSizeExample.razor b/src/Cropper.Blazor/Client/Pages/Replace/Examples/BasicReplaceImageWithNewSizeExample.razor index da52b5e5..d9c46689 100644 --- a/src/Cropper.Blazor/Client/Pages/Replace/Examples/BasicReplaceImageWithNewSizeExample.razor +++ b/src/Cropper.Blazor/Client/Pages/Replace/Examples/BasicReplaceImageWithNewSizeExample.razor @@ -50,6 +50,13 @@ { IsReplaced = true; + // In the .NET 8 with new render modes, SDK parameters components are not updated timely when using 'blazor.web.js'. + // We strongly recommend using StateHasChanged() method after changing a component's parameter, + // but be careful with this because you're updating the entire component where there may be more than one component, + // which can also affect performance a bit when there are too many components on the page or animations. + // As an option, make a separate wrapper with minimal functionality in this case. + //StateHasChanged(); + await CropperComponent!.ReplaceAsync("images/raspberry.jpg", false); // Releases an existing object URL which was previously created by calling URL. diff --git a/src/Cropper.Blazor/Client/Pages/Replace/Examples/BasicReplaceImageWithSameSizeExample.razor b/src/Cropper.Blazor/Client/Pages/Replace/Examples/BasicReplaceImageWithSameSizeExample.razor index af6eaf8c..445f02c3 100644 --- a/src/Cropper.Blazor/Client/Pages/Replace/Examples/BasicReplaceImageWithSameSizeExample.razor +++ b/src/Cropper.Blazor/Client/Pages/Replace/Examples/BasicReplaceImageWithSameSizeExample.razor @@ -50,6 +50,13 @@ { IsReplaced = true; + // In the .NET 8 with new render modes, SDK parameters components are not updated timely when using 'blazor.web.js'. + // We strongly recommend using StateHasChanged() method after changing a component's parameter, + // but be careful with this because you're updating the entire component where there may be more than one component, + // which can also affect performance a bit when there are too many components on the page or animations. + // As an option, make a separate wrapper with minimal functionality in this case. + //StateHasChanged(); + await CropperComponent!.ReplaceAsync("images/raspberry.jpg", true); // Releases an existing object URL which was previously created by calling URL. diff --git a/src/Cropper.Blazor/Client/Program.cs b/src/Cropper.Blazor/Client/Program.cs index 91998f48..91bd55b7 100644 --- a/src/Cropper.Blazor/Client/Program.cs +++ b/src/Cropper.Blazor/Client/Program.cs @@ -23,4 +23,4 @@ static void ConfigureServices(IServiceCollection services, IWebAssemblyHostEnvir .AddSingleton() .AddCropper() .TryAddDocsViewServices(); -} \ No newline at end of file +} diff --git a/src/Cropper.Blazor/Client/Services/MenuService.cs b/src/Cropper.Blazor/Client/Services/MenuService.cs index bf60862f..b3190fa8 100644 --- a/src/Cropper.Blazor/Client/Services/MenuService.cs +++ b/src/Cropper.Blazor/Client/Services/MenuService.cs @@ -38,6 +38,7 @@ public class MenuService : IMenuService new() {Title = "CropperComponent", Href = "api"}, new() {Title = "ViewMode", Href = "api/ViewMode"}, new() {Title = "DragMode", Href = "api/DragMode"}, + new() {Title = "CropperComponentType", Href = "api/CropperComponentType"}, new() {Group = "Options", Title = "Options", Href = "api/Options"}, new() {Group = "Options", Title = "GetCroppedCanvasOptions", Href = "api/GetCroppedCanvasOptions"}, new() {Group = "Options", Title = "SetCropBoxDataOptions", Href = "api/SetCropBoxDataOptions"}, diff --git a/src/Cropper.Blazor/Client/Shared/AppbarButtons.razor.cs b/src/Cropper.Blazor/Client/Shared/AppbarButtons.razor.cs index 620fa8d9..7542a987 100644 --- a/src/Cropper.Blazor/Client/Shared/AppbarButtons.razor.cs +++ b/src/Cropper.Blazor/Client/Shared/AppbarButtons.razor.cs @@ -6,4 +6,4 @@ namespace Cropper.Blazor.Client.Shared; public partial class AppbarButtons { [Inject] private LayoutService LayoutService { get; set; } = null!; -} \ No newline at end of file +} diff --git a/src/Cropper.Blazor/Client/Shared/CroppedCanvasDialog.razor.cs b/src/Cropper.Blazor/Client/Shared/CroppedCanvasDialog.razor.cs index f399195d..452525d4 100644 --- a/src/Cropper.Blazor/Client/Shared/CroppedCanvasDialog.razor.cs +++ b/src/Cropper.Blazor/Client/Shared/CroppedCanvasDialog.razor.cs @@ -14,8 +14,8 @@ public partial class CroppedCanvasDialog public async Task DownloadImageSrcAsync() { await JSRuntime!.InvokeVoidAsync( - "downloadFromUrl", - new { Url = Src, FileName = $"{Guid.NewGuid()}.png" }); + "downloadFromUrl", + new { Url = Src, FileName = $"{Guid.NewGuid()}.png" }); } public Dictionary CroppedImageInputAttributes { get; set; } = diff --git a/src/Cropper.Blazor/Client/Shared/DocsLayout.razor.cs b/src/Cropper.Blazor/Client/Shared/DocsLayout.razor.cs index b9835cd2..0c835876 100644 --- a/src/Cropper.Blazor/Client/Shared/DocsLayout.razor.cs +++ b/src/Cropper.Blazor/Client/Shared/DocsLayout.razor.cs @@ -40,4 +40,4 @@ private void OnDrawerOpenChanged(bool value) _drawerOpen = value; StateHasChanged(); } -} \ No newline at end of file +} diff --git a/src/Cropper.Blazor/Client/Shared/SeoHeader.razor.cs b/src/Cropper.Blazor/Client/Shared/SeoHeader.razor.cs index 9b115d51..9fee1fba 100644 --- a/src/Cropper.Blazor/Client/Shared/SeoHeader.razor.cs +++ b/src/Cropper.Blazor/Client/Shared/SeoHeader.razor.cs @@ -40,7 +40,26 @@ private string GetKeywords() ".net core", "pwa", "webassembly", - ..Keywords, + "blazor image editor", + "image editor", + "blazor image", + "free image cropper", + "online cropper", + "photo cropper", + "avatar cropper", + "photo cropper", + "image cropper", + "resize image", + "photo resizer", + "blazor online cropper", + "blazor free image cropper", + "blazor photo cropper", + "blazor avatar cropper", + "blazor photo cropper", + "blazor image cropper", + "blazor resize image", + "blazor photo resizer", + .. Keywords ]; return string.Join(", ", keywords); diff --git a/src/Cropper.Blazor/Client/Shared/Utilities/CropperBlazorLogo.razor b/src/Cropper.Blazor/Client/Shared/Utilities/CropperBlazorLogo.razor index b5eca0ec..1673e1f9 100644 --- a/src/Cropper.Blazor/Client/Shared/Utilities/CropperBlazorLogo.razor +++ b/src/Cropper.Blazor/Client/Shared/Utilities/CropperBlazorLogo.razor @@ -10,15 +10,15 @@ + c0,1.1-0.9,2-2,2H22c-6.9,0-12.5,5.6-12.5,12.5v85c0,6.9,5.6,12.5,12.5,12.5h56.5c1.1,0,2,0.9,2,2V557c0,6.9,5.6,12.5,12.5,12.5 + h364.5c1.1,0,2,0.9,2,2V628c0,6.9,5.6,12.5,12.5,12.5h85c6.9,0,12.5-5.6,12.5-12.5v-56.5c0-1.1,0.9-2,2-2H628 + c6.9,0,12.5-5.6,12.5-12.5v-85C640.5,465.1,634.9,459.5,628,459.5z M80.5,163.5c0,1.1-0.9,2-2,2h-42c-1.1,0-2-0.9-2-2v-56 + c0-1.1,0.9-2,2-2h42c1.1,0,2,0.9,2,2V163.5z M544.5,613.5c0,1.1-0.9,2-2,2h-56c-1.1,0-2-0.9-2-2v-42c0-1.1,0.9-2,2-2h56 + c1.1,0,2,0.9,2,2V613.5z M615.5,542.5c0,1.1-0.9,2-2,2h-506c-1.1,0-2-0.9-2-2v-506c0-1.1,0.9-2,2-2h56c1.1,0,2,0.9,2,2V472 + c0,6.9,5.6,12.5,12.5,12.5h435.5c1.1,0,2,0.9,2,2V542.5z M484.5,178v249c0,6.9-5.6,12.5-12.5,12.5s-12.5-5.6-12.5-12.5V192.5 + c0-1.1-0.9-2-2-2H223c-6.9,0-12.5-5.6-12.5-12.5s5.6-12.5,12.5-12.5h249C478.9,165.5,484.5,171.1,484.5,178z M569.5,93v334 + c0,6.9-5.6,12.5-12.5,12.5s-12.5-5.6-12.5-12.5V107.5c0-1.1-0.9-2-2-2H223c-6.9,0-12.5-5.6-12.5-12.5s5.6-12.5,12.5-12.5h334 + C563.9,80.5,569.5,86.1,569.5,93z" /> @@ -28,25 +28,25 @@ + c0,6.9-5.6,12.5-12.5,12.5h-56.5c-1.1,0-2,0.9-2,2V628c0,6.9-5.6,12.5-12.5,12.5h-85c-6.9,0-12.5-5.6-12.5-12.5v-56.5 + c0-1.1-0.9-2-2-2H93c-6.9,0-12.5-5.6-12.5-12.5V192.5c0-1.1-0.9-2-2-2H22c-6.9,0-12.5-5.6-12.5-12.5V93c0-6.9,5.6-12.5,12.5-12.5 + h56.5c1.1,0,2-0.9,2-2V22c0-6.9,5.6-12.5,12.5-12.5H178 M107.5,544.5h506c1.1,0,2-0.9,2-2v-56c0-1.1-0.9-2-2-2H178 + c-6.9,0-12.5-5.6-12.5-12.5V36.5c0-1.1-0.9-2-2-2h-56c-1.1,0-2,0.9-2,2v506C105.5,543.6,106.4,544.5,107.5,544.5 M36.5,165.5h42 + c1.1,0,2-0.9,2-2v-56c0-1.1-0.9-2-2-2h-42c-1.1,0-2,0.9-2,2v56C34.5,164.6,35.4,165.5,36.5,165.5 M486.5,615.5h56c1.1,0,2-0.9,2-2 + v-42c0-1.1-0.9-2-2-2h-56c-1.1,0-2,0.9-2,2v42C484.5,614.6,485.4,615.5,486.5,615.5 M557,80.5c6.9,0,12.5,5.6,12.5,12.5v334 + c0,6.9-5.6,12.5-12.5,12.5s-12.5-5.6-12.5-12.5V107.5c0-1.1-0.9-2-2-2H223c-6.9,0-12.5-5.6-12.5-12.5s5.6-12.5,12.5-12.5H557 + M472,165.5c6.9,0,12.5,5.6,12.5,12.5v249c0,6.9-5.6,12.5-12.5,12.5s-12.5-5.6-12.5-12.5V192.5c0-1.1-0.9-2-2-2H223 + c-6.9,0-12.5-5.6-12.5-12.5s5.6-12.5,12.5-12.5H472 M178,1.5H93c-11.3,0-20.5,9.2-20.5,20.5v50.5H22C10.7,72.5,1.5,81.7,1.5,93v85 + c0,11.3,9.2,20.5,20.5,20.5h50.5V557c0,11.3,9.2,20.5,20.5,20.5h358.5V628c0,11.3,9.2,20.5,20.5,20.5h85c11.3,0,20.5-9.2,20.5-20.5 + v-50.5H628c11.3,0,20.5-9.2,20.5-20.5v-85c0-11.3-9.2-20.5-20.5-20.5H198.5V22C198.5,10.7,189.3,1.5,178,1.5L178,1.5z M113.5,42.5 + h44V472c0,11.3,9.2,20.5,20.5,20.5h429.5v44h-494V42.5L113.5,42.5z M42.5,113.5h30v44h-30V113.5L42.5,113.5z M492.5,577.5h44v30 + h-44V577.5L492.5,577.5z M557,72.5H223c-11.3,0-20.5,9.2-20.5,20.5s9.2,20.5,20.5,20.5h313.5V427c0,11.3,9.2,20.5,20.5,20.5 + s20.5-9.2,20.5-20.5V93C577.5,81.7,568.3,72.5,557,72.5L557,72.5z M472,157.5H223c-11.3,0-20.5,9.2-20.5,20.5s9.2,20.5,20.5,20.5 + h228.5V427c0,11.3,9.2,20.5,20.5,20.5s20.5-9.2,20.5-20.5V178C492.5,166.7,483.3,157.5,472,157.5L472,157.5z" /> @code { [Parameter] public string Style { get; set; } = null!; - [Parameter] public string Class { get; set; } = null!; + [Parameter] public string Class { get; set; } = null!; } diff --git a/src/Cropper.Blazor/Client/Styles/Cropper.Blazor.Client.scss b/src/Cropper.Blazor/Client/Styles/Cropper.Blazor.Client.scss index 7208256a..5ac344f4 100644 --- a/src/Cropper.Blazor/Client/Styles/Cropper.Blazor.Client.scss +++ b/src/Cropper.Blazor/Client/Styles/Cropper.Blazor.Client.scss @@ -1,8 +1,8 @@ -@import 'layout/_mainlayout'; -@import 'layout/_markdown'; -@import 'layout/_updateAvaibleDetector.scss'; -@import 'components/docssection.scss'; -@import 'components/docspage.scss'; +@import 'layout/mainlayout'; +@import 'layout/markdown'; +@import 'layout/updateAvaibleDetector'; +@import 'components/docssection'; +@import 'components/docspage'; .master-loader { height: 100%; @@ -47,8 +47,6 @@ padding: 20px; &:nth-child(1) { - -moz-filter: grayscale(1); - -webkit-filter: grayscale(1); filter: grayscale(1); z-index: 1; } @@ -86,7 +84,7 @@ padding-top: 1.1rem; } -.valid.modified:not([type=checkbox]) { +.valid.modified:not([type="checkbox"]) { outline: 1px solid #26b050; } @@ -101,10 +99,10 @@ #blazor-error-ui { background: lightyellow; bottom: 0; - box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + box-shadow: 0 -1px 2px rgb(0 0 0 / 20%); display: none; left: 0; - padding: 0.6rem 1.25rem 0.7rem 1.25rem; + padding: 0.6rem 1.25rem 0.7rem; position: fixed; width: 100%; z-index: 1000; @@ -118,7 +116,7 @@ } .blazor-error-boundary { - background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + background: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=") no-repeat 1rem/1.8rem, #b32121; padding: 1rem 1rem 1rem 3.7rem; color: white; } @@ -150,7 +148,6 @@ background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab); background-size: 400% 400%; background-clip: text; - -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 800; letter-spacing: 1px; @@ -183,7 +180,7 @@ } .button-gradient:hover { - box-shadow: 0.3px 0.5px 0.4px hsla(200, 100%, 38%, 0.62), 0.3px 0.6px 0.5px -0.7px hsla(200, 100%, 38%, 0.54), 0.8px 1.6px 1.3px -1.4px hsla(200, 100%, 38%, 0.46), 2px 4.1px 3.4px -2.1px hsla(200, 100%, 38%, 0.38), 4.5px 9px 7.5px -2.9px hsla(200, 100%, 38%, 0.31), 8.5px 17.1px 14.3px -3.6px hsla(200, 100%, 38%, 0.23), 14.6px 29.2px 24.5px -4.3px hsla(200, 100%, 38%, 0.15), 23px 46px 38.6px -5px hsla(200, 100%, 38%, 0.08); + box-shadow: 0.3px 0.5px 0.4px hsl(200deg 100% 38% / 62%), 0.3px 0.6px 0.5px -0.7px hsl(200deg 100% 38% / 54%), 0.8px 1.6px 1.3px -1.4px hsl(200deg 100% 38% / 46%), 2px 4.1px 3.4px -2.1px hsl(200deg 100% 38% / 38%), 4.5px 9px 7.5px -2.9px hsl(200deg 100% 38% / 31%), 8.5px 17.1px 14.3px -3.6px hsl(200deg 100% 38% / 23%), 14.6px 29.2px 24.5px -4.3px hsl(200deg 100% 38% / 15%), 23px 46px 38.6px -5px hsl(200deg 100% 38% / 8%); } .cropper-error-load { @@ -195,7 +192,7 @@ display: flex; width: 30px; height: 20px; - background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjAwIDgwMCI+DQo8cGF0aCBmaWxsPSIjM2E3NWM0IiBkPSJtMCwwaDEyMDB2ODAwaC0xMjAweiIvPg0KPHBhdGggZmlsbD0iI2Y5ZGQxNiIgZD0ibTAsNDAwaDEyMDB2NDAwaC0xMjAweiIvPg0KPC9zdmc+DQo=); + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjAwIDgwMCI+DQo8cGF0aCBmaWxsPSIjM2E3NWM0IiBkPSJtMCwwaDEyMDB2ODAwaC0xMjAweiIvPg0KPHBhdGggZmlsbD0iI2Y5ZGQxNiIgZD0ibTAsNDAwaDEyMDB2NDAwaC0xMjAweiIvPg0KPC9zdmc+DQo="); background-color: initial; border-radius: 4px; box-sizing: border-box; @@ -210,7 +207,7 @@ } .cropper-face { - opacity: 10%; + opacity: 0.1; } .img-container.cropper-face-close .cropper-container .cropper-crop-box .cropper-face { diff --git a/src/Cropper.Blazor/Client/Styles/components/docspage.scss b/src/Cropper.Blazor/Client/Styles/components/docspage.scss index eb99cfdf..0a5b10c7 100644 --- a/src/Cropper.Blazor/Client/Styles/components/docspage.scss +++ b/src/Cropper.Blazor/Client/Styles/components/docspage.scss @@ -73,6 +73,11 @@ color: var(--mud-palette-tertiary); background-color: var(--mud-palette-tertiary-hover); } + + &.docs-code-warning { + color: var(--mud-palette-warning); + background-color: var(--mud-palette-warning-hover); + } } .docs-frame { @@ -82,10 +87,7 @@ & .docs-frame-absolute { position: absolute; - bottom: 0; - left: 0; - top: 0; - right: auto; + inset: 0 auto 0 0; width: 100%; height: 100%; diff --git a/src/Cropper.Blazor/Client/Styles/components/docssection.scss b/src/Cropper.Blazor/Client/Styles/components/docssection.scss index d609cad7..94df2129 100644 --- a/src/Cropper.Blazor/Client/Styles/components/docssection.scss +++ b/src/Cropper.Blazor/Client/Styles/components/docssection.scss @@ -174,38 +174,6 @@ } } } - - .docs-code { - display: inline-block; - padding: 0 5px; - direction: ltr; - font-size: 0.85em; - font-weight: 900; - font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; - line-height: 1.4; - border-radius: 2px; - -webkit-font-smoothing: subpixel-antialiased; - - &.docs-code-primary { - color: var(--mud-palette-primary); - background-color: var(--mud-palette-primary-hover); - } - - &.docs-code-secondary { - color: var(--mud-palette-secondary); - background-color: var(--mud-palette-secondary-hover); - } - - &.docs-code-tertiary { - color: var(--mud-palette-tertiary); - background-color: var(--mud-palette-tertiary-hover); - } - - &.docs-code-warning { - color: var(--mud-palette-warning); - background-color: var(--mud-palette-warning-hover); - } - } } .warning-color { diff --git a/src/Cropper.Blazor/Client/Styles/layout/_mainlayout.scss b/src/Cropper.Blazor/Client/Styles/layout/mainlayout.scss similarity index 87% rename from src/Cropper.Blazor/Client/Styles/layout/_mainlayout.scss rename to src/Cropper.Blazor/Client/Styles/layout/mainlayout.scss index 08364e79..6e3acab5 100644 --- a/src/Cropper.Blazor/Client/Styles/layout/_mainlayout.scss +++ b/src/Cropper.Blazor/Client/Styles/layout/mainlayout.scss @@ -1,11 +1,11 @@ .docs-title { - font-family: 'Public Sans', 'Roboto', 'Arial', sans-serif; + font-family: 'Public Sans', Roboto, Arial, sans-serif; font-weight: 600; font-size: 3.75rem; } .docs-title-description { - font-family: 'Public Sans', 'Roboto', 'Arial', sans-serif; + font-family: 'Public Sans', Roboto, Arial, sans-serif; font-weight: 500; line-height: 1.75; font-size: 1.1rem; @@ -48,10 +48,10 @@ } .mud-button { - font-family: 'Public Sans', 'Roboto', 'Arial', sans-serif; + font-family: 'Public Sans', Roboto, Arial, sans-serif; font-weight: 500; line-height: 1.75; - letter-spacing: 0.02857em; + letter-spacing: 0.0286em; text-transform: none; } } @@ -75,7 +75,7 @@ .docs-brand-text { letter-spacing: 0.1rem; font-weight: 500; - font-family: 'Roboto', sans-serif; + font-family: Roboto, sans-serif; margin-inline-start: 12px; user-select: none; } diff --git a/src/Cropper.Blazor/Client/Styles/layout/_markdown.scss b/src/Cropper.Blazor/Client/Styles/layout/markdown.scss similarity index 80% rename from src/Cropper.Blazor/Client/Styles/layout/_markdown.scss rename to src/Cropper.Blazor/Client/Styles/layout/markdown.scss index f0c1446d..04064d4b 100644 --- a/src/Cropper.Blazor/Client/Styles/layout/_markdown.scss +++ b/src/Cropper.Blazor/Client/Styles/layout/markdown.scss @@ -1,46 +1,46 @@ .mud-landingpage-editor { & .html, .codearea { - & .htmlTagDelimiter { + & .html-tag-delimiter { color: #979797; } - & .htmlElementName { + & .html-element-name { color: #988ef1; } - & .htmlAttributeName { + & .html-attribute-name { color: #27b5b5; } - & .htmlOperator, .quot { + & .html-operator, .quot { color: #c8c8c8; } - & .htmlAttributeValue { + & .html-attribute-value { color: #ededed; } - & .htmlLink { + & .html-link { color: #61afef; text-decoration: underline; } & .enum { color: #b4eb8f; - background-color: rgba(255, 255, 255, 0.15); + background-color: rgb(255 255 255 / 15%); } - & .enumValue, .sharpVariable { + & .enum-value, .sharp-variable { color: #ededed; - background-color: rgba(255, 255, 255, 0.15); + background-color: rgb(255 255 255 / 15%); } & .keyword { color: #61afef; - background-color: rgba(255, 255, 255, 0.15); + background-color: rgb(255 255 255 / 15%); } - & .atSign { + & .at-sign { color: #8323d8; } @@ -50,7 +50,7 @@ } & .csharp { - & .atSign { + & .at-sign { color: #000; background-color: #d2d295; } @@ -71,7 +71,7 @@ color: #4ec9b0; } - & .localVar { + & .local-var { color: #9cdcfe; } @@ -110,7 +110,7 @@ & .css { & .property { - color: hsl(76, 21%, 52%); + color: hsl(76deg 21% 52%); } & .comment { @@ -119,7 +119,7 @@ } & .csharp { - & .atSign { + & .at-sign { color: #8323d8; } @@ -139,7 +139,7 @@ color: #1ec8a5; } - & .localVar { + & .local-var { color: #2196f3; } @@ -159,19 +159,19 @@ color: #57a64a; } - & .htmlElementName { + & .html-element-name { color: #7e6fffff; } - & .htmlAttributeName { + & .html-attribute-name { color: #8323d8; } - & .htmlAttributeValue { + & .html-attribute-value { color: #ff4081; } - & .htmlOperator { + & .html-operator { color: #737373; } @@ -195,28 +195,28 @@ user-select: all; } - & .htmlTagDelimiter { + & .html-tag-delimiter { color: #979797; } - & .htmlElementName { + & .html-element-name { color: var(--mud-palette-primary); font-weight: 600; } - & .htmlAttributeName { + & .html-attribute-name { color: #8323d8; } - & .htmlOperator, .quot { + & .html-operator, .quot { color: #737373; } - & .htmlAttributeValue { + & .html-attribute-value { color: #ff4081; } - & .htmlLink { + & .html-link { color: #ff4081; text-decoration: underline; } @@ -226,7 +226,7 @@ background-color: var(--mud-palette-grey-light); } - & .enumValue, .sharpVariable { + & .enum-value, .sharp-variable { color: var(--mud-palette-text-primary); background-color: var(--mud-palette-grey-light); } @@ -236,7 +236,7 @@ background-color: var(--mud-palette-grey-light); } - & .atSign { + & .at-sign { color: #8323d8; } diff --git a/src/Cropper.Blazor/Client/Styles/layout/_updateAvaibleDetector.scss b/src/Cropper.Blazor/Client/Styles/layout/updateAvaibleDetector.scss similarity index 100% rename from src/Cropper.Blazor/Client/Styles/layout/_updateAvaibleDetector.scss rename to src/Cropper.Blazor/Client/Styles/layout/updateAvaibleDetector.scss diff --git a/src/Cropper.Blazor/Client/wwwroot/helper.js b/src/Cropper.Blazor/Client/wwwroot/helper.js index 1610cffd..acdef48a 100644 --- a/src/Cropper.Blazor/Client/wwwroot/helper.js +++ b/src/Cropper.Blazor/Client/wwwroot/helper.js @@ -1,53 +1,53 @@ -window.downloadFromUrl = (options) => { - const anchorElement = document.createElement('a'); - anchorElement.href = options.url; - anchorElement.download = options.fileName ?? ''; - anchorElement.click(); - anchorElement.remove(); -}; +window.downloadFromUrl = (options) => { + const anchorElement = document.createElement('a') + anchorElement.href = options.url + anchorElement.download = options.fileName ?? '' + anchorElement.click() + anchorElement.remove() +} window.getPolygonImage = (sourceCanvas, path) => { - const canvas = document.createElement('canvas'); - const context = canvas.getContext('2d'); - const width = sourceCanvas.width, - height = sourceCanvas.height; - - canvas.width = width; - canvas.height = height; - context.imageSmoothingEnabled = true; - - context.beginPath(); - context.moveTo(path[0] * width / 100, path[1] * height / 100); - context.fillStyle = "rgba(255, 255, 255, 0)"; - - for (let i = 2; i < path.length; i += 2) { - context.lineTo(path[i] * width / 100, path[i + 1] * height / 100); - } - - context.closePath(); - context.clip(); - context.fill(); - context.globalCompositeOperation = 'lighter'; - context.drawImage(sourceCanvas, 0, 0, width, height); - - return canvas.toDataURL("image/png", 1); + const canvas = document.createElement('canvas') + const context = canvas.getContext('2d') + const width = sourceCanvas.width + const height = sourceCanvas.height + + canvas.width = width + canvas.height = height + context.imageSmoothingEnabled = true + + context.beginPath() + context.moveTo(path[0] * width / 100, path[1] * height / 100) + context.fillStyle = 'rgba(255, 255, 255, 0)' + + for (let i = 2; i < path.length; i += 2) { + context.lineTo(path[i] * width / 100, path[i + 1] * height / 100) + } + + context.closePath() + context.clip() + context.fill() + context.globalCompositeOperation = 'lighter' + context.drawImage(sourceCanvas, 0, 0, width, height) + + return canvas.toDataURL('image/png', 1) } window.getEllipseImage = (sourceCanvas) => { - const createdCanvas = document.createElement('canvas'); - const contextCanvas = createdCanvas.getContext('2d'); - const widthCanvas = sourceCanvas.width, - heightCanvas = sourceCanvas.height; - - createdCanvas.width = widthCanvas; - createdCanvas.height = heightCanvas; - contextCanvas.imageSmoothingEnabled = true; - - contextCanvas.drawImage(sourceCanvas, 0, 0, widthCanvas, heightCanvas); - contextCanvas.globalCompositeOperation = 'destination-in'; - contextCanvas.beginPath(); - contextCanvas.ellipse(widthCanvas / 2, heightCanvas / 2, widthCanvas / 2, heightCanvas / 2, 0 * Math.PI, 0, 180 * Math.PI, true); - contextCanvas.fill(); - - return createdCanvas.toDataURL("image/png", 1); + const createdCanvas = document.createElement('canvas') + const contextCanvas = createdCanvas.getContext('2d') + const widthCanvas = sourceCanvas.width + const heightCanvas = sourceCanvas.height + + createdCanvas.width = widthCanvas + createdCanvas.height = heightCanvas + contextCanvas.imageSmoothingEnabled = true + + contextCanvas.drawImage(sourceCanvas, 0, 0, widthCanvas, heightCanvas) + contextCanvas.globalCompositeOperation = 'destination-in' + contextCanvas.beginPath() + contextCanvas.ellipse(widthCanvas / 2, heightCanvas / 2, widthCanvas / 2, heightCanvas / 2, 0 * Math.PI, 0, 180 * Math.PI, true) + contextCanvas.fill() + + return createdCanvas.toDataURL('image/png', 1) } diff --git a/src/Cropper.Blazor/Client/wwwroot/index.html b/src/Cropper.Blazor/Client/wwwroot/index.html index c28e845c..2cf83604 100644 --- a/src/Cropper.Blazor/Client/wwwroot/index.html +++ b/src/Cropper.Blazor/Client/wwwroot/index.html @@ -2,27 +2,27 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + @@ -38,40 +38,40 @@
-
- An unhandled error has occurred. - Reload - 🗙 -
- - - - - - - - - +
+ An unhandled error has occurred. + Reload + 🗙 +
+ + + + + + + + + diff --git a/src/Cropper.Blazor/Client/wwwroot/jsObjectModule.js b/src/Cropper.Blazor/Client/wwwroot/jsObjectModule.js index 34e44839..f2fba990 100644 --- a/src/Cropper.Blazor/Client/wwwroot/jsObjectModule.js +++ b/src/Cropper.Blazor/Client/wwwroot/jsObjectModule.js @@ -1,33 +1,32 @@ -class JsObject { - getPropertyList(path) { - let res = path.replace('[', '.').replace(']', '').split('.'); +class JsObject { + getPropertyList (path) { + const res = path.replace('[', '.').replace(']', '').split('.') - if (res[0] === "") { // if we pass "[0].id" we want to return [0,'id'] - res.shift(); - } - - return res; + if (res[0] === '') { // if we pass "[0].id" we want to return [0,'id'] + res.shift() } - getInstanceProperty(instance, propertyPath) { - - if (propertyPath === '') { - return instance; - } + return res + } - let currentProperty = instance; - let splitProperty = this.getPropertyList(propertyPath); + getInstanceProperty (instance, propertyPath) { + if (propertyPath === '') { + return instance + } - for (let i = 0; i < splitProperty.length; i++) { - if (splitProperty[i] in currentProperty) { - currentProperty = currentProperty[splitProperty[i]]; - } else { - return null; - } - } + let currentProperty = instance + const splitProperty = this.getPropertyList(propertyPath) - return currentProperty; + for (let i = 0; i < splitProperty.length; i++) { + if (splitProperty[i] in currentProperty) { + currentProperty = currentProperty[splitProperty[i]] + } else { + return null + } } + + return currentProperty + } } -window.jsObject = new JsObject(); +window.jsObject = new JsObject() diff --git a/src/Cropper.Blazor/Client/wwwroot/overrideCropperJsInteropModule.js b/src/Cropper.Blazor/Client/wwwroot/overrideCropperJsInteropModule.js index 087682ac..c3fd2d26 100644 --- a/src/Cropper.Blazor/Client/wwwroot/overrideCropperJsInteropModule.js +++ b/src/Cropper.Blazor/Client/wwwroot/overrideCropperJsInteropModule.js @@ -1,15 +1,14 @@ -window.overrideOnZoomCropperEvent = (minZoomRatio, maxZoomRatio) => { - window.cropper.onZoom = function (imageObject, event, correlationId) { - const jSEventData = this.getJSEventData(event, correlationId); - - const isApplyPreventZoomMinRatio = (minZoomRatio != null) && (minZoomRatio > event.detail.ratio); - const isApplyPreventZoomMaxRatio = (maxZoomRatio != null) && (event.detail.ratio > maxZoomRatio); +window.overrideOnZoomCropperEvent = (minZoomRatio, maxZoomRatio) => { + window.cropper.onZoom = function (imageObject, event, correlationId) { + const jSEventData = this.getJSEventData(event, correlationId) - if (isApplyPreventZoomMinRatio || isApplyPreventZoomMaxRatio) { - event.preventDefault(); - } - else { - imageObject.invokeMethodAsync('CropperIsZoomed', jSEventData); - } - }; -}; \ No newline at end of file + const isApplyPreventZoomMinRatio = (minZoomRatio != null) && (minZoomRatio > event.detail.ratio) + const isApplyPreventZoomMaxRatio = (maxZoomRatio != null) && (event.detail.ratio > maxZoomRatio) + + if (isApplyPreventZoomMinRatio || isApplyPreventZoomMaxRatio) { + event.preventDefault() + } else { + imageObject.invokeMethodAsync('CropperIsZoomed', jSEventData) + } + } +} diff --git a/src/Cropper.Blazor/Client/wwwroot/resizeWindowEventListener.js b/src/Cropper.Blazor/Client/wwwroot/resizeWindowEventListener.js index b81ddcd1..5adb49b3 100644 --- a/src/Cropper.Blazor/Client/wwwroot/resizeWindowEventListener.js +++ b/src/Cropper.Blazor/Client/wwwroot/resizeWindowEventListener.js @@ -1,19 +1,19 @@ let timer window.addEventListener('resize', () => { - if (!Object.hasOwn(this, 'cropper') || cropper == null || cropper.cropperInstances == null) { - return; - } - let keys = Object.keys(cropper.cropperInstances); - clearTimeout(timer); - if (keys.length > 0) { - keys.forEach((key) => { - cropper.cropperInstances[key].disable(); - }); - timer = setTimeout(() => { - let keys = Object.keys(cropper.cropperInstances); - keys.forEach((key) => { - cropper.cropperInstances[key].enable(); - }); - }, 100); - } -}) \ No newline at end of file + if (!Object.hasOwn(this, 'cropper') || cropper == null || cropper.cropperInstances == null) { // eslint-disable-line no-undef + return + } + const keys = Object.keys(cropper.cropperInstances) // eslint-disable-line no-undef + clearTimeout(timer) + if (keys.length > 0) { + keys.forEach((key) => { + cropper.cropperInstances[key].disable() // eslint-disable-line no-undef + }) + timer = setTimeout(() => { + const keys = Object.keys(cropper.cropperInstances) // eslint-disable-line no-undef + keys.forEach((key) => { + cropper.cropperInstances[key].enable() // eslint-disable-line no-undef + }) + }, 100) + } +}) diff --git a/src/Cropper.Blazor/Client/wwwroot/service-worker.js b/src/Cropper.Blazor/Client/wwwroot/service-worker.js index 4882765c..68c0fc3a 100644 --- a/src/Cropper.Blazor/Client/wwwroot/service-worker.js +++ b/src/Cropper.Blazor/Client/wwwroot/service-worker.js @@ -2,4 +2,4 @@ // This is because caching would make development more difficult (changes would not // be reflected on the first load after each change). self.addEventListener('fetch', () => { -}); +}) diff --git a/src/Cropper.Blazor/Client/wwwroot/service-worker.published.js b/src/Cropper.Blazor/Client/wwwroot/service-worker.published.js index 241dae9e..ccf70192 100644 --- a/src/Cropper.Blazor/Client/wwwroot/service-worker.published.js +++ b/src/Cropper.Blazor/Client/wwwroot/service-worker.published.js @@ -1,75 +1,75 @@ // Caution! Be sure you understand the caveats before publishing an application with // offline support. See https://aka.ms/blazor-offline-considerations -self.importScripts('./service-worker-assets.js'); +self.importScripts('./service-worker-assets.js') self.addEventListener('install', event => { - event.waitUntil( - Promise.all([ - onInstall(), - self.skipWaiting(), - ]) - ); -}); + event.waitUntil( + Promise.all([ + onInstall(), + self.skipWaiting() + ]) + ) +}) self.addEventListener('activate', event => { - event.waitUntil( - Promise.all( - [ - onActivate(), - self.clients.claim(), - self.skipWaiting(), - ] - ) - .catch( - (err) => { - event.skipWaiting(); - } - ) - ); -}); -self.addEventListener('fetch', event => event.respondWith(onFetch(event))); + event.waitUntil( + Promise.all( + [ + onActivate(), + self.clients.claim(), + self.skipWaiting() + ] + ) + .catch( + (err) => { // eslint-disable-line + event.skipWaiting() + } + ) + ) +}) +self.addEventListener('fetch', event => event.respondWith(onFetch(event))) -const cacheNamePrefix = 'offline-cache-'; -const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`; -const offlineAssetsInclude = [/\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/]; -const offlineAssetsExclude = [/^service-worker\.js$/]; +const cacheNamePrefix = 'offline-cache-' +const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}` +const offlineAssetsInclude = [/\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/] +const offlineAssetsExclude = [/^service-worker\.js$/] -async function onInstall(event) { - console.info('Service worker: Install'); +async function onInstall (event) { + console.info('Service worker: Install') - // Fetch and cache all matching items from the assets manifest - const assetsRequests = self.assetsManifest.assets - .filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url))) - .filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url))) - .map(asset => new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' })); + // Fetch and cache all matching items from the assets manifest + const assetsRequests = self.assetsManifest.assets + .filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url))) + .filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url))) + .map(asset => new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' })) - await caches.open(cacheName) - .then(cache => cache.addAll(assetsRequests)) - .then(() => { - return self.skipWaiting(); - }); + await caches.open(cacheName) + .then(cache => cache.addAll(assetsRequests)) + .then(() => { + return self.skipWaiting() + }) } -async function onActivate(event) { - console.info('Service worker: Activate'); +async function onActivate (event) { + console.info('Service worker: Activate') - // Delete unused caches - const cacheKeys = await caches.keys(); - await Promise.all(cacheKeys - .filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName) - .map(key => caches.delete(key))); + // Delete unused caches + const cacheKeys = await caches.keys() + await Promise.all(cacheKeys + .filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName) + .map(key => caches.delete(key))) } -async function onFetch(event) { - let cachedResponse = null; - if (event.request.method === 'GET') { - // For all navigation requests, try to serve index.html from cache - // If you need some URLs to be server-rendered, edit the following check to exclude those URLs - const shouldServeIndexHtml = event.request.mode === 'navigate'; +async function onFetch (event) { + let cachedResponse = null + if (event.request.method === 'GET') { + // For all navigation requests, try to serve index.html from cache + // If you need some URLs to be server-rendered, edit the following check to exclude those URLs + const shouldServeIndexHtml = event.request.mode === 'navigate' - const request = shouldServeIndexHtml ? 'index.html' : event.request; - const cache = await caches.open(cacheName); - cachedResponse = await cache.match(request); - } + const request = shouldServeIndexHtml ? 'index.html' : event.request + const cache = await caches.open(cacheName) + cachedResponse = await cache.match(request) + } - return cachedResponse || fetch(event.request); + return cachedResponse || fetch(event.request) } diff --git a/src/Cropper.Blazor/Client/wwwroot/sw-registrator.js b/src/Cropper.Blazor/Client/wwwroot/sw-registrator.js index c4ec0c18..7dc64239 100644 --- a/src/Cropper.Blazor/Client/wwwroot/sw-registrator.js +++ b/src/Cropper.Blazor/Client/wwwroot/sw-registrator.js @@ -1,30 +1,30 @@ window.updateAvailable = new Promise((resolve, reject) => { - if (!('serviceWorker' in navigator)) { - const errorMessage = `This browser doesn't support service workers`; - console.error(errorMessage); - reject(errorMessage); - return; - } + if (!('serviceWorker' in navigator)) { + const errorMessage = 'This browser doesn\'t support service workers' + console.error(errorMessage) + reject(errorMessage) + return + } - navigator.serviceWorker.register('/service-worker.min.js') - .then(registration => { - console.info(`Service worker registration successful (scope: ${registration.scope})`); + navigator.serviceWorker.register('/service-worker.min.js') + .then(registration => { + console.info(`Service worker registration successful (scope: ${registration.scope})`) - setInterval(() => { - registration.update(); - }, 60 * 1000); // 60000ms -> check each minute + setInterval(() => { + registration.update() + }, 60 * 1000) // 60000ms -> check each minute - registration.onupdatefound = () => { - const installingServiceWorker = registration.installing; - installingServiceWorker.onstatechange = () => { - if (installingServiceWorker.state === 'installed') { - resolve(!!navigator.serviceWorker.controller); - } - } - }; - }) - .catch(error => { - console.error('Service worker registration failed with error:', error); - reject(error); - }); -}); + registration.onupdatefound = () => { + const installingServiceWorker = registration.installing + installingServiceWorker.onstatechange = () => { + if (installingServiceWorker.state === 'installed') { + resolve(!!navigator.serviceWorker.controller) + } + } + } + }) + .catch(error => { + console.error('Service worker registration failed with error:', error) + reject(error) + }) +}) diff --git a/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/CodeSnippetsCompiler.cs b/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/CodeSnippetsCompiler.cs index ebc63027..df8d8faf 100644 --- a/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/CodeSnippetsCompiler.cs +++ b/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/CodeSnippetsCompiler.cs @@ -10,14 +10,14 @@ public static partial class CodeSnippetsCompiler { public static bool Execute() { - var paths = new Paths(); + var success = true; try { var currentCode = string.Empty; - if (File.Exists(paths.SnippetsFilePath)) + if (File.Exists(Paths.SnippetsFilePath)) { - currentCode = File.ReadAllText(paths.SnippetsFilePath); + currentCode = File.ReadAllText(Paths.SnippetsFilePath); } var cb = new CodeBuilder(); @@ -29,7 +29,7 @@ public static bool Execute() cb.AddLine("{"); cb.IndentLevel++; - foreach (var entry in Directory.EnumerateFiles(paths.DocsDirPath, "*.razor", SearchOption.AllDirectories) + foreach (var entry in Directory.EnumerateFiles(Paths.DocsDirPath, "*.razor", SearchOption.AllDirectories) .OrderBy(e => e.Replace("\\", "/"), StringComparer.Ordinal)) { var filename = Path.GetFileName(entry); @@ -46,12 +46,12 @@ public static bool Execute() if (currentCode != cb.ToString()) { - File.WriteAllText(paths.SnippetsFilePath, cb.ToString()); + File.WriteAllText(Paths.SnippetsFilePath, cb.ToString()); } } catch (Exception e) { - Console.WriteLine($"Error generating {paths.SnippetsFilePath} : {e.Message}"); + Console.WriteLine($"Error generating {Paths.SnippetsFilePath} : {e.Message}"); success = false; } diff --git a/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/Cropper.Blazor.Client.Compiler.csproj b/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/Cropper.Blazor.Client.Compiler.csproj index 73941058..df03a916 100644 --- a/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/Cropper.Blazor.Client.Compiler.csproj +++ b/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/Cropper.Blazor.Client.Compiler.csproj @@ -6,11 +6,12 @@ - + + + + + + - - - - diff --git a/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/DocStrings.cs b/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/DocStrings.cs index 4e0accee..e5d57f88 100644 --- a/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/DocStrings.cs +++ b/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/DocStrings.cs @@ -15,14 +15,13 @@ public partial class DocStrings public bool Execute() { - var paths = new Paths(); var success = true; try { var currentCode = string.Empty; - if (File.Exists(paths.DocStringsFilePath)) + if (File.Exists(Paths.DocStringsFilePath)) { - currentCode = File.ReadAllText(paths.DocStringsFilePath); + currentCode = File.ReadAllText(Paths.DocStringsFilePath); } var cb = new CodeBuilder(); @@ -110,12 +109,12 @@ public bool Execute() if (currentCode != cb.ToString()) { - File.WriteAllText(paths.DocStringsFilePath, cb.ToString()); + File.WriteAllText(Paths.DocStringsFilePath, cb.ToString()); } } catch (Exception e) { - Console.WriteLine($"Error generating {paths.DocStringsFilePath} : {e.Message}"); + Console.WriteLine($"Error generating {Paths.DocStringsFilePath} : {e.Message}"); success = false; } @@ -160,6 +159,16 @@ private static string ConvertCrefToHTML(string markdownText) { return $"{value}"; } + else if (result.Contains("Cropper.Blazor.Models.CropperComponentType")) + { + (string enumName, string enumItemName) = result.RemoveNamespaceFromEnumValue(); + + return $"{enumName}.{enumItemName}"; + } + else if (result.EndsWith("Cropper.Blazor.Components.CropperComponent.IsErrorLoadImage")) + { + return value; + } return $"{value}"; }); @@ -177,6 +186,7 @@ private static string ConvertSeeTagsForMethod(string doc, string formattedReturn .Replace("", "DotNetStreamReference") .Replace("", "ValueTask") .Replace("", $"{formattedReturnSignature}") + .Replace("", $"{formattedReturnSignature}") .Replace("", "JSEventData<>") .Replace("", "CancellationToken"); diff --git a/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/ExamplesMarkup.cs b/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/ExamplesMarkup.cs index ade74122..0fb25302 100644 --- a/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/ExamplesMarkup.cs +++ b/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/ExamplesMarkup.cs @@ -3,14 +3,14 @@ using System.Text; using System.Text.RegularExpressions; using ColorCode; +using ColorCode.Styling; namespace Cropper.Blazor.Client.Compiler { - public class ExamplesMarkup + public partial class ExamplesMarkup { public bool Execute() { - var paths = new Paths(); var newFiles = new StringBuilder(); var success = true; var noOfFilesUpdated = 0; @@ -18,15 +18,15 @@ public bool Execute() try { - var formatter = new HtmlClassFormatter(); + var formatter = new HtmlClassFormatter(StyleDictionaryKebabCase.KebabCase); var lastCheckedTime = new DateTime(); - if (File.Exists(paths.NewFilesToBuildPath)) + if (File.Exists(Paths.NewFilesToBuildPath)) { - var lastNewFilesToBuild = new FileInfo(paths.NewFilesToBuildPath); + var lastNewFilesToBuild = new FileInfo(Paths.NewFilesToBuildPath); lastCheckedTime = lastNewFilesToBuild.LastWriteTime; } - var directoryInfo = new DirectoryInfo(paths.DocsDirPath); + var directoryInfo = new DirectoryInfo(Paths.DocsDirPath); foreach (var entry in directoryInfo.GetFiles("*.razor", SearchOption.AllDirectories)) { @@ -50,13 +50,13 @@ public bool Execute() var src = StripComponentSource(entry.FullName); var blocks = src.Split("@code"); - var blocks0 = Regex.Replace(blocks[0], @"", string.Empty) + var blocks0 = DocsFrameEndTagRegularExpression().Replace(blocks[0], string.Empty) .Replace("@", "PlaceholdeR") .Trim(); // Note: the @ creates problems and thus we replace it with an unlikely placeholder and in the markup replace back. var html = formatter.GetHtmlString(blocks0, Languages.Html).Replace("PlaceholdeR", "@"); - html = AttributePostprocessing(html).Replace("@", "@"); + html = AttributePostprocessing(html).Replace("@", "@"); var currentCode = string.Empty; if (File.Exists(markupPath)) @@ -73,7 +73,7 @@ public bool Execute() { cb.AddLine( formatter.GetHtmlString("@code" + blocks[1], Languages.CSharp) - .Replace("@", "@") + .Replace("@", "@") .ToLfLineEndings()); } @@ -94,7 +94,7 @@ public bool Execute() } } - File.WriteAllText(paths.NewFilesToBuildPath, newFiles.ToString()); + File.WriteAllText(Paths.NewFilesToBuildPath, newFiles.ToString()); } catch (Exception e) { @@ -110,21 +110,18 @@ public bool Execute() private static string StripComponentSource(string path) { var source = File.ReadAllText(path, Encoding.UTF8); - source = Regex.Replace(source, "@(namespace|layout|page) .+?\n", string.Empty); + source = NamespaceLayoutOrPageRegularExpression().Replace(source, string.Empty); return source.Trim(); } public static string AttributePostprocessing(string html) { - return Regex.Replace( - html, - @""(?'value'.*?)"", - new MatchEvaluator(m => - { - var value = m.Groups["value"].Value; - return - $@""{AttributeValuePostprocessing(value)}""; - })); + return HtmlAttributeValueSpanRegularExpression().Replace(html, new MatchEvaluator(m => + { + var value = m.Groups["value"].Value; + return + $@""{AttributeValuePostprocessing(value)}""; + })); } private static string AttributeValuePostprocessing(string value) @@ -133,18 +130,33 @@ private static string AttributeValuePostprocessing(string value) return value; if (value is "true" or "false") return $"{value}"; - if (Regex.IsMatch(value, "^[A-Z][A-Za-z0-9]+[.][A-Za-z][A-Za-z0-9]+$")) + if (AlphanumericDotAlphanumericRegularExpression().IsMatch(value)) { var tokens = value.Split('.'); - return $"{tokens[0]}.{tokens[1]}"; + return $"{tokens[0]}.{tokens[1]}"; } - if (Regex.IsMatch(value, "^@[A-Za-z0-9]+$")) + if (AlphanumericRegularExpression().IsMatch(value)) { - return $"{value}"; + return $"{value}"; } - return $"{value}"; + return $"{value}"; } + + [GeneratedRegex(@"")] + private static partial Regex DocsFrameEndTagRegularExpression(); + + [GeneratedRegex("@(namespace|layout|page) .+?\n")] + private static partial Regex NamespaceLayoutOrPageRegularExpression(); + + [GeneratedRegex(@""(?'value'.*?)"")] + private static partial Regex HtmlAttributeValueSpanRegularExpression(); + + [GeneratedRegex("^[A-Z][A-Za-z0-9]+[.][A-Za-z][A-Za-z0-9]+$")] + private static partial Regex AlphanumericDotAlphanumericRegularExpression(); + + [GeneratedRegex("^@[A-Za-z0-9]+$")] + private static partial Regex AlphanumericRegularExpression(); } } diff --git a/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/Paths.cs b/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/Paths.cs index 6da8f118..168e6dba 100644 --- a/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/Paths.cs +++ b/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/Paths.cs @@ -3,7 +3,7 @@ namespace Cropper.Blazor.Client.Compiler; -public class Paths +public static class Paths { private const string DocsDirectory = "Client"; private const string DocStringsFile = "DocStrings.generated.cs"; @@ -27,43 +27,13 @@ public static string SrcDirPath } } - public string DocsDirPath - { - get - { - return Directory.EnumerateDirectories(SrcDirPath, DocsDirectory).FirstOrDefault(); - } - } + public static string DocsDirPath => Directory.EnumerateDirectories(SrcDirPath, DocsDirectory).FirstOrDefault(); - public string DocsStringSnippetsDirPath - { - get - { - return Path.Join(DocsDirPath, "Models"); - } - } + public static string DocsStringSnippetsDirPath => Path.Join(DocsDirPath, "Models"); - public string SnippetsFilePath - { - get - { - return Path.Join(DocsStringSnippetsDirPath, SnippetsFile); - } - } + public static string SnippetsFilePath => Path.Join(DocsStringSnippetsDirPath, SnippetsFile); - public string DocStringsFilePath - { - get - { - return Path.Join(DocsStringSnippetsDirPath, DocStringsFile); - } - } + public static string DocStringsFilePath => Path.Join(DocsStringSnippetsDirPath, DocStringsFile); - public string NewFilesToBuildPath - { - get - { - return Path.Join(DocsDirPath, NewFilesToBuild); - } - } + public static string NewFilesToBuildPath => Path.Join(DocsDirPath, NewFilesToBuild); } diff --git a/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/Program.cs b/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/Program.cs index 2d20b197..9a6e50ac 100644 --- a/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/Program.cs +++ b/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/Program.cs @@ -9,9 +9,9 @@ public static int Main() { var stopWatch = Stopwatch.StartNew(); var success = - CodeSnippetsCompiler.Execute() - && new ExamplesMarkup().Execute() - && new DocStrings().Execute(); + CodeSnippetsCompiler.Execute() + && new ExamplesMarkup().Execute() + && new DocStrings().Execute(); Console.WriteLine($"Docs.Compiler completed in {stopWatch.ElapsedMilliseconds} msecs"); return success ? 0 : 1; diff --git a/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/StyleDictionary.KebabCase.cs b/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/StyleDictionary.KebabCase.cs new file mode 100644 index 00000000..72dbbfae --- /dev/null +++ b/src/Cropper.Blazor/Cropper.Blazor.Client.Compiler/StyleDictionary.KebabCase.cs @@ -0,0 +1,53 @@ +using ColorCode.Common; +using ColorCode.Styling; + +namespace Cropper.Blazor.Client.Compiler +{ + public class StyleDictionaryKebabCase + { + /// + /// A theme with reference names in kebab-case style. + /// + public static StyleDictionary KebabCase + { + get + { + return new StyleDictionary + { + new Style(ScopeName.String) + { + ReferenceName = "string" + }, + new Style(ScopeName.Keyword) + { + ReferenceName = "keyword" + }, + new Style(ScopeName.HtmlElementName) + { + ReferenceName = "html-element-name" + }, + new Style(ScopeName.HtmlAttributeName) + { + ReferenceName = "html-attribute-name" + }, + new Style(ScopeName.HtmlAttributeValue) + { + ReferenceName = "html-attribute-value" + }, + new Style(ScopeName.HtmlOperator) + { + ReferenceName = "html-operator" + }, + new Style(ScopeName.Comment) + { + ReferenceName = "comment" + }, + new Style(ScopeName.HtmlTagDelimiter) + { + ReferenceName = "html-tag-delimiter" + } + }; + } + } + } +} diff --git a/src/Cropper.Blazor/Cropper.Blazor.Shared/Cropper.Blazor.Shared.csproj b/src/Cropper.Blazor/Cropper.Blazor.Shared/Cropper.Blazor.Shared.csproj index 77fe107c..2d0af417 100644 --- a/src/Cropper.Blazor/Cropper.Blazor.Shared/Cropper.Blazor.Shared.csproj +++ b/src/Cropper.Blazor/Cropper.Blazor.Shared/Cropper.Blazor.Shared.csproj @@ -1,13 +1,13 @@  - - net8.0 - enable - enable - + + net8.0 + enable + enable + - - - + + + diff --git a/src/Cropper.Blazor/Cropper.Blazor.Shared/Extensions/MethodInfoExtensions.cs b/src/Cropper.Blazor/Cropper.Blazor.Shared/Extensions/MethodInfoExtensions.cs index 2cf4fba3..efbe0cfa 100644 --- a/src/Cropper.Blazor/Cropper.Blazor.Shared/Extensions/MethodInfoExtensions.cs +++ b/src/Cropper.Blazor/Cropper.Blazor.Shared/Extensions/MethodInfoExtensions.cs @@ -262,12 +262,21 @@ public static string TypeName(this Type type, Func? GenericArgum stringBuilder.Append('>'); // Return result - return stringBuilder.ToString().RemoveNamespace(); + return stringBuilder.ToString(); } public static string RemoveNamespace(this string value) { - return value.Split('.')[value.Split('.').Length - 1]; + var splittedValue = value.Split('.'); + return splittedValue[splittedValue.Length - 1]; + } + + public static (string enumName, string enumItemName) RemoveNamespaceFromEnumValue(this string value) + { + var splittedValue = value.Split('.'); + var lengthValue = splittedValue.Length; + + return (splittedValue[lengthValue - 2], splittedValue[lengthValue - 1]); } public static string GetFormattedReturnSignature(this MethodInfo method, bool callable = false) @@ -285,7 +294,7 @@ public static string GetFormattedReturnSignature(this Type type, bool callable = if (callable == false) { // Append return type - stringBuilder.Append(type.TypeName(CreateLink).RemoveNamespace()); + stringBuilder.Append(type.TypeName(CreateLink)); stringBuilder.Append(' '); } @@ -305,7 +314,15 @@ public static string CreateLink(this string name) } else if (name == "ErrorEventArgs") { - return $"{name}"; + return $"{name}"; + } + else if (name == "RenderFragment") + { + return $"{name}"; + } + else if (name == "IJSObjectReference") + { + return $"{name}"; } else { diff --git a/src/Cropper.Blazor/Cropper.Blazor.Shared/Extensions/TypeNameHelper.cs b/src/Cropper.Blazor/Cropper.Blazor.Shared/Extensions/TypeNameHelper.cs index fd62fd37..1477b9bd 100644 --- a/src/Cropper.Blazor/Cropper.Blazor.Shared/Extensions/TypeNameHelper.cs +++ b/src/Cropper.Blazor/Cropper.Blazor.Shared/Extensions/TypeNameHelper.cs @@ -100,7 +100,7 @@ private static void ProcessType(StringBuilder builder, Type type, DisplayNameOpt builder.Append(type.Name); } else if (type.Assembly.ManifestModule.Name == "FSharp.Core.dll" - && FSharpTypeNames.TryGetValue(type.Name, out builtInName)) + && FSharpTypeNames.TryGetValue(type.Name, out builtInName)) { builder.Append(builtInName); } @@ -173,7 +173,7 @@ private static void ProcessGenericType(StringBuilder builder, Type type, Type[] } if (type.Assembly.ManifestModule.Name == "FSharp.Core.dll" - && FSharpTypeNames.TryGetValue(type.Name, out var builtInName)) + && FSharpTypeNames.TryGetValue(type.Name, out var builtInName)) { builder.Append(builtInName); } diff --git a/src/Cropper.Blazor/Cropper.Blazor.Shared/Extensions/XmlDocumentationExtension.cs b/src/Cropper.Blazor/Cropper.Blazor.Shared/Extensions/XmlDocumentationExtension.cs index 80c9bd03..43834027 100644 --- a/src/Cropper.Blazor/Cropper.Blazor.Shared/Extensions/XmlDocumentationExtension.cs +++ b/src/Cropper.Blazor/Cropper.Blazor.Shared/Extensions/XmlDocumentationExtension.cs @@ -67,9 +67,9 @@ string ConvertToCsharpSource(Type type) } var correctGeneric = genericParameters.Dequeue(); result += (firstIteration ? string.Empty : ",") + - (correctGeneric.IsGenericParameter - ? showGenericParameters ? (firstIteration ? string.Empty : " ") + correctGeneric.Name : string.Empty - : (firstIteration ? string.Empty : " ") + correctGeneric.ConvertToCSharpSource()); + (correctGeneric.IsGenericParameter + ? showGenericParameters ? (firstIteration ? string.Empty : " ") + correctGeneric.Name : string.Empty + : (firstIteration ? string.Empty : " ") + correctGeneric.ConvertToCSharpSource()); firstIteration = false; } result += ">"; diff --git a/src/Cropper.Blazor/Cropper.Blazor.Sitemap.Generator/Cropper.Blazor.Sitemap.Generator.csproj b/src/Cropper.Blazor/Cropper.Blazor.Sitemap.Generator/Cropper.Blazor.Sitemap.Generator.csproj index 7b5b20bc..1cc87412 100644 --- a/src/Cropper.Blazor/Cropper.Blazor.Sitemap.Generator/Cropper.Blazor.Sitemap.Generator.csproj +++ b/src/Cropper.Blazor/Cropper.Blazor.Sitemap.Generator/Cropper.Blazor.Sitemap.Generator.csproj @@ -1,14 +1,14 @@  - - net8.0 - enable - enable - Exe - + + net8.0 + enable + enable + Exe + - - - + + + diff --git a/src/Cropper.Blazor/Cropper.Blazor.UnitTests/Components/CropperComponent_Should.cs b/src/Cropper.Blazor/Cropper.Blazor.UnitTests/Components/CropperComponent_Should.cs index ce10fbb0..ad019808 100644 --- a/src/Cropper.Blazor/Cropper.Blazor.UnitTests/Components/CropperComponent_Should.cs +++ b/src/Cropper.Blazor/Cropper.Blazor.UnitTests/Components/CropperComponent_Should.cs @@ -28,6 +28,7 @@ using Moq; using Xunit; using ErrorEventArgs = Microsoft.AspNetCore.Components.Web.ErrorEventArgs; +using Options = Cropper.Blazor.Models.Options; namespace Cropper.Blazor.UnitTests.Components { @@ -129,7 +130,7 @@ private async Task Should_DisposeAsync_CropperComponent_After_Render_Async() } [Fact] - public async Task Should_Render_CropperComponent_SuccessfulAsync() + public async Task Should_Render_CropperComponent_From_Image_SuccessfulAsync() { // arrange string errorLoadImageClass = "cropper-error-load"; @@ -363,24 +364,24 @@ public async Task Should_Render_CropperComponent_SuccessfulAsync() // assert IElement expectedElement = cropperComponent.Find($"img.{imageClass}"); - ElementReference elementReference = (ElementReference)cropperComponent.Instance - .GetInstanceField("ImageReference"); + ElementReference? elementReference = cropperComponent.Instance + .GetCropperElementReference(); Guid cropperComponentId = (Guid)cropperComponent.Instance .GetInstanceField("CropperComponentId"); _mockCropperJsInterop.Verify(c => c.LoadModuleAsync(cancellationToken), Times.Once()); - elementReference.Id.Should().NotBeNullOrEmpty(); + elementReference!.Value.Id.Should().NotBeNullOrEmpty(); expectedElement.ClassName.Should().Be(imageClass); expectedElement.GetAttribute("loading").Should().Be(lazyAttributeValue); expectedElement.GetAttribute("src").Should().Be(imageSrcAttributeValue); - expectedElement.GetAttribute("blazor:elementreference").Should().Be(elementReference.Id); + expectedElement.GetAttribute("blazor:elementreference").Should().Be(elementReference!.Value.Id); countCallsOnLoadImageHandler.Should().Be(0); expectedElement.TriggerEvent("onload", progressEventArgs); countCallsOnLoadImageHandler.Should().Be(1); _mockCropperJsInterop.Verify(c => c.InitCropperAsync( cropperComponentId, - elementReference, + elementReference!.Value, options, It.IsAny>(), cancellationToken), Times.Once()); @@ -518,6 +519,369 @@ await cropperComponent.InvokeAsync(async () => }); } + [Fact] + public async Task Should_Render_CropperComponent_From_Canvas_SuccessfulAsync() + { + // arrange + string errorLoadImageClass = "cropper-error-load"; + string imageClass = "cropper"; + string lazyAttributeValue = "lazy"; + Dictionary inputAttributes = new() + { + { "loading", lazyAttributeValue } + }; + string imageSrcAttributeValue = "https://.../image.jpg"; + string errorLoadImageSrcAttributeValue = "https://.../not-found-image.jpg"; + int countCallsOnCropEventHandler = 0; + int countCallsOnCropEndEventHandler = 0; + int countCallsOnCropMoveEventHandler = 0; + int countCallsOnCropStartEventHandler = 0; + int countCallsOnZoomEventHandler = 0; + int countCallsOnCropReadyEventHandler = 0; + + IBrowserFile imageFile = new Mock().Object; + CancellationToken cancellationToken = new(); + JSEventData cropReadyEvent = new Faker>() + .Generate(); + JSEventData zoomEvent = new Faker>() + .Generate(); + JSEventData cropStartEvent = new Faker>() + .Generate(); + JSEventData cropMoveEvent = new Faker>() + .Generate(); + JSEventData cropEndEvent = new Faker>() + .Generate(); + JSEventData cropEvent = new Faker>() + .Generate(); + Options options = new Faker() + .Generate(); + CanvasData expectedCanvasData = new Faker() + .Generate(); + ContainerData expectedContainerData = new Faker() + .Generate(); + CropBoxData expectedCropBoxData = new Faker() + .Generate(); + GetCroppedCanvasOptions getCroppedCanvasOptions = new Faker() + .Generate(); + Mock mockIJSObjectReference = new(); + CroppedCanvas expectedCroppedCanvas = new Faker() + .CustomInstantiator(c => new CroppedCanvas(mockIJSObjectReference.Object)); + CropperData expectedCropperData = new Faker() + .Generate(); + ImageData expectedImageData = new Faker() + .Generate(); + SetCanvasDataOptions setCanvasDataOptions = new Faker() + .Generate(); + SetCropBoxDataOptions setCropBoxDataOptions = new Faker() + .Generate(); + SetDataOptions setDataOptions = new Faker() + .Generate(); + + Faker faker = new(); + string expectedCroppedCanvasDataURL = faker.Random.Word(); + bool isRounded = faker.Random.Bool(); + decimal degree = faker.Random.Decimal(); + long maxAllowedSize = faker.Random.Long(); + string expectedImage = faker.Random.Word(); + decimal offsetX = faker.Random.Decimal(); + decimal? offsetY = faker.Random.Decimal(); + decimal x = faker.Random.Decimal(); + decimal? y = faker.Random.Decimal(); + string url = faker.Random.Word(); + decimal scaleX = faker.Random.Decimal(); + decimal scaleY = faker.Random.Decimal(); + decimal aspectRatio = faker.Random.Decimal(); + DragMode dragMode = faker.Random.Enum(); + decimal ratio = faker.Random.Decimal(); + decimal pivotX = faker.Random.Decimal(); + decimal pivotY = faker.Random.Decimal(); + string newUrlImage = faker.Random.Word(); + bool hasSameSize = faker.Random.Bool(); + string imageFormatType = faker.Random.Word(); + float numberImageQuality = faker.Random.Float(0, 1); + + Action>? onCropEventHandler = (JSEventData c) => + { + countCallsOnCropEventHandler++; + cropEvent.Should().BeEquivalentTo(c); + }; + Action>? onCropEndEventHandler = (JSEventData c) => + { + countCallsOnCropEndEventHandler++; + cropEndEvent.Should().BeEquivalentTo(c); + }; + Action>? onCropMoveEventHandler = (JSEventData c) => + { + countCallsOnCropMoveEventHandler++; + cropMoveEvent.Should().BeEquivalentTo(c); + }; + Action>? onCropStartEventHandler = (JSEventData c) => + { + countCallsOnCropStartEventHandler++; + cropStartEvent.Should().BeEquivalentTo(c); + }; + Action>? onZoomEventHandler = (JSEventData z) => + { + countCallsOnZoomEventHandler++; + zoomEvent.Should().BeEquivalentTo(z); + }; + Action>? onCropReadyEventHandler = (JSEventData c) => + { + countCallsOnCropReadyEventHandler++; + cropReadyEvent.Should().BeEquivalentTo(c); + }; + + ComponentParameter imageClassParameter = ComponentParameter.CreateParameter( + nameof(CropperComponent.Class), + imageClass); + ComponentParameter errorLoadImageClassParameter = ComponentParameter.CreateParameter( + nameof(CropperComponent.ErrorLoadImageClass), + errorLoadImageClass); + ComponentParameter loadingParameter = ComponentParameter.CreateParameter( + nameof(CropperComponent.InputAttributes), + inputAttributes); + ComponentParameter errorLoadImageSrcParameter = ComponentParameter.CreateParameter( + nameof(CropperComponent.ErrorLoadImageSrc), + errorLoadImageSrcAttributeValue); + ComponentParameter srcParameter = ComponentParameter.CreateParameter( + nameof(CropperComponent.Src), + imageSrcAttributeValue); + ComponentParameter isErrorLoadImageParameter = ComponentParameter.CreateParameter( + nameof(CropperComponent.IsErrorLoadImage), + false); + ComponentParameter isAvailableInitCropperParameter = ComponentParameter.CreateParameter( + nameof(CropperComponent.IsAvailableInitCropper), + true); + ComponentParameter optionsParameter = ComponentParameter.CreateParameter( + nameof(CropperComponent.Options), + options); + ComponentParameter onCropEventParameter = ComponentParameter.CreateParameter( + nameof(CropperComponent.OnCropEvent), + onCropEventHandler); + ComponentParameter onCropEndEventParameter = ComponentParameter.CreateParameter( + nameof(CropperComponent.OnCropEndEvent), + onCropEndEventHandler); + ComponentParameter onCropMoveEventParameter = ComponentParameter.CreateParameter( + nameof(CropperComponent.OnCropMoveEvent), + onCropMoveEventHandler); + ComponentParameter onCropStartEventParameter = ComponentParameter.CreateParameter( + nameof(CropperComponent.OnCropStartEvent), + onCropStartEventHandler); + ComponentParameter onZoomEventParameter = ComponentParameter.CreateParameter( + nameof(CropperComponent.OnZoomEvent), + onZoomEventHandler); + ComponentParameter onReadyEventParameter = ComponentParameter.CreateParameter( + nameof(CropperComponent.OnReadyEvent), + onCropReadyEventHandler); + ComponentParameter cropperComponentTypeParameter = ComponentParameter.CreateParameter( + nameof(CropperComponent.CropperComponentType), + CropperComponentType.Canvas); + + _mockCropperJsInterop + .Setup(c => c.GetCanvasDataAsync(It.IsAny(), cancellationToken)) + .ReturnsAsync(expectedCanvasData); + + _mockCropperJsInterop + .Setup(c => c.GetContainerDataAsync(It.IsAny(), cancellationToken)) + .ReturnsAsync(expectedContainerData); + + _mockCropperJsInterop + .Setup(c => c.GetCropBoxDataAsync(It.IsAny(), cancellationToken)) + .ReturnsAsync(expectedCropBoxData); + + _mockCropperJsInterop + .Setup(c => c.GetCroppedCanvasAsync(It.IsAny(), getCroppedCanvasOptions, cancellationToken)) + .ReturnsAsync(expectedCroppedCanvas); + + _mockCropperJsInterop + .Setup(c => c.GetCroppedCanvasDataURLAsync( + It.IsAny(), + getCroppedCanvasOptions, + imageFormatType, + numberImageQuality, + cancellationToken)) + .ReturnsAsync(expectedCroppedCanvasDataURL); + + _mockCropperJsInterop + .Setup(c => c.GetDataAsync(It.IsAny(), isRounded, cancellationToken)) + .ReturnsAsync(expectedCropperData); + + _mockCropperJsInterop + .Setup(c => c.GetImageDataAsync(It.IsAny(), cancellationToken)) + .ReturnsAsync(expectedImageData); + + _mockCropperJsInterop + .Setup(c => c.GetImageUsingStreamingAsync(imageFile, maxAllowedSize, cancellationToken)) + .ReturnsAsync(expectedImage); + + // act + IRenderedComponent cropperComponent = _testContext + .RenderComponent( + errorLoadImageClassParameter, + loadingParameter, + errorLoadImageSrcParameter, + isErrorLoadImageParameter, + isAvailableInitCropperParameter, + srcParameter, + imageClassParameter, + optionsParameter, + onCropEventParameter, + onCropEndEventParameter, + onCropMoveEventParameter, + onCropStartEventParameter, + onZoomEventParameter, + onReadyEventParameter, + cropperComponentTypeParameter); + + // assert + IElement expectedElement = cropperComponent.Find($"canvas.{imageClass}"); + ElementReference? elementReference = cropperComponent.Instance.GetCropperElementReference(); + Guid cropperComponentId = (Guid)cropperComponent.Instance + .GetInstanceField("CropperComponentId"); + + _mockCropperJsInterop.Verify(c => c.LoadModuleAsync(cancellationToken), Times.Once()); + elementReference!.Value.Id.Should().NotBeNullOrEmpty(); + expectedElement.ClassName.Should().Be(imageClass); + expectedElement.GetAttribute("loading").Should().Be(lazyAttributeValue); + expectedElement.GetAttribute("src").Should().Be(null); + expectedElement.GetAttribute("blazor:elementreference").Should().Be(elementReference!.Value.Id); + + _mockCropperJsInterop.Verify(c => c.InitCropperAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny()), Times.Never()); + + expectedElement.HasAttribute("onload").Should().BeFalse(); + expectedElement.HasAttribute("onerror").Should().BeFalse(); + + await cropperComponent.InvokeAsync(async () => + { + cropperComponent.Instance.Clear(); + _mockCropperJsInterop.Verify(c => c.ClearAsync(cropperComponentId, cancellationToken), Times.Once()); + + cropperComponent.Instance.Crop(); + _mockCropperJsInterop.Verify(c => c.CropAsync(cropperComponentId, cancellationToken), Times.Once()); + + cropperComponent.Instance.CropperIsCroped(cropEvent); + countCallsOnCropEventHandler.Should().Be(1); + + cropperComponent.Instance.CropperIsEnded(cropEndEvent); + countCallsOnCropEndEventHandler.Should().Be(1); + + cropperComponent.Instance.CropperIsMoved(cropMoveEvent); + countCallsOnCropMoveEventHandler.Should().Be(1); + + cropperComponent.Instance.CropperIsStarted(cropStartEvent); + countCallsOnCropStartEventHandler.Should().Be(1); + + cropperComponent.Instance.CropperIsZoomed(zoomEvent); + countCallsOnZoomEventHandler.Should().Be(1); + + cropperComponent.Instance.Destroy(); + _mockCropperJsInterop.Verify(c => c.DestroyAsync(cropperComponentId, cancellationToken), Times.Once()); + + cropperComponent.Instance.Disable(); + _mockCropperJsInterop.Verify(c => c.DisableAsync(cropperComponentId, cancellationToken), Times.Once()); + + cropperComponent.Instance.Enable(); + _mockCropperJsInterop.Verify(c => c.EnableAsync(cropperComponentId, cancellationToken), Times.Once()); + + CanvasData canvasData = await cropperComponent.Instance.GetCanvasDataAsync(); + expectedCanvasData.Should().BeEquivalentTo(canvasData); + _mockCropperJsInterop.Verify(c => c.GetCanvasDataAsync(cropperComponentId, cancellationToken), Times.Once()); + + ContainerData containerData = await cropperComponent.Instance.GetContainerDataAsync(); + expectedContainerData.Should().BeEquivalentTo(containerData); + _mockCropperJsInterop.Verify(c => c.GetContainerDataAsync(cropperComponentId, cancellationToken), Times.Once()); + + CropBoxData cropBoxData = await cropperComponent.Instance.GetCropBoxDataAsync(); + expectedCropBoxData.Should().BeEquivalentTo(cropBoxData); + _mockCropperJsInterop.Verify(c => c.GetCropBoxDataAsync(cropperComponentId, cancellationToken), Times.Once()); + + object croppedCanvas = await cropperComponent.Instance.GetCroppedCanvasAsync(getCroppedCanvasOptions); + expectedCroppedCanvas.Should().BeEquivalentTo(croppedCanvas); + _mockCropperJsInterop.Verify(c => c.GetCroppedCanvasAsync(cropperComponentId, getCroppedCanvasOptions, cancellationToken), Times.Once()); + + string croppedCanvasDataURL = await cropperComponent.Instance.GetCroppedCanvasDataURLAsync(getCroppedCanvasOptions, imageFormatType, numberImageQuality); + expectedCroppedCanvasDataURL.Should().BeEquivalentTo(croppedCanvasDataURL); + _mockCropperJsInterop.Verify(c => c.GetCroppedCanvasDataURLAsync(cropperComponentId, getCroppedCanvasOptions, imageFormatType, numberImageQuality, cancellationToken), Times.Once()); + + CropperData cropperData = await cropperComponent.Instance.GetDataAsync(isRounded); + expectedCropperData.Should().BeEquivalentTo(cropperData); + _mockCropperJsInterop.Verify(c => c.GetDataAsync(cropperComponentId, isRounded, cancellationToken), Times.Once()); + + ImageData imageData = await cropperComponent.Instance.GetImageDataAsync(); + expectedImageData.Should().Be(imageData); + _mockCropperJsInterop.Verify(c => c.GetImageDataAsync(cropperComponentId, cancellationToken), Times.Once()); + + await cropperComponent.Instance.ReplaceAsync(newUrlImage, hasSameSize); + _mockCropperJsInterop.Verify(c => c.ReplaceAsync(cropperComponentId, newUrlImage, hasSameSize, cancellationToken), Times.Once()); + cropperComponent.Instance.Src.Should().BeEquivalentTo(newUrlImage); + + string image = await cropperComponent.Instance.GetImageUsingStreamingAsync(imageFile, maxAllowedSize, cancellationToken); + expectedImage.Should().Be(image); + _mockCropperJsInterop.Verify(c => c.GetImageUsingStreamingAsync(imageFile, maxAllowedSize, cancellationToken), Times.Once()); + + cropperComponent.Instance.IsReady(cropReadyEvent); + countCallsOnCropReadyEventHandler.Should().Be(1); + + cropperComponent.Instance.Move(offsetX, offsetY); + _mockCropperJsInterop.Verify(c => c.MoveAsync(cropperComponentId, offsetX, offsetY, cancellationToken), Times.Once()); + + cropperComponent.Instance.MoveTo(x, y); + _mockCropperJsInterop.Verify(c => c.MoveToAsync(cropperComponentId, x, y, cancellationToken), Times.Once()); + + cropperComponent.Instance.Reset(); + _mockCropperJsInterop.Verify(c => c.ResetAsync(cropperComponentId, cancellationToken), Times.Once()); + + await cropperComponent.Instance.RevokeObjectUrlAsync(url); + _mockCropperJsInterop.Verify(c => c.RevokeObjectUrlAsync(url, cancellationToken), Times.Once()); + + cropperComponent.Instance.Rotate(degree); + _mockCropperJsInterop.Verify(c => c.RotateAsync(cropperComponentId, degree, cancellationToken), Times.Once()); + + cropperComponent.Instance.Scale(scaleX, scaleY); + _mockCropperJsInterop.Verify(c => c.ScaleAsync(cropperComponentId, scaleX, scaleY, cancellationToken), Times.Once()); + + cropperComponent.Instance.ScaleX(scaleX); + _mockCropperJsInterop.Verify(c => c.ScaleXAsync(cropperComponentId, scaleX, cancellationToken), Times.Once()); + + cropperComponent.Instance.ScaleY(scaleY); + _mockCropperJsInterop.Verify(c => c.ScaleYAsync(cropperComponentId, scaleY, cancellationToken), Times.Once()); + + cropperComponent.Instance.SetAspectRatio(aspectRatio); + _mockCropperJsInterop.Verify(c => c.SetAspectRatioAsync(cropperComponentId, aspectRatio, cancellationToken), Times.Once()); + + cropperComponent.Instance.SetCanvasData(setCanvasDataOptions); + _mockCropperJsInterop.Verify(c => c.SetCanvasDataAsync(cropperComponentId, setCanvasDataOptions, cancellationToken), Times.Once()); + + cropperComponent.Instance.SetCropBoxData(setCropBoxDataOptions); + _mockCropperJsInterop.Verify(c => c.SetCropBoxDataAsync(cropperComponentId, setCropBoxDataOptions, cancellationToken), Times.Once()); + + cropperComponent.Instance.SetData(setDataOptions); + _mockCropperJsInterop.Verify(c => c.SetDataAsync(cropperComponentId, setDataOptions, cancellationToken), Times.Once()); + + cropperComponent.Instance.SetDragMode(dragMode); + _mockCropperJsInterop.Verify(c => c.SetDragModeAsync(cropperComponentId, dragMode, cancellationToken), Times.Once()); + + cropperComponent.Instance.Zoom(ratio); + _mockCropperJsInterop.Verify(c => c.ZoomAsync(cropperComponentId, ratio, cancellationToken), Times.Once()); + + cropperComponent.Instance.ZoomTo(ratio, pivotX, pivotY); + _mockCropperJsInterop.Verify(c => c.ZoomToAsync(cropperComponentId, ratio, pivotX, pivotY, cancellationToken), Times.Once()); + + await cropperComponent.Instance.DisposeAsync(); + _mockCropperJsInterop.Verify(c => c.DisposeAsync(), Times.Once()); + _mockCropperJsInterop.Verify(c => c.DestroyAsync(cropperComponentId, cancellationToken), Times.Exactly(2)); + + cropperComponent.Instance.Dispose(); + _mockCropperJsInterop.Verify(c => c.DisposeAsync(), Times.Exactly(2)); + _mockCropperJsInterop.Verify(c => c.DestroyAsync(cropperComponentId, cancellationToken), Times.Exactly(3)); + }); + } + [Fact] public void Should_Render_CropperComponent_With_ErrorLoadImage_Parameter() { @@ -555,10 +919,10 @@ public void Should_Render_CropperComponent_With_ErrorLoadImage_Parameter() // assert IElement expectedElement = cropperComponent.Find($"img.{errorLoadImageClass}"); - ElementReference elementReference = (ElementReference)cropperComponent.Instance - .GetInstanceField("ImageReference"); + ElementReference? elementReference = cropperComponent.Instance + .GetCropperElementReference(); - elementReference.Id.Should().BeNullOrEmpty(); + elementReference.Should().BeNull(); expectedElement.ClassName.Should().Be(errorLoadImageClass); expectedElement.GetAttribute("loading").Should().Be(lazyAttributeValue); expectedElement.GetAttribute("src").Should().Be(errorLoadImageSrcAttributeValue); @@ -573,6 +937,56 @@ public void Should_Render_CropperComponent_With_ErrorLoadImage_Parameter() It.IsAny()), Times.Never()); } + [Fact] + public void Should_Render_CropperComponent_With_ErrorLoadImageContent_Parameter() + { + // arrange + RenderFragment errorLoadImageContent = builder => + { + builder.OpenElement(0, "div"); + builder.AddAttribute(1, "class", "error-img"); + builder.CloseElement(); + }; + Dictionary inputAttributes = new() + { + { "loading", "lazy" }, + { "Attribute_TEST", "TEST_VALUE" }, + { "src", "new_src" } + }; + + ComponentParameter errorLoadImageContentParameter = ComponentParameter.CreateParameter( + nameof(CropperComponent.ErrorLoadImageContent), + errorLoadImageContent); + ComponentParameter loadingParameter = ComponentParameter.CreateParameter( + nameof(CropperComponent.InputAttributes), + inputAttributes); + ComponentParameter isErrorLoadImage = ComponentParameter.CreateParameter( + nameof(CropperComponent.IsErrorLoadImage), + true); + + // act + IRenderedComponent cropperComponent = _testContext + .RenderComponent( + errorLoadImageContentParameter, + loadingParameter, + isErrorLoadImage); + + // assert + IElement expectedElement = cropperComponent.Find("div.error-img"); + ElementReference? elementReference = cropperComponent.Instance + .GetCropperElementReference(); + + elementReference.Should().BeNull(); + expectedElement.ClassName.Should().Be("error-img"); + + _mockCropperJsInterop.Verify(c => c.InitCropperAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny()), Times.Never()); + } + [Fact] public void Should_Not_Render_CropperComponent_With_IsNotAvaibleInitCropper_Parameter() { @@ -614,10 +1028,10 @@ public void Should_Not_Render_CropperComponent_With_IsNotAvaibleInitCropper_Para // assert IElement expectedElement = cropperComponent.Find($"img.{errorLoadImageClass}"); - ElementReference elementReference = (ElementReference)cropperComponent.Instance - .GetInstanceField("ImageReference"); + ElementReference? elementReference = cropperComponent.Instance + .GetCropperElementReference(); - elementReference.Id.Should().BeNullOrEmpty(); + elementReference.Should().BeNull(); expectedElement.ClassName.Should().Be(errorLoadImageClass); expectedElement.GetAttribute("loading").Should().Be(lazyAttributeValue); expectedElement.GetAttribute("src").Should().Be(errorLoadImageSrcAttributeValue); @@ -681,16 +1095,16 @@ public void Should_Render_CropperComponent_With_IsNotAvaibleInitCropper_Paramete // assert IElement expectedElement = cropperComponent.Find($"img"); - ElementReference elementReference = (ElementReference)cropperComponent.Instance - .GetInstanceField("ImageReference"); + ElementReference? elementReference = cropperComponent.Instance + .GetCropperElementReference(); Guid cropperComponentId = (Guid)cropperComponent.Instance .GetInstanceField("CropperComponentId"); _mockCropperJsInterop.Verify(c => c.LoadModuleAsync(cancellationToken), Times.Once()); - elementReference.Id.Should().NotBeNullOrEmpty(); + elementReference!.Value.Id.Should().NotBeNullOrEmpty(); expectedElement.ClassName.Should().BeNull(); expectedElement.GetAttribute("src").Should().BeNull(); - expectedElement.GetAttribute("blazor:elementreference").Should().Be(elementReference.Id); + expectedElement.GetAttribute("blazor:elementreference").Should().Be(elementReference!.Value.Id); countCallsOnLoadImageHandler.Should().Be(0); expectedElement.TriggerEvent("onload", progressEventArgs); @@ -746,16 +1160,16 @@ public void Should_Render_CropperComponent_With_IsNotAvaibleInitCropper_Paramete // assert IElement expectedElement = cropperComponent.Find($"img"); - ElementReference elementReference = (ElementReference)cropperComponent.Instance - .GetInstanceField("ImageReference"); + ElementReference? elementReference = cropperComponent.Instance + .GetCropperElementReference(); Guid cropperComponentId = (Guid)cropperComponent.Instance .GetInstanceField("CropperComponentId"); _mockCropperJsInterop.Verify(c => c.LoadModuleAsync(cancellationToken), Times.Once()); - elementReference.Id.Should().NotBeNullOrEmpty(); + elementReference!.Value.Id.Should().NotBeNullOrEmpty(); expectedElement.ClassName.Should().BeNull(); expectedElement.GetAttribute("src").Should().BeNull(); - expectedElement.GetAttribute("blazor:elementreference").Should().Be(elementReference.Id); + expectedElement.GetAttribute("blazor:elementreference").Should().Be(elementReference!.Value.Id); countCallsOnLoadImageHandler.Should().Be(0); expectedElement.TriggerEvent("onload", progressEventArgs); @@ -825,21 +1239,21 @@ public async Task Should_Render_CropperComponent_Without_Action_Parameters_Succe // assert IElement expectedElement = cropperComponent.Find($"img"); - ElementReference elementReference = (ElementReference)cropperComponent.Instance - .GetInstanceField("ImageReference"); + ElementReference? elementReference = cropperComponent.Instance + .GetCropperElementReference(); Guid cropperComponentId = (Guid)cropperComponent.Instance .GetInstanceField("CropperComponentId"); _mockCropperJsInterop.Verify(c => c.LoadModuleAsync(cancellationToken), Times.Once()); - elementReference.Id.Should().NotBeNullOrEmpty(); + elementReference!.Value.Id.Should().NotBeNullOrEmpty(); expectedElement.ClassName.Should().BeNull(); expectedElement.GetAttribute("src").Should().BeNull(); - expectedElement.GetAttribute("blazor:elementreference").Should().Be(elementReference.Id); + expectedElement.GetAttribute("blazor:elementreference").Should().Be(elementReference!.Value.Id); expectedElement.TriggerEvent("onload", progressEventArgs); _mockCropperJsInterop.Verify(c => c.InitCropperAsync( cropperComponentId, - elementReference, + elementReference!.Value, options, It.IsAny>(), cancellationToken), Times.Once()); @@ -876,21 +1290,21 @@ public async Task Should_Render_CropperComponent_Without_Options_Parameter_Succe // assert IElement expectedElement = cropperComponent.Find($"img"); - ElementReference elementReference = (ElementReference)cropperComponent.Instance - .GetInstanceField("ImageReference"); + ElementReference? elementReference = cropperComponent.Instance + .GetCropperElementReference(); Guid cropperComponentId = (Guid)cropperComponent.Instance .GetInstanceField("CropperComponentId"); _mockCropperJsInterop.Verify(c => c.LoadModuleAsync(cancellationToken), Times.Once()); - elementReference.Id.Should().NotBeNullOrEmpty(); + elementReference!.Value.Id.Should().NotBeNullOrEmpty(); expectedElement.ClassName.Should().BeNull(); expectedElement.GetAttribute("src").Should().BeNull(); - expectedElement.GetAttribute("blazor:elementreference").Should().Be(elementReference.Id); + expectedElement.GetAttribute("blazor:elementreference").Should().Be(elementReference!.Value.Id); expectedElement.TriggerEvent("onload", progressEventArgs); _mockCropperJsInterop.Verify(c => c.InitCropperAsync( cropperComponentId, - elementReference, + elementReference!.Value, It.Is(o => VerifyOptions(o)), It.IsAny>(), cancellationToken), Times.Once()); diff --git a/src/Cropper.Blazor/Cropper.Blazor.UnitTests/Cropper.Blazor.UnitTests.csproj b/src/Cropper.Blazor/Cropper.Blazor.UnitTests/Cropper.Blazor.UnitTests.csproj index 055070af..21f88d45 100644 --- a/src/Cropper.Blazor/Cropper.Blazor.UnitTests/Cropper.Blazor.UnitTests.csproj +++ b/src/Cropper.Blazor/Cropper.Blazor.UnitTests/Cropper.Blazor.UnitTests.csproj @@ -8,17 +8,17 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -26,18 +26,18 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - +
- + - + - + @@ -45,16 +45,16 @@ - - - - true - cobertura - ExcludeFromCodeCoverageAttribute - [Cropper.Blazor]* - **/*.g.cs - true - ./ - + + + + true + cobertura + ExcludeFromCodeCoverageAttribute + [Cropper.Blazor]* + **/*.g.cs + true + ./ + diff --git a/src/Cropper.Blazor/Cropper.Blazor.UnitTests/Services/CropperJsInterop_Should.cs b/src/Cropper.Blazor/Cropper.Blazor.UnitTests/Services/CropperJsInterop_Should.cs index a9c6f235..7c515484 100644 --- a/src/Cropper.Blazor/Cropper.Blazor.UnitTests/Services/CropperJsInterop_Should.cs +++ b/src/Cropper.Blazor/Cropper.Blazor.UnitTests/Services/CropperJsInterop_Should.cs @@ -335,7 +335,7 @@ public async Task Verify_GetImageUsingStreamingAsync() using DotNetStreamReference dotnetImageStream = new(jsImageStream); _testContext.JSInterop .Setup("cropper.getImageUsingStreaming", - jSRuntimeInvocation => jSRuntimeInvocation.Arguments.Count == 1 && VerifyStreamArgument(jSRuntimeInvocation)) + jSRuntimeInvocation => jSRuntimeInvocation.Arguments.Count == 1 && VerifyStreamArgument(jSRuntimeInvocation)) .SetResult(expectedImageData); bool VerifyStreamArgument(JSRuntimeInvocation jSRuntimeInvocation) diff --git a/src/Cropper.Blazor/Cropper.Blazor.sln b/src/Cropper.Blazor/Cropper.Blazor.sln index 157f7c8b..1e70a019 100644 --- a/src/Cropper.Blazor/Cropper.Blazor.sln +++ b/src/Cropper.Blazor/Cropper.Blazor.sln @@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cropper.Blazor.Testing", "C EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{624968C7-925A-4896-8405-4174FD48B9BC}" ProjectSection(SolutionItems) = preProject + ..\..\.editorconfig = ..\..\.editorconfig + ..\..\.gitattributes = ..\..\.gitattributes ..\..\.github\CODE_OF_CONDUCT.md = ..\..\.github\CODE_OF_CONDUCT.md ..\..\.github\PULL_REQUEST_TEMPLATE.md = ..\..\.github\PULL_REQUEST_TEMPLATE.md ..\..\README.md = ..\..\README.md @@ -24,6 +26,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "linters", "linters", "{42196427-7CE7-4B56-BAA3-717B1CB6C245}" ProjectSection(SolutionItems) = preProject + ..\..\.github\linters\.checkov.yaml = ..\..\.github\linters\.checkov.yaml ..\..\.github\linters\.htmlhintrc = ..\..\.github\linters\.htmlhintrc ..\..\.github\linters\.jscpd.json = ..\..\.github\linters\.jscpd.json ..\..\.github\linters\.stylelintrc.json = ..\..\.github\linters\.stylelintrc.json @@ -31,8 +34,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "linters", "linters", "{4219 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pipelines", "pipelines", "{378A2956-8E21-434D-9480-9D4E0732098B}" ProjectSection(SolutionItems) = preProject + ..\..\.github\workflows\build-test-template.yml = ..\..\.github\workflows\build-test-template.yml ..\..\.github\workflows\cd.yml = ..\..\.github\workflows\cd.yml ..\..\.github\workflows\ci.yml = ..\..\.github\workflows\ci.yml + ..\..\.github\workflows\dependabot-ci.yml = ..\..\.github\workflows\dependabot-ci.yml ..\..\.github\dependabot.yml = ..\..\.github\dependabot.yml ..\..\.github\workflows\release.yml = ..\..\.github\workflows\release.yml EndProjectSection diff --git a/src/Cropper.Blazor/Cropper.Blazor/.config/dotnet-tools.json b/src/Cropper.Blazor/Cropper.Blazor/.config/dotnet-tools.json index bf2cbcf3..ccc65f95 100644 --- a/src/Cropper.Blazor/Cropper.Blazor/.config/dotnet-tools.json +++ b/src/Cropper.Blazor/Cropper.Blazor/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "excubo.webcompiler": { - "version": "3.5.79", + "version": "3.9.0", "commands": [ "webcompiler" ] diff --git a/src/Cropper.Blazor/Cropper.Blazor/Components/CropperComponent.razor b/src/Cropper.Blazor/Cropper.Blazor/Components/CropperComponent.razor index 4e7a93ae..d32c875c 100644 --- a/src/Cropper.Blazor/Cropper.Blazor/Components/CropperComponent.razor +++ b/src/Cropper.Blazor/Cropper.Blazor/Components/CropperComponent.razor @@ -1,16 +1,33 @@ -@if (IsErrorLoadImage == true) +@using Models; + +@if (IsErrorLoadImage == true) { - + @if (ErrorLoadImageContent is null) + { + + } + else + { + @ErrorLoadImageContent + } } else { - + if (CropperComponentType == Models.CropperComponentType.Canvas) + { + + } + else + { + + } } diff --git a/src/Cropper.Blazor/Cropper.Blazor/Components/CropperComponent.razor.cs b/src/Cropper.Blazor/Cropper.Blazor/Components/CropperComponent.razor.cs index 92de5e27..277b2eb8 100644 --- a/src/Cropper.Blazor/Cropper.Blazor/Components/CropperComponent.razor.cs +++ b/src/Cropper.Blazor/Cropper.Blazor/Components/CropperComponent.razor.cs @@ -30,7 +30,12 @@ public partial class CropperComponent : ICropperComponentBase, IAsyncDisposable, /// /// Gets a reference to the img HTML element rendered by the component. /// - private ElementReference ImageReference; + private ElementReference? ImageReference; + + /// + /// Gets a reference to the canvas HTML element rendered by the component. + /// + private ElementReference? CanvasReference; /// /// The unique identifier of the cropper component. @@ -49,6 +54,13 @@ public partial class CropperComponent : ICropperComponentBase, IAsyncDisposable, [Parameter] public string Src { get; set; } = null!; + /// + /// Specifies the target element for cropping, the default value is . + /// In addition, for type requires manual uploading of images into canvas HTMl element, including error handling. + /// + [Parameter] + public CropperComponentType CropperComponentType { get; set; } = CropperComponentType.Image; + /// /// Specifies the path to the image when loading from src fails. /// @@ -67,6 +79,12 @@ public partial class CropperComponent : ICropperComponentBase, IAsyncDisposable, [Parameter] public bool IsErrorLoadImage { get; set; } + /// + /// Content is shown instead of the default error image. + /// + [Parameter] + public RenderFragment? ErrorLoadImageContent { get; set; } + /// /// Responsible for allowing the initialization of the cropper after a successful image download, the default is always allowed (true). /// In addition, it should be used to disable re-initialization (replace image) of cropper after successful image load when set to false. @@ -169,7 +187,29 @@ protected override void OnInitialized() } /// - /// This event is fired when the image is loaded. + /// Returns the reference to the cropper element, which can be either a canvas or an image, depending on the . + /// If an error occurs while loading the image (when equal to true), null is returned. + /// + /// A representing reference to the cropper element. + public ElementReference? GetCropperElementReference() + { + if (IsErrorLoadImage) + { + return null; + } + + if (CropperComponentType == CropperComponentType.Canvas) + { + return CanvasReference; + } + else + { + return ImageReference; + } + } + + /// + /// This event is fired when an image is loaded or called manually. /// /// /// If successful, outputs a which is @@ -203,8 +243,19 @@ public void OnErrorLoadImage(ErrorEventArgs errorEventArgs) public void InitCropper(CancellationToken cancellationToken = default) { ICropperComponentBase cropperComponentBase = this; - CropperJsIntertop!.InitCropperAsync(CropperComponentId, ImageReference, Options!, DotNetObjectReference.Create(cropperComponentBase), cancellationToken); - OnLoadImageEvent?.Invoke(); + ElementReference? cropperElementReference = GetCropperElementReference(); + + if (cropperElementReference.HasValue) + { + CropperJsIntertop!.InitCropperAsync( + CropperComponentId, + cropperElementReference.Value, + Options!, + DotNetObjectReference.Create(cropperComponentBase), + cancellationToken); + + OnLoadImageEvent?.Invoke(); + } } /// diff --git a/src/Cropper.Blazor/Cropper.Blazor/Cropper.Blazor.csproj b/src/Cropper.Blazor/Cropper.Blazor/Cropper.Blazor.csproj index 72c6e392..f72aaf69 100644 --- a/src/Cropper.Blazor/Cropper.Blazor/Cropper.Blazor.csproj +++ b/src/Cropper.Blazor/Cropper.Blazor/Cropper.Blazor.csproj @@ -14,7 +14,7 @@ - 1.3.2 + 1.3.3 LICENSE NuGet.png Cropper.Blazor @@ -114,15 +114,15 @@ - + - + - + diff --git a/src/Cropper.Blazor/Cropper.Blazor/Models/CropperComponentType.cs b/src/Cropper.Blazor/Cropper.Blazor/Models/CropperComponentType.cs new file mode 100644 index 00000000..5c669b17 --- /dev/null +++ b/src/Cropper.Blazor/Cropper.Blazor/Models/CropperComponentType.cs @@ -0,0 +1,22 @@ +using System.Runtime.Serialization; + +namespace Cropper.Blazor.Models +{ + /// + /// Enumeration of cropper component types. + /// + public enum CropperComponentType + { + /// + /// Specifies the target image element for cropping. + /// + [EnumMember(Value = "image")] + Image, + + /// + /// Specifies the target canvas element for cropping. + /// + [EnumMember(Value = "canvas")] + Canvas + } +} diff --git a/src/Cropper.Blazor/Cropper.Blazor/Models/GetCroppedCanvasOptions.cs b/src/Cropper.Blazor/Cropper.Blazor/Models/GetCroppedCanvasOptions.cs index ff1b3a5a..b5970e0b 100644 --- a/src/Cropper.Blazor/Cropper.Blazor/Models/GetCroppedCanvasOptions.cs +++ b/src/Cropper.Blazor/Cropper.Blazor/Models/GetCroppedCanvasOptions.cs @@ -61,4 +61,4 @@ public class GetCroppedCanvasOptions /// public bool? Rounded { get; set; } = false; } -} \ No newline at end of file +} diff --git a/src/Cropper.Blazor/Cropper.Blazor/Services/CropperJsInterop.cs b/src/Cropper.Blazor/Cropper.Blazor/Services/CropperJsInterop.cs index 6a179b31..48f29a17 100644 --- a/src/Cropper.Blazor/Cropper.Blazor/Services/CropperJsInterop.cs +++ b/src/Cropper.Blazor/Cropper.Blazor/Services/CropperJsInterop.cs @@ -884,4 +884,4 @@ protected virtual async ValueTask DisposeAsyncCore() Module = null; } } -} \ No newline at end of file +} diff --git a/src/Cropper.Blazor/Cropper.Blazor/Services/ICropperJsInterop.cs b/src/Cropper.Blazor/Cropper.Blazor/Services/ICropperJsInterop.cs index 4cb47894..72a578de 100644 --- a/src/Cropper.Blazor/Cropper.Blazor/Services/ICropperJsInterop.cs +++ b/src/Cropper.Blazor/Cropper.Blazor/Services/ICropperJsInterop.cs @@ -419,4 +419,4 @@ ValueTask RevokeObjectUrlAsync( /// A representing any asynchronous operation. ValueTask DisposeAsync(); } -} \ No newline at end of file +} diff --git a/src/Cropper.Blazor/Cropper.Blazor/wwwroot/cropper.min.js b/src/Cropper.Blazor/Cropper.Blazor/wwwroot/cropper.min.js index 959d89fe..3102cb54 100644 --- a/src/Cropper.Blazor/Cropper.Blazor/wwwroot/cropper.min.js +++ b/src/Cropper.Blazor/Cropper.Blazor/wwwroot/cropper.min.js @@ -1,10 +1,10 @@ /*! - * Cropper.js v1.6.1 + * Cropper.js v1.6.2 * https://fengyuanchen.github.io/cropperjs * * Copyright 2015-present Chen Fengyuan * Released under the MIT license * - * Date: 2023-09-17T03:44:19.860Z + * Date: 2024-04-21T07:43:05.335Z */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Cropper=e()}(this,function(){"use strict";function C(e,t){var i,a=Object.keys(e);return Object.getOwnPropertySymbols&&(i=Object.getOwnPropertySymbols(e),t&&(i=i.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),a.push.apply(a,i)),a}function S(a){for(var t=1;tt.length)&&(e=t.length);for(var i=0,a=new Array(e);it.width?3===i?o=t.height*e:h=t.width/e:3===i?h=t.width/e:o=t.height*e,{aspectRatio:e,naturalWidth:n,naturalHeight:a,width:o,height:h});this.canvasData=e,this.limited=1===i||2===i,this.limitCanvas(!0,!0),e.width=Math.min(Math.max(e.width,e.minWidth),e.maxWidth),e.height=Math.min(Math.max(e.height,e.minHeight),e.maxHeight),e.left=(t.width-e.width)/2,e.top=(t.height-e.height)/2,e.oldLeft=e.left,e.oldTop=e.top,this.initialCanvasData=g({},e)},limitCanvas:function(t,e){var i=this.options,a=this.containerData,n=this.canvasData,o=this.cropBoxData,h=i.viewMode,r=n.aspectRatio,s=this.cropped&&o;t&&(t=Number(i.minCanvasWidth)||0,i=Number(i.minCanvasHeight)||0,1=a.width&&(n.minLeft=Math.min(0,r),n.maxLeft=Math.max(0,r)),n.height>=a.height)&&(n.minTop=Math.min(0,t),n.maxTop=Math.max(0,t))):(n.minLeft=-n.width,n.minTop=-n.height,n.maxLeft=a.width,n.maxTop=a.height))},renderCanvas:function(t,e){var i,a,n,o,h=this.canvasData,r=this.imageData;e&&(e={width:r.naturalWidth*Math.abs(r.scaleX||1),height:r.naturalHeight*Math.abs(r.scaleY||1),degree:r.rotate||0},r=e.width,o=e.height,e=e.degree,i=90==(e=Math.abs(e)%180)?{width:o,height:r}:(a=e%90*Math.PI/180,i=Math.sin(a),n=r*(a=Math.cos(a))+o*i,r=r*i+o*a,90h.maxWidth||h.widthh.maxHeight||h.heighte.width?a.height=a.width/i:a.width=a.height*i),this.cropBoxData=a,this.limitCropBox(!0,!0),a.width=Math.min(Math.max(a.width,a.minWidth),a.maxWidth),a.height=Math.min(Math.max(a.height,a.minHeight),a.maxHeight),a.width=Math.max(a.minWidth,a.width*t),a.height=Math.max(a.minHeight,a.height*t),a.left=e.left+(e.width-a.width)/2,a.top=e.top+(e.height-a.height)/2,a.oldLeft=a.left,a.oldTop=a.top,this.initialCropBoxData=g({},a)},limitCropBox:function(t,e){var i,a,n=this.options,o=this.containerData,h=this.canvasData,r=this.cropBoxData,s=this.limited,c=n.aspectRatio;t&&(t=Number(n.minCropBoxWidth)||0,n=Number(n.minCropBoxHeight)||0,i=s?Math.min(o.width,h.width,h.width+h.left,o.width-h.left):o.width,a=s?Math.min(o.height,h.height,h.height+h.top,o.height-h.top):o.height,t=Math.min(t,o.width),n=Math.min(n,o.height),c&&(t&&n?ti.maxWidth||i.widthi.maxHeight||i.height=e.width&&i.height>=e.height?q:I),f(this.cropBox,g({width:i.width,height:i.height},x({translateX:i.left,translateY:i.top}))),this.cropped&&this.limited&&this.limitCanvas(!0,!0),this.disabled||this.output()},output:function(){this.preview(),y(this.element,tt,this.getData())}},i={initPreview:function(){var t=this.element,i=this.crossOrigin,e=this.options.preview,a=i?this.crossOriginUrl:this.url,n=t.alt||"The image to preview",o=document.createElement("img");i&&(o.crossOrigin=i),o.src=a,o.alt=n,this.viewBox.appendChild(o),this.viewBoxImage=o,e&&("string"==typeof(o=e)?o=t.ownerDocument.querySelectorAll(e):e.querySelector&&(o=[e]),z(this.previews=o,function(t){var e=document.createElement("img");w(t,m,{width:t.offsetWidth,height:t.offsetHeight,html:t.innerHTML}),i&&(e.crossOrigin=i),e.src=a,e.alt=n,e.style.cssText='display:block;width:100%;height:auto;min-width:0!important;min-height:0!important;max-width:none!important;max-height:none!important;image-orientation:0deg!important;"',t.innerHTML="",t.appendChild(e)}))},resetPreview:function(){z(this.previews,function(e){var i=Bt(e,m),i=(f(e,{width:i.width,height:i.height}),e.innerHTML=i.html,e),e=m;if(o(i[e]))try{delete i[e]}catch(t){i[e]=void 0}else if(i.dataset)try{delete i.dataset[e]}catch(t){i.dataset[e]=void 0}else i.removeAttribute("data-".concat(Dt(e)))})},preview:function(){var h=this.imageData,t=this.canvasData,e=this.cropBoxData,r=e.width,s=e.height,c=h.width,d=h.height,l=e.left-t.left-h.left,p=e.top-t.top-h.top;this.cropped&&!this.disabled&&(f(this.viewBoxImage,g({width:c,height:d},x(g({translateX:-l,translateY:-p},h)))),z(this.previews,function(t){var e=Bt(t,m),i=e.width,e=e.height,a=i,n=e,o=1;r&&(n=s*(o=i/r)),s&&eMath.abs(a-1)?i:a)&&(t.restore&&(o=this.getCanvasData(),h=this.getCropBoxData()),this.render(),t.restore)&&(this.setCanvasData(z(o,function(t,e){o[e]=t*n})),this.setCropBoxData(z(h,function(t,e){h[e]=t*n}))))},dblclick:function(){var t,e;this.disabled||this.options.dragMode===_||this.setDragMode((t=this.dragBox,e=Q,(t.classList?t.classList.contains(e):-1y&&(D.x=y-f);break;case k:p+D.xx&&(D.y=x-v)}}var i,a,o,n=this.options,h=this.canvasData,r=this.containerData,s=this.cropBoxData,c=this.pointers,d=this.action,l=n.aspectRatio,p=s.left,m=s.top,u=s.width,g=s.height,f=p+u,v=m+g,w=0,b=0,y=r.width,x=r.height,M=!0,C=(!l&&t.shiftKey&&(l=u&&g?u/g:1),this.limited&&(w=s.minLeft,b=s.minTop,y=w+Math.min(r.width,h.width,h.left+h.width),x=b+Math.min(r.height,h.height,h.top+h.height)),c[Object.keys(c)[0]]),D={x:C.endX-C.startX,y:C.endY-C.startY};switch(d){case I:p+=D.x,m+=D.y;break;case B:0<=D.x&&(y<=f||l&&(m<=b||x<=v))?M=!1:(e(B),(u+=D.x)<0&&(d=k,p-=u=-u),l&&(m+=(s.height-(g=u/l))/2));break;case T:D.y<=0&&(m<=b||l&&(p<=w||y<=f))?M=!1:(e(T),g-=D.y,m+=D.y,g<0&&(d=O,m-=g=-g),l&&(p+=(s.width-(u=g*l))/2));break;case k:D.x<=0&&(p<=w||l&&(m<=b||x<=v))?M=!1:(e(k),u-=D.x,p+=D.x,u<0&&(d=B,p-=u=-u),l&&(m+=(s.height-(g=u/l))/2));break;case O:0<=D.y&&(x<=v||l&&(p<=w||y<=f))?M=!1:(e(O),(g+=D.y)<0&&(d=T,m-=g=-g),l&&(p+=(s.width-(u=g*l))/2));break;case E:if(l){if(D.y<=0&&(m<=b||y<=f)){M=!1;break}e(T),g-=D.y,m+=D.y,u=g*l}else e(T),e(B),!(0<=D.x)||fMath.abs(o)&&(o=i)})}),o),t),M=!1;break;case U:D.x&&D.y?(i=Wt(this.cropper),p=C.startX-i.left,m=C.startY-i.top,u=s.minWidth,g=s.minHeight,0 or element.");this.element=t,this.options=g({},ut,u(e)&&e),this.cropped=!1,this.disabled=!1,this.pointers={},this.ready=!1,this.reloading=!1,this.replaced=!1,this.sized=!1,this.sizing=!1,this.init()}var t,e,i;return t=n,i=[{key:"noConflict",value:function(){return window.Cropper=Pt,n}},{key:"setDefaults",value:function(t){g(ut,u(t)&&t)}}],(e=[{key:"init",value:function(){var t,e=this.element,i=e.tagName.toLowerCase();if(!e[c]){if(e[c]=this,"img"===i){if(this.isImg=!0,t=e.getAttribute("src")||"",!(this.originalUrl=t))return;t=e.src}else"canvas"===i&&window.HTMLCanvasElement&&(t=e.toDataURL());this.load(t)}}},{key:"load",value:function(t){var e,i,a,n,o,h,r=this;t&&(this.url=t,this.imageData={},e=this.element,(i=this.options).rotatable||i.scalable||(i.checkOrientation=!1),i.checkOrientation&&window.ArrayBuffer?lt.test(t)?pt.test(t)?this.read((h=(h=t).replace(Xt,""),a=atob(h),h=new ArrayBuffer(a.length),z(n=new Uint8Array(h),function(t,e){n[e]=a.charCodeAt(e)}),h)):this.clone():(o=new XMLHttpRequest,h=this.clone.bind(this),this.reloading=!0,(this.xhr=o).onabort=h,o.onerror=h,o.ontimeout=h,o.onprogress=function(){o.getResponseHeader("content-type")!==ct&&o.abort()},o.onload=function(){r.read(o.response)},o.onloadend=function(){r.reloading=!1,r.xhr=null},i.checkCrossOrigin&&Lt(t)&&e.crossOrigin&&(t=zt(t)),o.open("GET",t,!0),o.responseType="arraybuffer",o.withCredentials="use-credentials"===e.crossOrigin,o.send()):this.clone())}},{key:"read",value:function(t){var e=this.options,i=this.imageData,a=Rt(t),n=0,o=1,h=1;1
',o=(n=n.querySelector(".".concat(c,"-container"))).querySelector(".".concat(c,"-canvas")),h=n.querySelector(".".concat(c,"-drag-box")),s=(r=n.querySelector(".".concat(c,"-crop-box"))).querySelector(".".concat(c,"-face")),this.container=a,this.cropper=n,this.canvas=o,this.dragBox=h,this.cropBox=r,this.viewBox=n.querySelector(".".concat(c,"-view-box")),this.face=s,o.appendChild(i),v(t,L),a.insertBefore(n,t.nextSibling),X(i,Z),this.initPreview(),this.bind(),e.initialAspectRatio=Math.max(0,e.initialAspectRatio)||NaN,e.aspectRatio=Math.max(0,e.aspectRatio)||NaN,e.viewMode=Math.max(0,Math.min(3,Math.round(e.viewMode)))||0,v(r,L),e.guides||v(r.getElementsByClassName("".concat(c,"-dashed")),L),e.center||v(r.getElementsByClassName("".concat(c,"-center")),L),e.background&&v(n,"".concat(c,"-bg")),e.highlight||v(s,G),e.cropBoxMovable&&(v(s,V),w(s,d,I)),e.cropBoxResizable||(v(r.getElementsByClassName("".concat(c,"-line")),L),v(r.getElementsByClassName("".concat(c,"-point")),L)),this.render(),this.ready=!0,this.setDragMode(e.dragMode),e.autoCrop&&this.crop(),this.setData(e.data),l(e.ready)&&b(t,"ready",e.ready,{once:!0}),y(t,"ready"))}},{key:"unbuild",value:function(){var t;this.ready&&(this.ready=!1,this.unbind(),this.resetPreview(),(t=this.cropper.parentNode)&&t.removeChild(this.cropper),X(this.element,L))}},{key:"uncreate",value:function(){this.ready?(this.unbuild(),this.ready=!1,this.cropped=!1):this.sizing?(this.sizingImage.onload=null,this.sizing=!1,this.sized=!1):this.reloading?(this.xhr.onabort=null,this.xhr.abort()):this.image&&this.stop()}}])&&j(t.prototype,e),i&&j(t,i),Object.defineProperty(t,"prototype",{writable:!1}),n}();return g(It.prototype,t,i,e,St,jt,At),It}); \ No newline at end of file +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Cropper=e()}(this,function(){"use strict";function C(e,t){var i,a=Object.keys(e);return Object.getOwnPropertySymbols&&(i=Object.getOwnPropertySymbols(e),t&&(i=i.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),a.push.apply(a,i)),a}function S(a){for(var t=1;tt.length)&&(e=t.length);for(var i=0,a=new Array(e);it.width?3===i?o=t.height*e:h=t.width/e:3===i?h=t.width/e:o=t.height*e,{aspectRatio:e,naturalWidth:n,naturalHeight:a,width:o,height:h});this.canvasData=e,this.limited=1===i||2===i,this.limitCanvas(!0,!0),e.width=Math.min(Math.max(e.width,e.minWidth),e.maxWidth),e.height=Math.min(Math.max(e.height,e.minHeight),e.maxHeight),e.left=(t.width-e.width)/2,e.top=(t.height-e.height)/2,e.oldLeft=e.left,e.oldTop=e.top,this.initialCanvasData=g({},e)},limitCanvas:function(t,e){var i=this.options,a=this.containerData,n=this.canvasData,o=this.cropBoxData,h=i.viewMode,r=n.aspectRatio,s=this.cropped&&o;t&&(t=Number(i.minCanvasWidth)||0,i=Number(i.minCanvasHeight)||0,1=a.width&&(n.minLeft=Math.min(0,r),n.maxLeft=Math.max(0,r)),n.height>=a.height)&&(n.minTop=Math.min(0,t),n.maxTop=Math.max(0,t))):(n.minLeft=-n.width,n.minTop=-n.height,n.maxLeft=a.width,n.maxTop=a.height))},renderCanvas:function(t,e){var i,a,n,o,h=this.canvasData,r=this.imageData;e&&(e={width:r.naturalWidth*Math.abs(r.scaleX||1),height:r.naturalHeight*Math.abs(r.scaleY||1),degree:r.rotate||0},r=e.width,o=e.height,e=e.degree,i=90==(e=Math.abs(e)%180)?{width:o,height:r}:(a=e%90*Math.PI/180,i=Math.sin(a),n=r*(a=Math.cos(a))+o*i,r=r*i+o*a,90h.maxWidth||h.widthh.maxHeight||h.heighte.width?a.height=a.width/i:a.width=a.height*i),this.cropBoxData=a,this.limitCropBox(!0,!0),a.width=Math.min(Math.max(a.width,a.minWidth),a.maxWidth),a.height=Math.min(Math.max(a.height,a.minHeight),a.maxHeight),a.width=Math.max(a.minWidth,a.width*t),a.height=Math.max(a.minHeight,a.height*t),a.left=e.left+(e.width-a.width)/2,a.top=e.top+(e.height-a.height)/2,a.oldLeft=a.left,a.oldTop=a.top,this.initialCropBoxData=g({},a)},limitCropBox:function(t,e){var i,a,n=this.options,o=this.containerData,h=this.canvasData,r=this.cropBoxData,s=this.limited,c=n.aspectRatio;t&&(t=Number(n.minCropBoxWidth)||0,n=Number(n.minCropBoxHeight)||0,i=s?Math.min(o.width,h.width,h.width+h.left,o.width-h.left):o.width,a=s?Math.min(o.height,h.height,h.height+h.top,o.height-h.top):o.height,t=Math.min(t,o.width),n=Math.min(n,o.height),c&&(t&&n?ti.maxWidth||i.widthi.maxHeight||i.height=e.width&&i.height>=e.height?q:I),f(this.cropBox,g({width:i.width,height:i.height},x({translateX:i.left,translateY:i.top}))),this.cropped&&this.limited&&this.limitCanvas(!0,!0),this.disabled||this.output()},output:function(){this.preview(),y(this.element,tt,this.getData())}},i={initPreview:function(){var t=this.element,i=this.crossOrigin,e=this.options.preview,a=i?this.crossOriginUrl:this.url,n=t.alt||"The image to preview",o=document.createElement("img");i&&(o.crossOrigin=i),o.src=a,o.alt=n,this.viewBox.appendChild(o),this.viewBoxImage=o,e&&("string"==typeof(o=e)?o=t.ownerDocument.querySelectorAll(e):e.querySelector&&(o=[e]),z(this.previews=o,function(t){var e=document.createElement("img");w(t,m,{width:t.offsetWidth,height:t.offsetHeight,html:t.innerHTML}),i&&(e.crossOrigin=i),e.src=a,e.alt=n,e.style.cssText='display:block;width:100%;height:auto;min-width:0!important;min-height:0!important;max-width:none!important;max-height:none!important;image-orientation:0deg!important;"',t.innerHTML="",t.appendChild(e)}))},resetPreview:function(){z(this.previews,function(e){var i=Bt(e,m),i=(f(e,{width:i.width,height:i.height}),e.innerHTML=i.html,e),e=m;if(o(i[e]))try{delete i[e]}catch(t){i[e]=void 0}else if(i.dataset)try{delete i.dataset[e]}catch(t){i.dataset[e]=void 0}else i.removeAttribute("data-".concat(Dt(e)))})},preview:function(){var h=this.imageData,t=this.canvasData,e=this.cropBoxData,r=e.width,s=e.height,c=h.width,d=h.height,l=e.left-t.left-h.left,p=e.top-t.top-h.top;this.cropped&&!this.disabled&&(f(this.viewBoxImage,g({width:c,height:d},x(g({translateX:-l,translateY:-p},h)))),z(this.previews,function(t){var e=Bt(t,m),i=e.width,e=e.height,a=i,n=e,o=1;r&&(n=s*(o=i/r)),s&&eMath.abs(a-1)?i:a)&&(t.restore&&(o=this.getCanvasData(),h=this.getCropBoxData()),this.render(),t.restore)&&(this.setCanvasData(z(o,function(t,e){o[e]=t*n})),this.setCropBoxData(z(h,function(t,e){h[e]=t*n}))))},dblclick:function(){var t,e;this.disabled||this.options.dragMode===_||this.setDragMode((t=this.dragBox,e=Q,(t.classList?t.classList.contains(e):-1y&&(D.x=y-f);break;case k:p+D.xx&&(D.y=x-v)}}var i,a,o,n=this.options,h=this.canvasData,r=this.containerData,s=this.cropBoxData,c=this.pointers,d=this.action,l=n.aspectRatio,p=s.left,m=s.top,u=s.width,g=s.height,f=p+u,v=m+g,w=0,b=0,y=r.width,x=r.height,M=!0,C=(!l&&t.shiftKey&&(l=u&&g?u/g:1),this.limited&&(w=s.minLeft,b=s.minTop,y=w+Math.min(r.width,h.width,h.left+h.width),x=b+Math.min(r.height,h.height,h.top+h.height)),c[Object.keys(c)[0]]),D={x:C.endX-C.startX,y:C.endY-C.startY};switch(d){case I:p+=D.x,m+=D.y;break;case B:0<=D.x&&(y<=f||l&&(m<=b||x<=v))?M=!1:(e(B),(u+=D.x)<0&&(d=k,p-=u=-u),l&&(m+=(s.height-(g=u/l))/2));break;case T:D.y<=0&&(m<=b||l&&(p<=w||y<=f))?M=!1:(e(T),g-=D.y,m+=D.y,g<0&&(d=O,m-=g=-g),l&&(p+=(s.width-(u=g*l))/2));break;case k:D.x<=0&&(p<=w||l&&(m<=b||x<=v))?M=!1:(e(k),u-=D.x,p+=D.x,u<0&&(d=B,p-=u=-u),l&&(m+=(s.height-(g=u/l))/2));break;case O:0<=D.y&&(x<=v||l&&(p<=w||y<=f))?M=!1:(e(O),(g+=D.y)<0&&(d=T,m-=g=-g),l&&(p+=(s.width-(u=g*l))/2));break;case E:if(l){if(D.y<=0&&(m<=b||y<=f)){M=!1;break}e(T),g-=D.y,m+=D.y,u=g*l}else e(T),e(B),!(0<=D.x)||fMath.abs(o)&&(o=i)})}),o),t),M=!1;break;case U:D.x&&D.y?(i=Wt(this.cropper),p=C.startX-i.left,m=C.startY-i.top,u=s.minWidth,g=s.minHeight,0 or element.");this.element=t,this.options=g({},ut,u(e)&&e),this.cropped=!1,this.disabled=!1,this.pointers={},this.ready=!1,this.reloading=!1,this.replaced=!1,this.sized=!1,this.sizing=!1,this.init()}return t=n,i=[{key:"noConflict",value:function(){return window.Cropper=Pt,n}},{key:"setDefaults",value:function(t){g(ut,u(t)&&t)}}],(e=[{key:"init",value:function(){var t,e=this.element,i=e.tagName.toLowerCase();if(!e[c]){if(e[c]=this,"img"===i){if(this.isImg=!0,t=e.getAttribute("src")||"",!(this.originalUrl=t))return;t=e.src}else"canvas"===i&&window.HTMLCanvasElement&&(t=e.toDataURL());this.load(t)}}},{key:"load",value:function(t){var e,i,a,n,o,h,r=this;t&&(this.url=t,this.imageData={},e=this.element,(i=this.options).rotatable||i.scalable||(i.checkOrientation=!1),i.checkOrientation&&window.ArrayBuffer?lt.test(t)?pt.test(t)?this.read((h=(h=t).replace(Xt,""),a=atob(h),h=new ArrayBuffer(a.length),z(n=new Uint8Array(h),function(t,e){n[e]=a.charCodeAt(e)}),h)):this.clone():(o=new XMLHttpRequest,h=this.clone.bind(this),this.reloading=!0,(this.xhr=o).onabort=h,o.onerror=h,o.ontimeout=h,o.onprogress=function(){o.getResponseHeader("content-type")!==ct&&o.abort()},o.onload=function(){r.read(o.response)},o.onloadend=function(){r.reloading=!1,r.xhr=null},i.checkCrossOrigin&&Lt(t)&&e.crossOrigin&&(t=zt(t)),o.open("GET",t,!0),o.responseType="arraybuffer",o.withCredentials="use-credentials"===e.crossOrigin,o.send()):this.clone())}},{key:"read",value:function(t){var e=this.options,i=this.imageData,a=Rt(t),n=0,o=1,h=1;1
',o=(n=n.querySelector(".".concat(c,"-container"))).querySelector(".".concat(c,"-canvas")),h=n.querySelector(".".concat(c,"-drag-box")),s=(r=n.querySelector(".".concat(c,"-crop-box"))).querySelector(".".concat(c,"-face")),this.container=a,this.cropper=n,this.canvas=o,this.dragBox=h,this.cropBox=r,this.viewBox=n.querySelector(".".concat(c,"-view-box")),this.face=s,o.appendChild(i),v(t,L),a.insertBefore(n,t.nextSibling),X(i,Z),this.initPreview(),this.bind(),e.initialAspectRatio=Math.max(0,e.initialAspectRatio)||NaN,e.aspectRatio=Math.max(0,e.aspectRatio)||NaN,e.viewMode=Math.max(0,Math.min(3,Math.round(e.viewMode)))||0,v(r,L),e.guides||v(r.getElementsByClassName("".concat(c,"-dashed")),L),e.center||v(r.getElementsByClassName("".concat(c,"-center")),L),e.background&&v(n,"".concat(c,"-bg")),e.highlight||v(s,G),e.cropBoxMovable&&(v(s,V),w(s,d,I)),e.cropBoxResizable||(v(r.getElementsByClassName("".concat(c,"-line")),L),v(r.getElementsByClassName("".concat(c,"-point")),L)),this.render(),this.ready=!0,this.setDragMode(e.dragMode),e.autoCrop&&this.crop(),this.setData(e.data),l(e.ready)&&b(t,"ready",e.ready,{once:!0}),y(t,"ready"))}},{key:"unbuild",value:function(){var t;this.ready&&(this.ready=!1,this.unbind(),this.resetPreview(),(t=this.cropper.parentNode)&&t.removeChild(this.cropper),X(this.element,L))}},{key:"uncreate",value:function(){this.ready?(this.unbuild(),this.ready=!1,this.cropped=!1):this.sizing?(this.sizingImage.onload=null,this.sizing=!1,this.sized=!1):this.reloading?(this.xhr.onabort=null,this.xhr.abort()):this.image&&this.stop()}}])&&A(t.prototype,e),i&&A(t,i),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e,i}();return g(It.prototype,t,i,e,St,jt,At),It}); \ No newline at end of file diff --git a/src/Cropper.Blazor/Cropper.Blazor/wwwroot/cropperJsInterop.js b/src/Cropper.Blazor/Cropper.Blazor/wwwroot/cropperJsInterop.js index 98c1f0be..1ee0bbb3 100644 --- a/src/Cropper.Blazor/Cropper.Blazor/wwwroot/cropperJsInterop.js +++ b/src/Cropper.Blazor/Cropper.Blazor/wwwroot/cropperJsInterop.js @@ -1,287 +1,287 @@ class CropperDecorator { - - constructor() { - this.cropperInstances = {}; - } - - clear(cropperComponentId) { - return this.cropperInstances[cropperComponentId] - .clear(); - } - - crop(cropperComponentId) { - return this.cropperInstances[cropperComponentId] - .crop(); - } - - destroy(cropperComponentId) { - this.cropperInstances[cropperComponentId] - .destroy(); - delete this.cropperInstances[cropperComponentId]; - } - - disable(cropperComponentId) { - return this.cropperInstances[cropperComponentId] - .disable(); - } - - enable(cropperComponentId) { - return this.cropperInstances[cropperComponentId] - .enable(); - } - - getCanvasData(cropperComponentId) { - return this.cropperInstances[cropperComponentId] - .getCanvasData(); - } - - getContainerData(cropperComponentId) { - return this.cropperInstances[cropperComponentId] - .getContainerData(); - } - - getCropBoxData(cropperComponentId) { - return this.cropperInstances[cropperComponentId] - .getCropBoxData(); - } - - getCroppedCanvas(cropperComponentId, options) { - options.maxWidth ??= Infinity; - options.maxHeight ??= Infinity; - - return this.cropperInstances[cropperComponentId] - .getCroppedCanvas(options); - } - - getCroppedCanvasDataURL(cropperComponentId, options, type, encoderOptions) { - options.maxWidth ??= Infinity; - options.maxHeight ??= Infinity; - - return this.cropperInstances[cropperComponentId] - .getCroppedCanvas(options) - .toDataURL(type, encoderOptions); - } - - getData(cropperComponentId, rounded) { - return this.cropperInstances[cropperComponentId] - .getData(rounded); - } - - getImageData(cropperComponentId) { - return this.cropperInstances[cropperComponentId] - .getImageData(); - } - - move(cropperComponentId, offsetX, offsetY) { - return this.cropperInstances[cropperComponentId] - .move(offsetX, offsetY); - } - - moveTo(cropperComponentId, x, y) { - return this.cropperInstances[cropperComponentId] - .moveTo(x, y); - } - - replace(cropperComponentId, url, onlyColorChanged) { - return this.cropperInstances[cropperComponentId] - .replace(url, onlyColorChanged); - } - - reset(cropperComponentId) { - return this.cropperInstances[cropperComponentId] - .reset(); - } - - rotate(cropperComponentId, degree) { - return this.cropperInstances[cropperComponentId] - .rotate(degree); - } - - rotateTo(cropperComponentId, degree) { - return this.cropperInstances[cropperComponentId] - .rotateTo(degree); - } - - scale(cropperComponentId, scaleX, scaleY) { - return this.cropperInstances[cropperComponentId] - .scale(scaleX, scaleY); - } - - scaleX(cropperComponentId, scaleX) { - return this.cropperInstances[cropperComponentId] - .scaleX(scaleX); - } - - scaleY(cropperComponentId, scaleY) { - return this.cropperInstances[cropperComponentId] - .scaleY(scaleY); - } - - setAspectRatio(cropperComponentId, aspectRatio) { - return this.cropperInstances[cropperComponentId] - .setAspectRatio(aspectRatio); - } - - setCanvasData(cropperComponentId, data) { - return this.cropperInstances[cropperComponentId] - .setCanvasData(data); - } - - setCropBoxData(cropperComponentId, data) { - return this.cropperInstances[cropperComponentId] - .setCropBoxData(data); - } - - setData(cropperComponentId, data) { - return this.cropperInstances[cropperComponentId] - .setData(data); - } - - setDragMode(cropperComponentId, dragMode) { - return this.cropperInstances[cropperComponentId] - .setDragMode(dragMode); - } - - zoom(cropperComponentId, ratio) { - return this.cropperInstances[cropperComponentId] - .zoom(ratio); - } - - zoomTo(cropperComponentId, ratio, pivotX, pivotY) { - return this.cropperInstances[cropperComponentId] - .zoomTo(ratio, { pivotX, pivotY }); - } - - noConflict() { - return Cropper.noConflict(); - } - - setDefaults(options) { - return Cropper.setDefaults(options); - } - - async getImageUsingStreaming(imageStream) { - const arrayBuffer = await imageStream.arrayBuffer(); - const blob = new Blob([arrayBuffer]); - return URL.createObjectURL(blob); - } - - revokeObjectUrl(url) { - URL.revokeObjectURL(url); - } - - getJSEventData(instance, correlationId) { - return { - isTrusted: instance.isTrusted, - detail: this.getJSEventDataDetail(instance), - type: instance.type, - eventPhase: instance.eventPhase, - bubbles: instance.bubbles, - cancelable: instance.cancelable, - defaultPrevented: instance.defaultPrevented, - composed: instance.composed, - timeStamp: instance.timeStamp, - returnValue: instance.returnValue, - cancelBubble: instance.cancelBubble, - correlationId: correlationId - }; - } - - getJSEventDataDetail(instance) { - if (instance.type === "zoom") { - return { - oldRatio: instance.detail.oldRatio, - ratio: instance.detail.ratio, - originalEvent: instance.detail.originalEvent ? - DotNet.createJSObjectReference(instance.detail.originalEvent) : null - }; - } - else if (instance.type === "cropstart" || instance.type === "cropend" || instance.type === "cropmove") { - return { - action: instance.detail.action, - originalEvent: instance.detail.originalEvent ? - DotNet.createJSObjectReference(instance.detail.originalEvent) : null - }; - } - - return instance.detail; - } - - onReady(imageObject, event, correlationId) { - const jSEventData = this.getJSEventData(event, correlationId); - imageObject.invokeMethodAsync('IsReady', jSEventData); - } - - onCropStart(imageObject, event, correlationId) { - const jSEventData = this.getJSEventData(event, correlationId); - imageObject.invokeMethodAsync('CropperIsStarted', jSEventData); - } - - onCropMove(imageObject, event, correlationId) { - const jSEventData = this.getJSEventData(event, correlationId); - imageObject.invokeMethodAsync('CropperIsMoved', jSEventData); - } - - onCropEnd(imageObject, event, correlationId) { - const jSEventData = this.getJSEventData(event, correlationId); - imageObject.invokeMethodAsync('CropperIsEnded', jSEventData); - } - - onCrop(imageObject, event, correlationId) { - const jSEventData = this.getJSEventData(event, correlationId); - imageObject.invokeMethodAsync('CropperIsCroped', jSEventData); - } - - onZoom(imageObject, event, correlationId) { - const jSEventData = this.getJSEventData(event, correlationId); - imageObject.invokeMethodAsync('CropperIsZoomed', jSEventData); - } - - initCropper(cropperComponentId, image, optionsImage, imageObject) { - if (image == null) { - throw "Parameter 'image' must be is not null!"; - } - - if (optionsImage == null) { - throw "Parameter 'optionsImage' must be is not null!"; - } - - const options = {}; - const correlationId = optionsImage["correlationId"]; - - if (imageObject != null) { - const self = this; - - options['ready'] = function (event) { - self.onReady(imageObject, event, correlationId); - }; - options['cropstart'] = function (event) { - self.onCropStart(imageObject, event, correlationId); - }; - options['cropmove'] = function (event) { - self.onCropMove(imageObject, event, correlationId); - }; - options['cropend'] = function (event) { - self.onCropEnd(imageObject, event, correlationId); - }; - options['crop'] = function (event) { - self.onCrop(imageObject, event, correlationId); - }; - options['zoom'] = function (event) { - self.onZoom(imageObject, event, correlationId); - }; - } - - if (optionsImage != null) { - Object.entries(optionsImage)?.forEach(([key, value]) => { - options[key] = value; - }); - } - - const cropper = new Cropper(image, options); - - this.cropperInstances[cropperComponentId] = cropper; - } + constructor () { + this.cropperInstances = {} + } + + clear (cropperComponentId) { + return this.cropperInstances[cropperComponentId] + .clear() + } + + crop (cropperComponentId) { + return this.cropperInstances[cropperComponentId] + .crop() + } + + destroy (cropperComponentId) { + this.cropperInstances[cropperComponentId] + .destroy() + delete this.cropperInstances[cropperComponentId] + } + + disable (cropperComponentId) { + return this.cropperInstances[cropperComponentId] + .disable() + } + + enable (cropperComponentId) { + return this.cropperInstances[cropperComponentId] + .enable() + } + + getCanvasData (cropperComponentId) { + return this.cropperInstances[cropperComponentId] + .getCanvasData() + } + + getContainerData (cropperComponentId) { + return this.cropperInstances[cropperComponentId] + .getContainerData() + } + + getCropBoxData (cropperComponentId) { + return this.cropperInstances[cropperComponentId] + .getCropBoxData() + } + + getCroppedCanvas (cropperComponentId, options) { + options.maxWidth ??= Infinity + options.maxHeight ??= Infinity + + return this.cropperInstances[cropperComponentId] + .getCroppedCanvas(options) + } + + getCroppedCanvasDataURL (cropperComponentId, options, type, encoderOptions) { + options.maxWidth ??= Infinity + options.maxHeight ??= Infinity + + return this.cropperInstances[cropperComponentId] + .getCroppedCanvas(options) + .toDataURL(type, encoderOptions) + } + + getData (cropperComponentId, rounded) { + return this.cropperInstances[cropperComponentId] + .getData(rounded) + } + + getImageData (cropperComponentId) { + return this.cropperInstances[cropperComponentId] + .getImageData() + } + + move (cropperComponentId, offsetX, offsetY) { + return this.cropperInstances[cropperComponentId] + .move(offsetX, offsetY) + } + + moveTo (cropperComponentId, x, y) { + return this.cropperInstances[cropperComponentId] + .moveTo(x, y) + } + + replace (cropperComponentId, url, onlyColorChanged) { + return this.cropperInstances[cropperComponentId] + .replace(url, onlyColorChanged) + } + + reset (cropperComponentId) { + return this.cropperInstances[cropperComponentId] + .reset() + } + + rotate (cropperComponentId, degree) { + return this.cropperInstances[cropperComponentId] + .rotate(degree) + } + + rotateTo (cropperComponentId, degree) { + return this.cropperInstances[cropperComponentId] + .rotateTo(degree) + } + + scale (cropperComponentId, scaleX, scaleY) { + return this.cropperInstances[cropperComponentId] + .scale(scaleX, scaleY) + } + + scaleX (cropperComponentId, scaleX) { + return this.cropperInstances[cropperComponentId] + .scaleX(scaleX) + } + + scaleY (cropperComponentId, scaleY) { + return this.cropperInstances[cropperComponentId] + .scaleY(scaleY) + } + + setAspectRatio (cropperComponentId, aspectRatio) { + return this.cropperInstances[cropperComponentId] + .setAspectRatio(aspectRatio) + } + + setCanvasData (cropperComponentId, data) { + return this.cropperInstances[cropperComponentId] + .setCanvasData(data) + } + + setCropBoxData (cropperComponentId, data) { + return this.cropperInstances[cropperComponentId] + .setCropBoxData(data) + } + + setData (cropperComponentId, data) { + return this.cropperInstances[cropperComponentId] + .setData(data) + } + + setDragMode (cropperComponentId, dragMode) { + return this.cropperInstances[cropperComponentId] + .setDragMode(dragMode) + } + + zoom (cropperComponentId, ratio) { + return this.cropperInstances[cropperComponentId] + .zoom(ratio) + } + + zoomTo (cropperComponentId, ratio, pivotX, pivotY) { + return this.cropperInstances[cropperComponentId] + .zoomTo(ratio, { pivotX, pivotY }) + } + + noConflict () { + return Cropper.noConflict() // eslint-disable-line no-undef + } + + setDefaults (options) { + return Cropper.setDefaults(options) // eslint-disable-line no-undef + } + + async getImageUsingStreaming (imageStream) { + const arrayBuffer = await imageStream.arrayBuffer() + const blob = new Blob([arrayBuffer]) + return URL.createObjectURL(blob) + } + + revokeObjectUrl (url) { + URL.revokeObjectURL(url) + } + + getJSEventData (instance, correlationId) { + return { + isTrusted: instance.isTrusted, + detail: this.getJSEventDataDetail(instance), + type: instance.type, + eventPhase: instance.eventPhase, + bubbles: instance.bubbles, + cancelable: instance.cancelable, + defaultPrevented: instance.defaultPrevented, + composed: instance.composed, + timeStamp: instance.timeStamp, + returnValue: instance.returnValue, + cancelBubble: instance.cancelBubble, + correlationId + } + } + + getJSEventDataDetail (instance) { + if (instance.type === 'zoom') { + return { + oldRatio: instance.detail.oldRatio, + ratio: instance.detail.ratio, + originalEvent: instance.detail.originalEvent + ? DotNet.createJSObjectReference(instance.detail.originalEvent) // eslint-disable-line no-undef + : null + } + } else if (instance.type === 'cropstart' || instance.type === 'cropend' || instance.type === 'cropmove') { + return { + action: instance.detail.action, + originalEvent: instance.detail.originalEvent + ? DotNet.createJSObjectReference(instance.detail.originalEvent) // eslint-disable-line no-undef + : null + } + } + + return instance.detail + } + + onReady (imageObject, event, correlationId) { + const jSEventData = this.getJSEventData(event, correlationId) + imageObject.invokeMethodAsync('IsReady', jSEventData) + } + + onCropStart (imageObject, event, correlationId) { + const jSEventData = this.getJSEventData(event, correlationId) + imageObject.invokeMethodAsync('CropperIsStarted', jSEventData) + } + + onCropMove (imageObject, event, correlationId) { + const jSEventData = this.getJSEventData(event, correlationId) + imageObject.invokeMethodAsync('CropperIsMoved', jSEventData) + } + + onCropEnd (imageObject, event, correlationId) { + const jSEventData = this.getJSEventData(event, correlationId) + imageObject.invokeMethodAsync('CropperIsEnded', jSEventData) + } + + onCrop (imageObject, event, correlationId) { + const jSEventData = this.getJSEventData(event, correlationId) + imageObject.invokeMethodAsync('CropperIsCroped', jSEventData) + } + + onZoom (imageObject, event, correlationId) { + const jSEventData = this.getJSEventData(event, correlationId) + imageObject.invokeMethodAsync('CropperIsZoomed', jSEventData) + } + + initCropper (cropperComponentId, image, optionsImage, imageObject) { + if (image == null) { + throw new Error("Parameter 'image' must be is not null!") + } + + if (optionsImage == null) { + throw new Error("Parameter 'optionsImage' must be is not null!") + } + + const options = {} + const correlationId = optionsImage.correlationId + + if (imageObject != null) { + const self = this + + options.ready = function (event) { + self.onReady(imageObject, event, correlationId) + } + options.cropstart = function (event) { + self.onCropStart(imageObject, event, correlationId) + } + options.cropmove = function (event) { + self.onCropMove(imageObject, event, correlationId) + } + options.cropend = function (event) { + self.onCropEnd(imageObject, event, correlationId) + } + options.crop = function (event) { + self.onCrop(imageObject, event, correlationId) + } + options.zoom = function (event) { + self.onZoom(imageObject, event, correlationId) + } + } + + if (optionsImage != null) { + Object.entries(optionsImage)?.forEach(([key, value]) => { + options[key] = value + }) + } + + const cropper = new Cropper(image, options) // eslint-disable-line no-undef + + this.cropperInstances[cropperComponentId] = cropper + } } -window.cropper = new CropperDecorator(); \ No newline at end of file +window.cropper = new CropperDecorator() diff --git a/src/Cropper.Blazor/Server/Cropper.Blazor.Server.csproj b/src/Cropper.Blazor/Server/Cropper.Blazor.Server.csproj index 2f409bb2..f68215e1 100644 --- a/src/Cropper.Blazor/Server/Cropper.Blazor.Server.csproj +++ b/src/Cropper.Blazor/Server/Cropper.Blazor.Server.csproj @@ -6,9 +6,8 @@ enable - - + diff --git a/src/Cropper.Blazor/Server/Pages/Error.cshtml.cs b/src/Cropper.Blazor/Server/Pages/Error.cshtml.cs index 63d2c2cb..15f3de20 100644 --- a/src/Cropper.Blazor/Server/Pages/Error.cshtml.cs +++ b/src/Cropper.Blazor/Server/Pages/Error.cshtml.cs @@ -24,4 +24,4 @@ public void OnGet() RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; } } -} \ No newline at end of file +}