diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ec023651..a3a4c049 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,17 +14,32 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Setup Flutter - uses: subosito/flutter-action@v2 + - name: Install FVM + shell: bash + run: | + curl -fsSL https://fvm.app/install.sh | bash + fvm use stable --force - - name: Install dependencies - run: flutter pub get + - uses: kuhnroyal/flutter-fvm-config-action@v2 + id: fvm-config-action - - name: Build Runner - run: dart run build_runner build --delete-conflicting-outputs + - uses: subosito/flutter-action@v2 + with: + flutter-version: ${{ steps.fvm-config-action.outputs.FLUTTER_VERSION }} + channel: ${{ steps.fvm-config-action.outputs.FLUTTER_CHANNEL }} + + - name: Setup Melos + uses: bluefireteam/melos-action@v3 - uses: invertase/github-action-dart-analyzer@v1 with: fatal-infos: false - - - run: flutter test \ No newline at end of file + fatal-warnings: false + + - name: Install dependencies + run: flutter pub get + + - name: Build Runner + run: melos run gen:build + + - run: melos run test diff --git a/.gitignore b/.gitignore index 2d7bde6b..773bfae4 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ build/ # Node file for website node_modules +.local-chrome diff --git a/melos.yaml b/melos.yaml index 91051863..bec64931 100644 --- a/melos.yaml +++ b/melos.yaml @@ -12,6 +12,8 @@ command: flutter: ">=3.19.0" dependencies: collection: ^1.18.0 + mix: ^1.5.4 + ack: ^0.0.2 # publish: # hooks: # pre: melos run gen:build @@ -78,6 +80,10 @@ scripts: packageFilters: dirExists: test + clean: + run: melos exec -- flutter clean + description: Clean all packages + brb: run: melos run gen:build diff --git a/packages/superdeck/.cursorignore b/packages/superdeck/.cursorignore new file mode 100644 index 00000000..c177a801 --- /dev/null +++ b/packages/superdeck/.cursorignore @@ -0,0 +1,11 @@ +# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) +.dart_tool/ +.idea/ +.vscode/ +coverage/ +build/ +ios/ +macos/ +web/ +windows/ +linux/ diff --git a/packages/superdeck/.gitignore b/packages/superdeck/.gitignore new file mode 100644 index 00000000..773bfae4 --- /dev/null +++ b/packages/superdeck/.gitignore @@ -0,0 +1,48 @@ +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock + +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Ignoring native folders of the example as they can be re-generated easily using: +# flutter create --platforms=android,ios,web,windows,macos . + + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ + +# FVM Version Cache +.fvm/ +.firebase + +# Node file for website +node_modules +.local-chrome diff --git a/packages/superdeck/analysis_options.yaml b/packages/superdeck/analysis_options.yaml index 0e5cf807..f3d426c5 100644 --- a/packages/superdeck/analysis_options.yaml +++ b/packages/superdeck/analysis_options.yaml @@ -1,19 +1,2 @@ include: package:flutter_lints/flutter.yaml - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options -analyzer: - errors: - invalid_annotation_target: ignore - body_might_complete_normally_nullable: ignore - plugins: - - custom_lint - exclude: - - '**.mapper.dart' - - '**/generated_plugin_registrant.dart' -linter: - rules: - public_member_api_docs: false - always_use_package_imports: false - prefer_relative_imports: true - library_private_types_in_public_api: false \ No newline at end of file +extends: ../../shared_analysis_options.yaml diff --git a/packages/superdeck/grammars/markdown.json b/packages/superdeck/assets/grammars/markdown.json similarity index 100% rename from packages/superdeck/grammars/markdown.json rename to packages/superdeck/assets/grammars/markdown.json diff --git a/packages/superdeck/grammars/mermaid.json b/packages/superdeck/assets/grammars/mermaid.json similarity index 100% rename from packages/superdeck/grammars/mermaid.json rename to packages/superdeck/assets/grammars/mermaid.json diff --git a/packages/superdeck/grammars/python.json b/packages/superdeck/assets/grammars/python.json similarity index 100% rename from packages/superdeck/grammars/python.json rename to packages/superdeck/assets/grammars/python.json diff --git a/packages/superdeck/assets/iframe_template.html b/packages/superdeck/assets/iframe_template.html new file mode 100644 index 00000000..ffde02f7 --- /dev/null +++ b/packages/superdeck/assets/iframe_template.html @@ -0,0 +1,104 @@ + + + + + + iframe template + + + + + + + + \ No newline at end of file diff --git a/packages/superdeck/build.yaml b/packages/superdeck/build.yaml index a77b33df..70ac09db 100644 --- a/packages/superdeck/build.yaml +++ b/packages/superdeck/build.yaml @@ -16,11 +16,6 @@ targets: mix_generator|class_utility: generate_for: - lib/**/*.dart - -global_options: - dart_mappable_builder: - options: - caseStyle: snakeCase - enumCaseStyle: snakeCase - ignoreNull: true - generateMethods: [decode, encode, copy, stringify, equals] \ No newline at end of file + mix_generator|tokens: + generate_for: + - lib/**/*.dart diff --git a/packages/superdeck/devtools_options.yaml b/packages/superdeck/devtools_options.yaml new file mode 100644 index 00000000..fa0b357c --- /dev/null +++ b/packages/superdeck/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/packages/superdeck/example/.gitignore b/packages/superdeck/example/.gitignore index 2cf32e94..daa03a9c 100644 --- a/packages/superdeck/example/.gitignore +++ b/packages/superdeck/example/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/packages/superdeck/example/.metadata b/packages/superdeck/example/.metadata index 90eabcff..391c336b 100644 --- a/packages/superdeck/example/.metadata +++ b/packages/superdeck/example/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819" + revision: "5874a72aa4c779a02553007c47dacbefba2374dc" channel: "stable" project_type: app @@ -13,26 +13,26 @@ project_type: app migration: platforms: - platform: root - create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc - platform: android - create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc - platform: ios - create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc - platform: linux - create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc - platform: macos - create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc - platform: web - create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc - platform: windows - create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + create_revision: 5874a72aa4c779a02553007c47dacbefba2374dc + base_revision: 5874a72aa4c779a02553007c47dacbefba2374dc # User provided section diff --git a/packages/superdeck/example/.superdeck/assets/mermaid_srHRIuii.png b/packages/superdeck/example/.superdeck/assets/mermaid_srHRIuii.png new file mode 100644 index 00000000..ac745557 Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/mermaid_srHRIuii.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_0zqy1l5c.png b/packages/superdeck/example/.superdeck/assets/thumbnail_0zqy1l5c.png new file mode 100644 index 00000000..b439621b Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_0zqy1l5c.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_14RbmSW5.png b/packages/superdeck/example/.superdeck/assets/thumbnail_14RbmSW5.png new file mode 100644 index 00000000..8b550691 Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_14RbmSW5.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_3sLdrfsM.png b/packages/superdeck/example/.superdeck/assets/thumbnail_3sLdrfsM.png new file mode 100644 index 00000000..528591db Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_3sLdrfsM.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_9BmK4SPw.png b/packages/superdeck/example/.superdeck/assets/thumbnail_9BmK4SPw.png new file mode 100644 index 00000000..2a0dfe16 Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_9BmK4SPw.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_9mHDFwa9.png b/packages/superdeck/example/.superdeck/assets/thumbnail_9mHDFwa9.png new file mode 100644 index 00000000..0e2ab817 Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_9mHDFwa9.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_9y5hBeTm.png b/packages/superdeck/example/.superdeck/assets/thumbnail_9y5hBeTm.png new file mode 100644 index 00000000..5a3b4b80 Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_9y5hBeTm.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_CwxHOCpO.png b/packages/superdeck/example/.superdeck/assets/thumbnail_CwxHOCpO.png new file mode 100644 index 00000000..d6e2ad8a Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_CwxHOCpO.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_F2fTbXOG.png b/packages/superdeck/example/.superdeck/assets/thumbnail_F2fTbXOG.png new file mode 100644 index 00000000..39525555 Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_F2fTbXOG.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_H2GzZVSx.png b/packages/superdeck/example/.superdeck/assets/thumbnail_H2GzZVSx.png new file mode 100644 index 00000000..b7fd6bb2 Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_H2GzZVSx.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_RiDZbaFZ.png b/packages/superdeck/example/.superdeck/assets/thumbnail_RiDZbaFZ.png new file mode 100644 index 00000000..e0921b85 Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_RiDZbaFZ.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_SJncL4H2.png b/packages/superdeck/example/.superdeck/assets/thumbnail_SJncL4H2.png new file mode 100644 index 00000000..37afd832 Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_SJncL4H2.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_WyJ8NjmF.png b/packages/superdeck/example/.superdeck/assets/thumbnail_WyJ8NjmF.png new file mode 100644 index 00000000..4dfab07e Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_WyJ8NjmF.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_XeDZiCNk.png b/packages/superdeck/example/.superdeck/assets/thumbnail_XeDZiCNk.png new file mode 100644 index 00000000..26d9d411 Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_XeDZiCNk.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_Z40wIUYP.png b/packages/superdeck/example/.superdeck/assets/thumbnail_Z40wIUYP.png new file mode 100644 index 00000000..9f25523d Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_Z40wIUYP.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_aTAXFyQ7.png b/packages/superdeck/example/.superdeck/assets/thumbnail_aTAXFyQ7.png new file mode 100644 index 00000000..f4f15328 Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_aTAXFyQ7.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_cS8UY7ii.png b/packages/superdeck/example/.superdeck/assets/thumbnail_cS8UY7ii.png new file mode 100644 index 00000000..c7be3675 Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_cS8UY7ii.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_nPPBLQ6k.png b/packages/superdeck/example/.superdeck/assets/thumbnail_nPPBLQ6k.png new file mode 100644 index 00000000..8a61edc8 Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_nPPBLQ6k.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_oglBIjM0.png b/packages/superdeck/example/.superdeck/assets/thumbnail_oglBIjM0.png new file mode 100644 index 00000000..f815f7bd Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_oglBIjM0.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_pukXIjvK.png b/packages/superdeck/example/.superdeck/assets/thumbnail_pukXIjvK.png new file mode 100644 index 00000000..9db9955f Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_pukXIjvK.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_ybLDY8oi.png b/packages/superdeck/example/.superdeck/assets/thumbnail_ybLDY8oi.png new file mode 100644 index 00000000..1983997f Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_ybLDY8oi.png differ diff --git a/packages/superdeck/example/.superdeck/assets/thumbnail_z34aal1W.png b/packages/superdeck/example/.superdeck/assets/thumbnail_z34aal1W.png new file mode 100644 index 00000000..550cc8db Binary files /dev/null and b/packages/superdeck/example/.superdeck/assets/thumbnail_z34aal1W.png differ diff --git a/packages/superdeck/example/.superdeck/generated/image_caching_c5tnibJL.gif b/packages/superdeck/example/.superdeck/generated/image_caching_c5tnibJL.gif deleted file mode 100644 index 87935e86..00000000 Binary files a/packages/superdeck/example/.superdeck/generated/image_caching_c5tnibJL.gif and /dev/null differ diff --git a/packages/superdeck/example/.superdeck/generated/image_caching_woxVKegn.gif b/packages/superdeck/example/.superdeck/generated/image_caching_woxVKegn.gif deleted file mode 100644 index aaabb82d..00000000 Binary files a/packages/superdeck/example/.superdeck/generated/image_caching_woxVKegn.gif and /dev/null differ diff --git a/packages/superdeck/example/.superdeck/generated/mermaid_s3Iic43G.png b/packages/superdeck/example/.superdeck/generated/mermaid_s3Iic43G.png deleted file mode 100644 index 44cc94d2..00000000 Binary files a/packages/superdeck/example/.superdeck/generated/mermaid_s3Iic43G.png and /dev/null differ diff --git a/packages/superdeck/example/.superdeck/generated/thumbnail_9aLrluQc.png b/packages/superdeck/example/.superdeck/generated/thumbnail_9aLrluQc.png deleted file mode 100644 index 31336ca3..00000000 Binary files a/packages/superdeck/example/.superdeck/generated/thumbnail_9aLrluQc.png and /dev/null differ diff --git a/packages/superdeck/example/.superdeck/generated/thumbnail_AbUkCAD1.png b/packages/superdeck/example/.superdeck/generated/thumbnail_AbUkCAD1.png deleted file mode 100644 index 8907df88..00000000 Binary files a/packages/superdeck/example/.superdeck/generated/thumbnail_AbUkCAD1.png and /dev/null differ diff --git a/packages/superdeck/example/.superdeck/generated/thumbnail_HR8xUsF0.png b/packages/superdeck/example/.superdeck/generated/thumbnail_HR8xUsF0.png deleted file mode 100644 index 3d0120a3..00000000 Binary files a/packages/superdeck/example/.superdeck/generated/thumbnail_HR8xUsF0.png and /dev/null differ diff --git a/packages/superdeck/example/.superdeck/generated/thumbnail_RiJb4GGG.png b/packages/superdeck/example/.superdeck/generated/thumbnail_RiJb4GGG.png deleted file mode 100644 index 80a38e8e..00000000 Binary files a/packages/superdeck/example/.superdeck/generated/thumbnail_RiJb4GGG.png and /dev/null differ diff --git a/packages/superdeck/example/.superdeck/generated/thumbnail_bztq1AdA.png b/packages/superdeck/example/.superdeck/generated/thumbnail_bztq1AdA.png deleted file mode 100644 index 64f04f2d..00000000 Binary files a/packages/superdeck/example/.superdeck/generated/thumbnail_bztq1AdA.png and /dev/null differ diff --git a/packages/superdeck/example/.superdeck/generated/thumbnail_hhnMdIWj.png b/packages/superdeck/example/.superdeck/generated/thumbnail_hhnMdIWj.png deleted file mode 100644 index 9f452379..00000000 Binary files a/packages/superdeck/example/.superdeck/generated/thumbnail_hhnMdIWj.png and /dev/null differ diff --git a/packages/superdeck/example/.superdeck/generated/thumbnail_kXnyZsjq.png b/packages/superdeck/example/.superdeck/generated/thumbnail_kXnyZsjq.png deleted file mode 100644 index baf4100c..00000000 Binary files a/packages/superdeck/example/.superdeck/generated/thumbnail_kXnyZsjq.png and /dev/null differ diff --git a/packages/superdeck/example/.superdeck/generated/thumbnail_nuiio8i0.png b/packages/superdeck/example/.superdeck/generated/thumbnail_nuiio8i0.png deleted file mode 100644 index 62ce9e2f..00000000 Binary files a/packages/superdeck/example/.superdeck/generated/thumbnail_nuiio8i0.png and /dev/null differ diff --git a/packages/superdeck/example/.superdeck/generated/thumbnail_q6arHVVz.png b/packages/superdeck/example/.superdeck/generated/thumbnail_q6arHVVz.png deleted file mode 100644 index 2f0e3424..00000000 Binary files a/packages/superdeck/example/.superdeck/generated/thumbnail_q6arHVVz.png and /dev/null differ diff --git a/packages/superdeck/example/.superdeck/generated/thumbnail_shPxXVHO.png b/packages/superdeck/example/.superdeck/generated/thumbnail_shPxXVHO.png deleted file mode 100644 index 51e344c6..00000000 Binary files a/packages/superdeck/example/.superdeck/generated/thumbnail_shPxXVHO.png and /dev/null differ diff --git a/packages/superdeck/example/.superdeck/generated/thumbnail_xRHNCnNN.png b/packages/superdeck/example/.superdeck/generated/thumbnail_xRHNCnNN.png deleted file mode 100644 index 46baf94e..00000000 Binary files a/packages/superdeck/example/.superdeck/generated/thumbnail_xRHNCnNN.png and /dev/null differ diff --git a/packages/superdeck/example/.superdeck/generated_assets.json b/packages/superdeck/example/.superdeck/generated_assets.json new file mode 100644 index 00000000..ef8e0c42 --- /dev/null +++ b/packages/superdeck/example/.superdeck/generated_assets.json @@ -0,0 +1,76 @@ +{ + "last_modified": "2025-02-21T16:03:53.326253Z", + "files": [ + ".superdeck/assets/thumbnail_cS8UY7ii.png", + ".superdeck/assets/thumbnail_nPPBLQ6k.png", + ".superdeck/assets/thumbnail_WyJ8NjmF.png", + ".superdeck/assets/thumbnail_F2fTbXOG.png", + ".superdeck/assets/thumbnail_0zqy1l5c.png", + ".superdeck/assets/thumbnail_RiDZbaFZ.png", + ".superdeck/assets/thumbnail_z34aal1W.png", + ".superdeck/assets/thumbnail_H2GzZVSx.png", + ".superdeck/assets/thumbnail_SJncL4H2.png", + ".superdeck/assets/thumbnail_ybLDY8oi.png", + ".superdeck/assets/thumbnail_aTAXFyQ7.png", + ".superdeck/assets/thumbnail_9mHDFwa9.png", + ".superdeck/assets/thumbnail_9BmK4SPw.png", + ".superdeck/assets/thumbnail_3sLdrfsM.png", + ".superdeck/assets/thumbnail_oglBIjM0.png", + ".superdeck/assets/thumbnail_Z40wIUYP.png", + ".superdeck/assets/thumbnail_9y5hBeTm.png", + ".superdeck/assets/thumbnail_CwxHOCpO.png", + ".superdeck/assets/thumbnail_pukXIjvK.png", + ".superdeck/assets/thumbnail_14RbmSW5.png", + ".superdeck/assets/thumbnail_XeDZiCNk.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png", + ".superdeck/assets/mermaid_srHRIuii.png" + ] +} \ No newline at end of file diff --git a/packages/superdeck/example/.superdeck/slides.json b/packages/superdeck/example/.superdeck/slides.json deleted file mode 100644 index a2fa57e5..00000000 --- a/packages/superdeck/example/.superdeck/slides.json +++ /dev/null @@ -1,205 +0,0 @@ -{ - "config": { - "transition": { - "type": "fade_in", - "duration": 0 - } - }, - "slides": [ - { - "style": "quote", - "layout": "image", - "options": { - "src": "https://picsum.photos/600/600.webp", - "fit": "cover" - }, - "content": "> Create your Flutter presentations faster and easier than ever.\n> You can quote me on that\n> ### Leo Farias", - "content_options": { - "alignment": "bottom_right" - }, - "key": "HR8xUsF0" - }, - { - "background": "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZGt1MnQ5N2k3cXVma24wb3V5cThlZ3ExY2NvY3czcmozang0bGQ1ZSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/XzWd8acQ37byKR4tmd/giphy.gif", - "style": "cover", - "content": "# Complex layout", - "key": "RiJb4GGG" - }, - { - "layout": "image", - "content": "## Image Layout\n\nCreate beautiful slides with images that fit your content.\n\n##### Options\n```yaml\ncontent:\noptions:\n src: https//www.url.com/image.jpg\n fit: cover\n position: left\n flex: 1\n```\n\n> Define position fit and flex options for the image.", - "style": "show_sections", - "options": { - "src": "https://picsum.photos/900/700?waves", - "fit": "cover", - "position": "left", - "flex": 1 - }, - "content_options": { - "alignment": "bottom_right", - "flex": 1 - }, - "key": "hhnMdIWj" - }, - { - "layout": "two_column", - "style": "show_sections", - "sections": { - "left": { - "flex": 2 - }, - "right": { - "alignment": "bottom_left" - } - }, - "content": "::left::\n\n# Two Column\n\nThis is a two-column layout. You can use it to compare two different concepts or ideas.\n\n::right::\n\n### Section Options\n\nEasily customize the content of each section to suit your needs.\n\nUse front matter to define the layout of each section\n\n\n```yaml\nsections:\n left:\n flex: 2\n right:\n alignment: bottom_left\n```", - "key": "kXnyZsjq" - }, - { - "layout": "two_column_header", - "content": "# Two Column + Header\n\n\n::left::\n\n### Left Section\nEasily customize the content of each section to suit your needs.\n\nUse front matter to define the layout of each section\n::right::\n\n#### Section Options\n\n```yaml\nsections:\n left:\n alignment: bottom_right\n flex: 2\n right:\n alignment: bottom_left\n header:\n alignment: bottom_left\n```", - "sections": { - "left": { - "flex": 2 - }, - "right": { - "alignment": "bottom_left" - }, - "header": { - "alignment": "bottom_left" - } - }, - "style": "show_sections", - "content_options": { - "alignment": "center", - "flex": 2 - }, - "key": "bztq1AdA" - }, - { - "style": "rad", - "layout": "two_column", - "content": "# Mix\n\nIntegration with Mix gives you complete control over all styling elements in your slides with a simple and intuitive API.\n\n::right::\n\n```dart\nVariantAttribute get radStyle {\n return const SlideVariant('rad')(\n $.h1.textStyle.as(GoogleFonts.poppins()),\n $.h1.textStyle.fontSize(140),\n $.code.decoration.border.all(\n color: Colors.red,\n width: 3,\n ),\n $.code.decoration(\n color: Colors.black54,\n ),\n $.code.padding.all(40),\n\n $.outerContainer.margin.all(60),\n\n $.innerContainer.borderRadius(25),\n $.innerContainer.shadow(\n blurRadius: 0,\n spreadRadius: 10,\n color: Colors.red.withOpacity(1),\n ),\n $.innerContainer.gradient.radial(\n stops: [0.0, 1.0],\n radius: 0.7,\n colors: [Colors.purple, Colors.deepPurple],\n ),\n\n // Events\n onMouseHover((event) {\n final position = event.position;\n final dx = position.x * 10;\n final dy = position.y * 10;\n\n return Style(\n $.innerContainer.transform(_transformMatrix(position)),\n $.innerContainer.shadow.offset(dx, dy),\n $.innerContainer.gradient.radial(\n center: position,\n ),\n );\n }),\n\n (onPressed | onLongPressed)(\n $.innerContainer.shadow(\n blurRadius: 5,\n spreadRadius: 1,\n offset: Offset.zero,\n color: Colors.purpleAccent,\n ),\n $.innerContainer.border.all(color: Colors.white, width: 1),\n $.innerContainer.gradient.radial\n .colors([Colors.purpleAccent, Colors.purpleAccent]),\n ),\n );\n}\n\n```", - "sections": { - "left": null, - "right": { - "alignment": "bottom_left", - "flex": 2 - } - }, - "content_options": { - "alignment": "center" - }, - "key": "AbUkCAD1" - }, - { - "style": "cover", - "background": "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExeGswdWJvY2oxazJoY3g2Y2poNHBvZXlpYmd5YTg0Z2g0ODRrbng4MyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/oB6KlAvOuaLtxYy8l4/giphy.gif", - "content": "# Markdown support", - "key": "q6arHVVz" - }, - { - "style": "show_sections", - "layout": "two_column", - "sections": null, - "content": "::left::\n\n\n**Bold Text**\n\n*Italic Text*\n\n~~Strikethrough~~\n\n`Inline Code`\n\n[Link here](https://github.com/leoafarias/superdeck)\n\n::right::\n\nLists\n\n1. Ordered list item 1\n2. Ordered list item 2\n\n- Unordered list item 1\n- Unordered list item 2\n\nQuotes\n\n> If you want to go fast, go alone. \n> If you want to go far, go together.\n> ### African Proverb", - "content_options": { - "flex": 4 - }, - "key": "shPxXVHO" - }, - { - "layout": "two_column", - "content": "::left::\n\n\nCode\n```dart\nint factorial(int n) {\n return n == 0 ? 1 : n * factorial(n - 1);\n}\n\n```\n\nTasks\n- [ ] Item 1\n- [x] Item 2\n\nSubtasks\n\n- [x] Item 1\n - [ ] Subitem 1\n\n::right::\n\nImages\n![Unsplash Image](https://picsum.photos/300/200?landscape)\n\n\nTable\n\n| Header 1 | Header 2 |\n|----------|----------|\n| Cell 1A | Cell 1B |\n| Cell 2A | Cell 2B |\n\nDivider\n\n___", - "key": "nuiio8i0" - }, - { - "title": "Mermaid example", - "layout": "two_column", - "content": "::left::\n\n![Mermaid Diagram](.superdeck/generated/mermaid_s3Iic43G.png)\n \n\n::right::\n\n## Mermaid Support\n\nSuperdeck allows you to use Mermaid diagrams in your slides. It automatically converts the code into a visual representation.", - "key": "9aLrluQc" - }, - { - "layout": "widget", - "options": { - "name": "demo", - "args": { - "text": "Hello, Superdeck!", - "height": 200.0, - "width": 300.0 - } - }, - "content": "## Showcase your widgets", - "key": "xRHNCnNN" - } - ], - "assets": [ - { - "path": ".superdeck/generated/thumbnail_RiJb4GGG.png", - "width": 512, - "height": 288 - }, - { - "path": ".superdeck/generated/image_caching_woxVKegn.gif", - "width": 500, - "height": 500, - "reference": "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZGt1MnQ5N2k3cXVma24wb3V5cThlZ3ExY2NvY3czcmozang0bGQ1ZSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/XzWd8acQ37byKR4tmd/giphy.gif" - }, - { - "path": ".superdeck/generated/thumbnail_hhnMdIWj.png", - "width": 512, - "height": 288 - }, - { - "path": ".superdeck/generated/thumbnail_kXnyZsjq.png", - "width": 512, - "height": 288 - }, - { - "path": ".superdeck/generated/thumbnail_bztq1AdA.png", - "width": 512, - "height": 288 - }, - { - "path": ".superdeck/generated/thumbnail_AbUkCAD1.png", - "width": 512, - "height": 288 - }, - { - "path": ".superdeck/generated/thumbnail_q6arHVVz.png", - "width": 512, - "height": 288 - }, - { - "path": ".superdeck/generated/image_caching_c5tnibJL.gif", - "width": 270, - "height": 480, - "reference": "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExeGswdWJvY2oxazJoY3g2Y2poNHBvZXlpYmd5YTg0Z2g0ODRrbng4MyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/oB6KlAvOuaLtxYy8l4/giphy.gif" - }, - { - "path": ".superdeck/generated/thumbnail_shPxXVHO.png", - "width": 512, - "height": 288 - }, - { - "path": ".superdeck/generated/thumbnail_nuiio8i0.png", - "width": 512, - "height": 288 - }, - { - "path": ".superdeck/generated/mermaid_s3Iic43G.png", - "width": 600, - "height": 866 - }, - { - "path": ".superdeck/generated/thumbnail_9aLrluQc.png", - "width": 512, - "height": 288 - }, - { - "path": ".superdeck/generated/thumbnail_xRHNCnNN.png", - "width": 512, - "height": 288 - } - ] -} \ No newline at end of file diff --git a/packages/superdeck/example/.superdeck/superdeck.json b/packages/superdeck/example/.superdeck/superdeck.json new file mode 100644 index 00000000..08a347cc --- /dev/null +++ b/packages/superdeck/example/.superdeck/superdeck.json @@ -0,0 +1,567 @@ +{ + "slides": [ + { + "key": "cS8UY7ii", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": "\n\n## Another image Another image Another image Another image Another image Another image Another image Another image Another image Another image {.here}\n", + "flex": 1, + "scrollable": false, + "type": "column" + }, + { + "name": "twitter", + "flex": 1, + "scrollable": false, + "type": "widget", + "username": "faafasdf", + "tweetId": 1746481414112256000 + }, + { + "content": "![mermaid_graph](.superdeck/assets/mermaid_srHRIuii.png) {.code}\n\n", + "flex": 1, + "scrollable": false, + "type": "column" + } + ], + "flex": 1, + "scrollable": false, + "type": "section" + } + ], + "comments": [ + "Test of notes" + ] + }, + { + "key": "nPPBLQ6k", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": "", + "flex": 1, + "scrollable": false, + "type": "column" + }, + { + "content": "\n# Hi\n", + "flex": 1, + "scrollable": false, + "type": "column" + }, + { + "content": "", + "flex": 1, + "scrollable": false, + "type": "column" + } + ], + "flex": 1, + "scrollable": false, + "type": "section" + } + ], + "comments": [] + }, + { + "key": "WyJ8NjmF", + "options": {}, + "sections": [ + { + "blocks": [], + "flex": 1, + "scrollable": false, + "type": "section" + }, + { + "blocks": [ + { + "content": "\n\n\n```dart\nColumn(\n crossAxisAlignment: CrossAxisAlignment.start,\n children: spans.map((span) {\n return RichText(\n text: TextSpan(\n style: interpolatedSpec.textStyle,\n children: [span],\n ),\n );\n }).toList(),\n)\n```{.code}\n", + "align": "center", + "flex": 2, + "scrollable": false, + "type": "column" + }, + { + "content": "![structured_output](https://picsum.photos/800/1400) {.cover}", + "flex": 1, + "scrollable": false, + "type": "column" + } + ], + "flex": 2, + "scrollable": false, + "type": "section" + } + ], + "comments": [] + }, + { + "key": "F2fTbXOG", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": "\n\n![mermaid_graph](.superdeck/assets/mermaid_srHRIuii.png) {.code}\n\n## Another image{.here}\n", + "flex": 1, + "scrollable": false, + "type": "column" + }, + { + "content": "![structured_output](https://picsum.photos/800/1400) {.cover}", + "flex": 1, + "scrollable": false, + "type": "column" + } + ], + "flex": 1, + "scrollable": false, + "type": "section" + } + ], + "comments": [] + }, + { + "key": "0zqy1l5c", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": "```dart\nColumn(\n crossAxisAlignment: CrossAxisAlignment.start,\n children: spans.map((span) {\n return RichText(\n text: TextSpan(\n style: interpolatedSpec.textStyle,\n children: [span],\n ),\n );\n }).toList(),\n)\n```{.code}", + "flex": 1, + "scrollable": false, + "type": "column" + } + ], + "flex": 1, + "scrollable": false, + "type": "section" + } + ], + "comments": [] + }, + { + "key": "RiDZbaFZ", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": "```dart\nColumn(\n children: []).toList(),\n);\n```{.code}", + "flex": 1, + "scrollable": false, + "type": "column" + } + ], + "flex": 1, + "scrollable": false, + "type": "section" + } + ], + "comments": [] + }, + { + "key": "z34aal1W", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": "\n\n![structured_output](https://picsum.photos/800/1400) {.cover}\n", + "flex": 1, + "scrollable": false, + "type": "column" + }, + { + "id": "idhere", + "embed": true, + "flex": 1, + "scrollable": false, + "type": "dartpad" + }, + { + "content": "## Yes different image{.here}", + "flex": 1, + "scrollable": false, + "type": "column" + } + ], + "flex": 1, + "scrollable": false, + "type": "section" + } + ], + "comments": [] + }, + { + "key": "H2GzZVSx", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": "\n\n#### Leo Farias\nleoafarias{.here}\n", + "align": "center_right", + "flex": 1, + "scrollable": false, + "type": "column" + }, + { + "content": "- Founder/CEO/CTO\n- Open Source Contributor\n- Flutter & Dart GDE\n- Passionate about UI/UX/DX", + "align": "center_left", + "flex": 1, + "scrollable": false, + "type": "column" + } + ], + "flex": 1, + "scrollable": false, + "type": "section" + } + ], + "comments": [] + }, + { + "key": "SJncL4H2", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": "", + "flex": 1, + "scrollable": false, + "type": "column" + }, + { + "content": "\n> [!WARNING] \n> This presentation contains live AI-generated content. Unexpected things may occur during the demonstration. \n", + "align": "center_left", + "flex": 2, + "scrollable": false, + "type": "column" + }, + { + "content": "", + "flex": 1, + "scrollable": false, + "type": "column" + } + ], + "flex": 1, + "scrollable": false, + "type": "section" + } + ], + "comments": [] + }, + { + "key": "ybLDY8oi", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": "\n### Generative UI\n", + "align": "center_right", + "flex": 2, + "scrollable": false, + "type": "column" + }, + { + "content": "\n## VS\n", + "flex": 1, + "scrollable": false, + "type": "column" + }, + { + "content": "### AI Assisted Code Generation", + "flex": 2, + "scrollable": false, + "type": "column" + } + ], + "flex": 1, + "scrollable": false, + "type": "section" + } + ], + "comments": [] + }, + { + "key": "aTAXFyQ7", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": "### What is Generative UI?{.animate}\n", + "flex": 1, + "scrollable": false, + "type": "column" + }, + { + "content": "- LLMs are great at generating content based on context\n- GUIs are great at providing structured, interactive interfaces for user input and navigation", + "flex": 1, + "scrollable": false, + "type": "column" + } + ], + "flex": 1, + "scrollable": false, + "type": "section" + } + ], + "comments": [] + }, + { + "key": "9mHDFwa9", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": "# LLM ❤️ GUI{.animate}", + "flex": 1, + "scrollable": false, + "type": "column" + } + ], + "flex": 1, + "scrollable": false, + "type": "section" + } + ], + "comments": [] + }, + { + "key": "9BmK4SPw", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": "", + "flex": 1, + "scrollable": false, + "type": "column" + }, + { + "content": "\nCreates dynamic, context-aware UIs by interpreting actions and maintaining state with LLMs for fluid, interactive responses.{.animate}\n", + "align": "center", + "flex": 2, + "scrollable": false, + "type": "column" + }, + { + "content": "", + "flex": 1, + "scrollable": false, + "type": "column" + } + ], + "flex": 1, + "scrollable": false, + "type": "section" + } + ], + "comments": [] + }, + { + "key": "3sLdrfsM", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": "### Benefits of UI over Chat{.animate}\n\n- More intuitive and user-friendly, especially for complex tasks\n- Faster feedback loop between users and LLMs\n- Enhances efficiency and interaction", + "flex": 1, + "scrollable": false, + "type": "column" + } + ], + "flex": 1, + "scrollable": false, + "type": "section" + } + ], + "comments": [] + }, + { + "key": "oglBIjM0", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": "\n### Flutter is Well-Suited
for Generative UI\nBuilt for any screen: Ideal for generating
adaptive UIs across devices and platforms.\n", + "align": "center", + "flex": 3, + "scrollable": false, + "type": "column" + }, + { + "content": "", + "flex": 1, + "scrollable": false, + "type": "column" + } + ], + "flex": 1, + "scrollable": false, + "type": "section" + } + ], + "comments": [] + }, + { + "key": "Z40wIUYP", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": "## How can LLMs Understtand Your UI?\n", + "flex": 1, + "scrollable": false, + "type": "column" + }, + { + "content": "![structured_output](assets/structured_output.png)", + "flex": 1, + "scrollable": false, + "type": "column" + } + ], + "flex": 1, + "scrollable": false, + "type": "section" + } + ], + "comments": [] + }, + { + "key": "9y5hBeTm", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": "\n\n### Structured Output\n", + "flex": 1, + "scrollable": false, + "type": "column" + }, + { + "content": "```dart\nfinal schema = Schema.array(\n description: 'List of recipes',\n items: Schema.object(\n properties: {\n 'recipeName': Schema.string(\n description: 'Name of the recipe.',\n nullable: false,\n ),\n },\n requiredProperties: ['recipeName'],\n ),\n);\n\n```", + "flex": 1, + "scrollable": false, + "type": "column" + } + ], + "flex": 1, + "scrollable": false, + "type": "section" + } + ], + "comments": [] + }, + { + "key": "CwxHOCpO", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": "```dart\nfinal model = GenerativeModel(\n model: 'gemini-1.5-pro',\n apiKey: apiKey,\n generationConfig: GenerationConfig(\n responseMimeType: 'application/json',\n responseSchema: schema,\n ),\n);\n\nfinal prompt = 'List a few popular cookie recipes.';\nfinal response = await model.generateContent([Content.text(prompt)]);\n\n```", + "flex": 1, + "scrollable": false, + "type": "column" + } + ], + "flex": 1, + "scrollable": false, + "type": "section" + } + ], + "comments": [] + }, + { + "key": "pukXIjvK", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": "### Color Palette Generator\n\nGenerate a color palette based on a given text.\n\n- Name of the palette\n- Font family\n- Font color\n- Color for each corner of the palette", + "flex": 1, + "scrollable": false, + "type": "column" + } + ], + "flex": 1, + "scrollable": false, + "type": "section" + } + ], + "comments": [] + }, + { + "key": "14RbmSW5", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": "```dart\nfinal schema = Schema.object(properties: {\n 'name': Schema.string(\n description:\n 'The text content to display on color palette. Format: #FF0000',\n nullable: false,\n ),\n 'font': Schema.enumString(\n enumValues: ColorPaletteFontFamily.enumString,\n description: 'The font to use for the poster text.',\n nullable: false,\n ),\n 'fontColor': Schema.string(\n description: 'The hex color value of the poster text. Format: #FF0000',\n nullable: false,\n ),\n 'topLeftColor': Schema.string(\n description:\n 'The hex color value top left corner of color palette. Format: #FF0000',\n nullable: false,\n ),\n 'topRightColor': Schema.string(\n description:\n 'The hex color value top right corner of color palette. Format: #FF0000',\n nullable: false,\n ),\n 'bottomLeftColor': Schema.string(\n description:\n 'The hex color value bottom left corner of color palette. Format: #FF0000',\n nullable: false,\n ),\n 'bottomRightColor': Schema.string(\n description:\n 'The hex color value bottom right corner of color palette. Format: #FF0000',\n nullable: false,\n )\n}, requiredProperties: [\n 'name',\n 'font',\n 'fontColor',\n 'topLeftColor',\n 'topRightColor',\n 'bottomLeftColor',\n 'bottomRightColor',\n]);\n\n```", + "flex": 1, + "scrollable": false, + "type": "column" + } + ], + "flex": 1, + "scrollable": false, + "type": "section" + } + ], + "comments": [] + }, + { + "key": "XeDZiCNk", + "options": { + "style": "demo" + }, + "sections": [ + { + "blocks": [ + { + "name": "colorPalette", + "flex": 1, + "scrollable": false, + "type": "widget", + "schema": true, + "prompts": [ + "tropical", + "vibrant", + "pastel", + "chocolatey pink unicorn", + "cyberpunk" + ] + } + ], + "flex": 1, + "scrollable": false, + "type": "section" + } + ], + "comments": [] + } + ], + "config": {} +} \ No newline at end of file diff --git a/packages/superdeck/example/assets/llm_interaction.png b/packages/superdeck/example/assets/llm_interaction.png new file mode 100644 index 00000000..fdafbf49 Binary files /dev/null and b/packages/superdeck/example/assets/llm_interaction.png differ diff --git a/packages/superdeck/example/assets/llm_tools.png b/packages/superdeck/example/assets/llm_tools.png new file mode 100644 index 00000000..1221ba54 Binary files /dev/null and b/packages/superdeck/example/assets/llm_tools.png differ diff --git a/packages/superdeck/example/assets/structured_output.png b/packages/superdeck/example/assets/structured_output.png new file mode 100644 index 00000000..b71b0103 Binary files /dev/null and b/packages/superdeck/example/assets/structured_output.png differ diff --git a/packages/superdeck/example/assets/widget_response.png b/packages/superdeck/example/assets/widget_response.png new file mode 100644 index 00000000..196928cd Binary files /dev/null and b/packages/superdeck/example/assets/widget_response.png differ diff --git a/packages/superdeck/example/lib/main.dart b/packages/superdeck/example/lib/main.dart index d1c5004c..8dea693f 100644 --- a/packages/superdeck/example/lib/main.dart +++ b/packages/superdeck/example/lib/main.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; import 'package:superdeck/superdeck.dart'; +import 'src/parts/background.dart'; +import 'src/parts/footer.dart'; +import 'src/parts/header.dart'; import 'src/style.dart'; -import 'src/widget/mix_demo.dart'; void main() async { await SuperDeckApp.initialize(); @@ -12,18 +14,49 @@ void main() async { title: 'Superdeck', debugShowCheckedModeBanner: false, home: SuperDeckApp( - styles: { - 'rad': radStyle, - 'custom': customStyle, - 'cover': coverStyle, - 'announcement': announcementStyle, - 'quote': quoteStyle, - 'show_sections': showSectionsStyle, - }, - // ignore: prefer_const_literals_to_create_immutables - examples: {'demo': mixExampleBuilder}, + options: DeckOptions( + baseStyle: BaseStyle(), + widgets: { + 'twitter': (args) { + return TwitterWidget( + username: args.getString('username'), + tweetId: args.getString('tweetId'), + ); + }, + }, + debug: false, + styles: { + 'announcement': AnnouncementStyle(), + 'quote': QuoteStyle(), + }, + parts: const SlideParts( + header: HeaderPart(), + footer: FooterPart(), + background: BackgroundPart(), + ), + ), ), ); }), ); } + +class TwitterWidget extends StatelessWidget { + final String username; + final String tweetId; + + const TwitterWidget( + {super.key, required this.username, required this.tweetId}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Container( + color: Colors.purple, + child: Text('Twitter: $username'), + ), + ], + ); + } +} diff --git a/packages/superdeck/example/lib/src/parts/background.dart b/packages/superdeck/example/lib/src/parts/background.dart new file mode 100644 index 00000000..f232fbd7 --- /dev/null +++ b/packages/superdeck/example/lib/src/parts/background.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:mesh/mesh.dart'; +import 'package:superdeck/superdeck.dart'; + +// Color _colorFromHex(String hexString) { +// hexString = hexString.trim(); +// if (hexString.isEmpty) { +// return Colors.black; // Default color if null or empty +// } +// hexString = hexString.replaceAll(RegExp(r'[^a-fA-F0-9]'), ''); +// hexString = hexString.replaceAll('#', ''); +// if (hexString.length == 6) { +// hexString = 'FF$hexString'; // Add opacity if not provided +// } +// return Color(int.parse(hexString, radix: 16)); +// } + +OMeshRect _meshBuilder(List colors) { + return OMeshRect( + width: 3, + height: 3, + fallbackColor: const Color(0xff0e0e0e), + backgroundColor: const Color(0x00d6d6d6), + vertices: [ + (0.0, 0.0).v, (0.5, 0.0).v, (1.0, 0.0).v, // Row 1 + + (0.0, 0.5).v, (0.5, 0.5).v, (1.0, 0.5).v, // Row 2 + + (0.0, 1.0).v, (0.5, 1.0).v, (1.0, 1.0).v, // Row 3 + ], + colors: colors, + ); +} + +class BackgroundPart extends StatelessWidget { + const BackgroundPart({ + super.key, + }); + + @override + Widget build(BuildContext context) { + final configuration = SlideConfiguration.of(context); + + return _AnimatedSwitcherOMesh( + slide: configuration, + ); + } +} + +// animate bwett colors and previous colors in duration +class _AnimatedSwitcherOMesh extends StatefulWidget { + final SlideConfiguration slide; + + const _AnimatedSwitcherOMesh({ + required this.slide, + }); + + @override + _AnimatedSwitcherOMeshState createState() => _AnimatedSwitcherOMeshState(); +} + +class _AnimatedSwitcherOMeshState extends State<_AnimatedSwitcherOMesh> + with SingleTickerProviderStateMixin { + late List _colors; + + final _duration = const Duration(milliseconds: 1000); + + @override + void initState() { + super.initState(); + _colors = _determiniscOrderBasedOnIndex(widget.slide.slideIndex); + } + + @override + void didUpdateWidget(covariant _AnimatedSwitcherOMesh oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.slide.slideIndex != oldWidget.slide.slideIndex) { + setState(() { + _colors = _determiniscOrderBasedOnIndex(widget.slide.slideIndex); + }); + } + } + + @override + Widget build(BuildContext context) { + return AnimatedOMeshGradient( + mesh: _meshBuilder(_colors), + duration: _duration, + ); + } +} + +final _buildColors = [ + const Color.fromARGB(255, 5, 5, 28), + const Color.fromARGB(255, 5, 5, 5), + const Color.fromARGB(255, 3, 19, 48), + const Color.fromARGB(255, 41, 12, 56), + const Color.fromARGB(255, 5, 5, 5), + const Color.fromARGB(255, 5, 5, 5), + const Color.fromARGB(255, 17, 0, 63), + const Color.fromARGB(255, 0, 0, 0), + const Color.fromARGB(255, 5, 5, 5), +]; +List _determiniscOrderBasedOnIndex(int index) { + return _buildColors.sublist(index % _buildColors.length) + ..addAll(_buildColors.sublist(0, index % _buildColors.length)); +} diff --git a/packages/superdeck/example/lib/src/parts/footer.dart b/packages/superdeck/example/lib/src/parts/footer.dart new file mode 100644 index 00000000..6686fb18 --- /dev/null +++ b/packages/superdeck/example/lib/src/parts/footer.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +class FooterPart extends StatelessWidget implements PreferredSizeWidget { + const FooterPart({ + super.key, + }); + + @override + Size get preferredSize => const Size.fromHeight(50); + + @override + Widget build(context) { + return const Padding( + padding: EdgeInsets.symmetric(horizontal: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text('SUPERDECK'), + ], + ), + ); + } +} diff --git a/packages/superdeck/example/lib/src/parts/header.dart b/packages/superdeck/example/lib/src/parts/header.dart new file mode 100644 index 00000000..442ca230 --- /dev/null +++ b/packages/superdeck/example/lib/src/parts/header.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:superdeck/superdeck.dart'; + +class HeaderPart extends StatelessWidget implements PreferredSizeWidget { + const HeaderPart({ + super.key, + }); + + @override + Size get preferredSize => const Size.fromHeight(50); + + @override + Widget build(context) { + final slide = SlideConfiguration.of(context); + + final index = slide.slideIndex; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text(slide.options.title ?? 'Flame Game'), + const SizedBox(width: 20), + Text('${index + 1}'), + ], + ), + ); + } +} diff --git a/packages/superdeck/example/lib/src/style.dart b/packages/superdeck/example/lib/src/style.dart index 6ee08e0c..ed99cead 100644 --- a/packages/superdeck/example/lib/src/style.dart +++ b/packages/superdeck/example/lib/src/style.dart @@ -1,182 +1,115 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:mix/mix.dart'; import 'package:superdeck/superdeck.dart'; -final _util = SlideSpecUtility.self; -final _h1 = _util.h1; -final _h2 = _util.h2; -final _h3 = _util.h3; +class BaseStyle extends DeckStyle { + BaseStyle(); -final _h6 = _util.h6; - -final _paragraph = _util.paragraph; - -final _code = _util.code; -final _blockquote = _util.blockquote; - -final _outerContainer = _util.outerContainer; -final _contentContainer = _util.contentContainer; - -final _textStyle = _util.textStyle; -final _innerContainer = _util.innerContainer; - -Style get radStyle { - return Style( - _h1.textStyle.as(GoogleFonts.poppins()), - _h1.textStyle.fontSize(140), - _code.decoration.border.all( - color: Colors.white, - width: 1, - ), - _code.decoration.color.black(), - _code.padding.all(40), - _outerContainer.margin.all(60), - _innerContainer.borderRadius(25), - _innerContainer.shadow( - blurRadius: 0, - spreadRadius: 10, - color: Colors.red.withOpacity(1), - ), - _innerContainer.gradient.radial( - stops: [0.0, 1.0], - radius: 0.7, - colors: [Colors.purple, Colors.deepPurple], - ), - $on.focus( - _innerContainer.color.yellow(), - ), - $on.hover.event((e) { - if (e == null) return const Style.empty(); - final position = e.position; - final dx = position.x * 10; - final dy = position.y * 10; - - return Style( - _innerContainer.transform(_transformMatrix(position)), - _innerContainer.shadow.offset(dx, dy), - _innerContainer.gradient.radial( - center: position, - ), - ); - }), - ($on.press | $on.longPress)( - _innerContainer.shadow( - blurRadius: 5, - spreadRadius: 1, - offset: Offset.zero, - color: Colors.purpleAccent, - ), - _innerContainer.border.all(color: Colors.white, width: 1), - _innerContainer.gradient.radial - .colors([Colors.purpleAccent, Colors.purpleAccent]), - ), - ); + @override + Style build() { + return super.build().merge( + Style( + // $.baseTextStyle.as(GoogleFonts.poppins()), + // $.h1.chain + // ..style.fontWeight.w900() + // ..style.fontSize(100), + // $.h2.chain + // ..style.fontWeight.w100() + // ..style.fontSize(80), + // $.h3.chain..style.fontSize(36), + // $.alert.all.chain + // ..heading.style.fontSize(24) + // ..icon.size(36) + // ..description.style.fontSize(24) + // ..description.style.fontWeight.w400(), + // $.alert.note.chain + // ..heading.style.color(_accent) + // ..icon.color(_accent) + // ..container.border.left.color(_accent), + // $.code.chain + // ..textStyle.as(GoogleFonts.jetBrainsMono()) + // ..decoration.color( + // const Color.fromARGB(255, 3, 17, 19), + // ) + // ..decoration.border.all( + // color: const Color.fromARGB(255, 6, 49, 50), + // width: 1, + // ), + ), + ); + } } -Style get customStyle { - return Style( - _textStyle.as(GoogleFonts.poppins()), - _h1.textStyle.as(GoogleFonts.smooch()), - _h1.textStyle.fontSize(200), - _h1.textStyle.height(0), - _h1.textStyle.shadow( - color: Colors.deepOrange, - blurRadius: 20, - ), - _h2.textStyle.fontSize(36), - _contentContainer.borderRadius(25), - _innerContainer.gradient.linear( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.black.withOpacity(0.5), - Colors.deepPurple.withOpacity(0.9), - ], - ), - _contentContainer.padding.vertical(0), - _outerContainer.padding(40), - _outerContainer.gradient.linear( - colors: [ - Colors.red, - Colors.redAccent, - ], - ), - _innerContainer.borderRadius(25), - _innerContainer.border.all( - color: Colors.deepOrange, - width: 4, - ), - _innerContainer.shadow( - color: Colors.black.withOpacity(0.4), - blurRadius: 20, - spreadRadius: 5, - ), - ); -} - -Style get coverStyle { - return Style( - _h1.textStyle.as(GoogleFonts.poppins()), - _h1.textStyle.fontSize(100), - _contentContainer.gradient.linear( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.black.withOpacity(0.5), - Colors.black.withOpacity(0.95), - ], - ), - ); -} - -Style get announcementStyle { - return Style( - _textStyle.height(0.6), - _h1.textStyle.fontSize(140), - _h1.textStyle.bold(), - _h1.textStyle.color(Colors.yellow), - _h2.textStyle.fontSize(140), - _h3.textStyle.fontSize(60), - _h3.textStyle.color(Colors.white), - _h3.textStyle.fontWeight(FontWeight.w100), - _contentContainer.gradient.linear( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.black.withOpacity(0.5), - Colors.black.withOpacity(0.95), - ], - ), - ); -} +class CoverStyle extends DeckStyle { + CoverStyle(); -Style get quoteStyle { - return Style( - _blockquote.textStyle.as(GoogleFonts.notoSerif()), - _blockquote.decoration.border.left( - width: 4, - color: Colors.red, - ), - _paragraph.textStyle.fontSize(32), - _h6.textStyle.as(GoogleFonts.notoSerif()), - _h6.textStyle.fontSize(20), - ); + @override + Style build() { + return super.build().merge( + Style( + $.h1.chain + ..style.as(GoogleFonts.poppins()) + ..style.fontSize(100), + $.blockContainer.gradient.linear( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.black.useOpacity(0.5), + Colors.black.useOpacity(0.95), + ], + ), + ), + ); + } } -Style get showSectionsStyle { - return Style( - _contentContainer.border.all( - color: Colors.blue, - width: 2, - ), - ); +class AnnouncementStyle extends DeckStyle { + AnnouncementStyle(); + @override + Style build() { + return super.build().merge( + Style( + $.baseTextStyle.height(0.6), + $.h1.chain + ..style.fontSize(140) + ..style.bold() + ..style.color(const Color.fromARGB(255, 201, 195, 139)), + $.h2.style.fontSize(140), + $.h3.chain + ..style.fontSize(60) + ..style.color(Colors.white) + ..style.fontWeight(FontWeight.w100), + $.blockContainer.gradient.linear( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.black.useOpacity(0.5), + Colors.black.useOpacity(0.95), + ], + ), + ), + ); + } } -Matrix4 _transformMatrix(Alignment alignment) { - final double rotateX = alignment.y * 0.2; - final double rotateY = -alignment.x * 0.2; - return Matrix4.identity() - ..rotateX(rotateX) - ..rotateY(rotateY) - ..translate(0.0, 0.0, 100.0); +class QuoteStyle extends DeckStyle { + QuoteStyle(); + @override + Style build() { + return super.build().merge( + Style( + $.blockquote.chain + ..textStyle.as(GoogleFonts.notoSerif()) + ..decoration.border.left( + width: 4, + color: Colors.red, + ), + $.p.style.fontSize(32), + $.h6.chain + ..style.as(GoogleFonts.notoSerif()) + ..style.fontSize(20), + ), + ); + } } diff --git a/packages/superdeck/example/lib/src/widget/mix_demo.dart b/packages/superdeck/example/lib/src/widget/mix_demo.dart deleted file mode 100644 index 0f8a2ffd..00000000 --- a/packages/superdeck/example/lib/src/widget/mix_demo.dart +++ /dev/null @@ -1,137 +0,0 @@ -import 'dart:math' as math; - -import 'package:flutter/material.dart'; -import 'package:superdeck/components/molecules/code_preview.dart'; -import 'package:superdeck/schema/schema_model.dart'; -import 'package:superdeck/superdeck.dart'; - -const purpleAccent = Color.fromARGB(255, 95, 44, 188); -const purple = Color.fromARGB(255, 66, 19, 152); - -Style get _style => Style( - // Box - $box.height(250), - $box.width(250), - $box.borderRadius.circular(10), - $box.alignment.center(), - $box.shadow( - blurRadius: 20, - spreadRadius: 10, - color: Colors.black.withOpacity(0.5), - ), - $box.gradient.radial( - stops: [0.0, 1.0], - radius: 0.7, - colors: [purpleAccent, purple], - ), - // Decorators - $with.scale(1.0), - $with.opacity(1), - // Text - $text.textAlign.center(), - $text.style.shadow.blurRadius(2), - $text.style( - color: Colors.white, - fontSize: 32, - ), - // Events - $on.hover.event((e) { - if (e == null) return const Style.empty(); - final position = e.position; - final dx = position.x * 10; - final dy = position.y * 10; - - final opacityDistance = _calculateDistance(position); - - return Style( - $text.style.shadow.color.black.withOpacity(opacityDistance), - $text.style.shadow.offset(-dx, -dy), - $box.transform(_transformMatrix(position)), - $box.shadow.offset(dx, dy), - $box.gradient.radial.center(position), - ); - }), - - $on.hover( - $box.color.black(), - ), - - ($on.press | $on.longPress)( - $box.shadow( - blurRadius: 5, - spreadRadius: 1, - offset: Offset.zero, - color: purpleAccent, - ), - $box.border(color: Colors.white, width: 1), - $box.gradient.radial.colors([purpleAccent, purpleAccent]), - $text.style.shadow.offset.zero(), - $with.scale(0.95), - ), - ); - -class ExampleOptions { - final double height; - final double width; - final String? text; - const ExampleOptions({ - required this.height, - required this.width, - this.text, - }); - - static ExampleOptions fromMap(Map map) { - return ExampleOptions( - height: map['height'] as double, - width: map['width'] as double, - text: map['text'] as String?, - ); - } - - static final schema = ArgsSchema( - validator: SchemaShape( - { - 'height': Schema.double.required(), - 'width': Schema.double.required(), - 'text': Schema.string.required(), - }, - ), - decoder: fromMap, - ); -} - -Widget mixExampleBuilder(BuildContext context) { - final options = ExampleOptions.fromMap(context.args); - return Builder( - builder: (context) { - return Center( - child: Box( - style: Style( - _style(), - $box.height(options.height), - $box.width(options.width), - ).animate(), - child: StyledText( - options.text ?? 'Mix', - ), - ), - ); - }, - ); -} - -double _calculateDistance(Alignment alignment) { - final distance = - -math.sqrt(alignment.x * alignment.x + alignment.y * alignment.y) / 1.5; - - return 1 - math.min(distance.abs(), 1); -} - -Matrix4 _transformMatrix(Alignment alignment) { - final double rotateX = alignment.y * 0.2; - final double rotateY = -alignment.x * 0.2; - return Matrix4.identity() - ..rotateX(rotateX) - ..rotateY(rotateY) - ..translate(0.0, 0.0, 100.0); -} diff --git a/packages/superdeck/example/linux/flutter/generated_plugin_registrant.cc b/packages/superdeck/example/linux/flutter/generated_plugin_registrant.cc index c84268e3..ebe9c0a3 100644 --- a/packages/superdeck/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/superdeck/example/linux/flutter/generated_plugin_registrant.cc @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include @@ -19,9 +19,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); - g_autoptr(FlPluginRegistrar) screen_retriever_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); - screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); + g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); + screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/packages/superdeck/example/linux/flutter/generated_plugins.cmake b/packages/superdeck/example/linux/flutter/generated_plugins.cmake index 66f0a374..5f72d152 100644 --- a/packages/superdeck/example/linux/flutter/generated_plugins.cmake +++ b/packages/superdeck/example/linux/flutter/generated_plugins.cmake @@ -5,7 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_saver file_selector_linux - screen_retriever + screen_retriever_linux url_launcher_linux window_manager ) diff --git a/packages/superdeck/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/superdeck/example/macos/Flutter/GeneratedPluginRegistrant.swift index 811a3726..2a2a3c4f 100644 --- a/packages/superdeck/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/packages/superdeck/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,20 +5,24 @@ import FlutterMacOS import Foundation +import file_picker import file_saver import file_selector_macos import path_provider_foundation -import screen_retriever -import sqflite +import screen_retriever_macos +import sqflite_darwin import url_launcher_macos +import webview_flutter_wkwebview import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) - ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) + ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) } diff --git a/packages/superdeck/example/macos/Podfile.lock b/packages/superdeck/example/macos/Podfile.lock index 4365112a..0e823b21 100644 --- a/packages/superdeck/example/macos/Podfile.lock +++ b/packages/superdeck/example/macos/Podfile.lock @@ -1,4 +1,6 @@ PODS: + - file_picker (0.0.1): + - FlutterMacOS - file_saver (0.0.1): - FlutterMacOS - file_selector_macos (0.0.1): @@ -7,27 +9,34 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - screen_retriever (0.0.1): + - screen_retriever_macos (0.0.1): - FlutterMacOS - - sqflite (0.0.3): + - sqflite_darwin (0.0.4): - Flutter - FlutterMacOS - url_launcher_macos (0.0.1): - FlutterMacOS + - webview_flutter_wkwebview (0.0.1): + - Flutter + - FlutterMacOS - window_manager (0.2.0): - FlutterMacOS DEPENDENCIES: + - file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`) - file_saver (from `Flutter/ephemeral/.symlinks/plugins/file_saver/macos`) - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - - screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`) - - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`) + - screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`) + - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) + - webview_flutter_wkwebview (from `Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin`) - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) EXTERNAL SOURCES: + file_picker: + :path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos file_saver: :path: Flutter/ephemeral/.symlinks/plugins/file_saver/macos file_selector_macos: @@ -36,25 +45,29 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral path_provider_foundation: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin - screen_retriever: - :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos - sqflite: - :path: Flutter/ephemeral/.symlinks/plugins/sqflite/darwin + screen_retriever_macos: + :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos + sqflite_darwin: + :path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos + webview_flutter_wkwebview: + :path: Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin window_manager: :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos SPEC CHECKSUMS: - file_saver: 44e6fbf666677faf097302460e214e977fdd977b - file_selector_macos: 54fdab7caa3ac3fc43c9fac4d7d8d231277f8cf2 + file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a + file_saver: e35bd97de451dde55ff8c38862ed7ad0f3418d0f + file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38 - sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec - url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399 - window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 + webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2 + window_manager: 1d01fa7ac65a6e6f83b965471b1a7fdd3f06166c PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/packages/superdeck/example/macos/Runner.xcodeproj/project.pbxproj b/packages/superdeck/example/macos/Runner.xcodeproj/project.pbxproj index 652a4230..8de28ead 100644 --- a/packages/superdeck/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/superdeck/example/macos/Runner.xcodeproj/project.pbxproj @@ -27,8 +27,8 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - D33EC936C6603F092BE164F8 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58206CAA5C9CAE5E71ACD682 /* Pods_Runner.framework */; }; - DC6D6673B8F35A47BEC03AAD /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C6840468B539A3E04B679E96 /* Pods_RunnerTests.framework */; }; + 3C7F02F531EE8B8E96FDCD85 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 67704BDC61896D7D1ED432F9 /* Pods_Runner.framework */; }; + FA047FDAACF132C8FDCAE309 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E600C98B2532AC2785701EE /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -62,6 +62,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 1E600C98B2532AC2785701EE /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 21511E81602A801A8E21C7CD /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; @@ -78,16 +80,14 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 58206CAA5C9CAE5E71ACD682 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 52F74288AF9AF86ABCFE970F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 581C05F03DD9D309B121AAEE /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 67704BDC61896D7D1ED432F9 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 70C3E6D656304B99FD0B1289 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; - 96D02405CAB3337A8B12E29B /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 82524DD4DD10587301372C85 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; - B9852E5A6795285C786AC0A5 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; - C6840468B539A3E04B679E96 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D549EC79AC8D2DF1C27D4870 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - DA04FC470987408FF8C57926 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - E2BD03C4940642B10DF984FF /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - FEF4F6D79728354A6C21CACE /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + D8DAC3BC7E807CD0F32FD395 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -95,7 +95,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DC6D6673B8F35A47BEC03AAD /* Pods_RunnerTests.framework in Frameworks */, + FA047FDAACF132C8FDCAE309 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -103,7 +103,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D33EC936C6603F092BE164F8 /* Pods_Runner.framework in Frameworks */, + 3C7F02F531EE8B8E96FDCD85 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -137,7 +137,7 @@ 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, - D5950B1D48D0D7CEF81BA26B /* Pods */, + EE39A6A2A55017D82C9C3078 /* Pods */, ); sourceTree = ""; }; @@ -185,27 +185,27 @@ path = Runner; sourceTree = ""; }; - D5950B1D48D0D7CEF81BA26B /* Pods */ = { + D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( - FEF4F6D79728354A6C21CACE /* Pods-Runner.debug.xcconfig */, - E2BD03C4940642B10DF984FF /* Pods-Runner.release.xcconfig */, - DA04FC470987408FF8C57926 /* Pods-Runner.profile.xcconfig */, - 96D02405CAB3337A8B12E29B /* Pods-RunnerTests.debug.xcconfig */, - D549EC79AC8D2DF1C27D4870 /* Pods-RunnerTests.release.xcconfig */, - B9852E5A6795285C786AC0A5 /* Pods-RunnerTests.profile.xcconfig */, + 67704BDC61896D7D1ED432F9 /* Pods_Runner.framework */, + 1E600C98B2532AC2785701EE /* Pods_RunnerTests.framework */, ); - name = Pods; - path = Pods; + name = Frameworks; sourceTree = ""; }; - D73912EC22F37F3D000D13A0 /* Frameworks */ = { + EE39A6A2A55017D82C9C3078 /* Pods */ = { isa = PBXGroup; children = ( - 58206CAA5C9CAE5E71ACD682 /* Pods_Runner.framework */, - C6840468B539A3E04B679E96 /* Pods_RunnerTests.framework */, + 21511E81602A801A8E21C7CD /* Pods-Runner.debug.xcconfig */, + 52F74288AF9AF86ABCFE970F /* Pods-Runner.release.xcconfig */, + 70C3E6D656304B99FD0B1289 /* Pods-Runner.profile.xcconfig */, + D8DAC3BC7E807CD0F32FD395 /* Pods-RunnerTests.debug.xcconfig */, + 82524DD4DD10587301372C85 /* Pods-RunnerTests.release.xcconfig */, + 581C05F03DD9D309B121AAEE /* Pods-RunnerTests.profile.xcconfig */, ); - name = Frameworks; + name = Pods; + path = Pods; sourceTree = ""; }; /* End PBXGroup section */ @@ -215,7 +215,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - 19EEF7D0A3B09786CF680AFC /* [CP] Check Pods Manifest.lock */, + E1EAB9F2FA8DCA9689F733BC /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -234,13 +234,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - D54F2E16B80C97964B7F5826 /* [CP] Check Pods Manifest.lock */, + 1EDCDE1967A52C0C5A85096D /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, - C22D8E44EE0F21092537C376 /* [CP] Embed Pods Frameworks */, + 835DD4EE6B9B696A485C6794 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -323,7 +323,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 19EEF7D0A3B09786CF680AFC /* [CP] Check Pods Manifest.lock */ = { + 1EDCDE1967A52C0C5A85096D /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -338,7 +338,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -383,7 +383,7 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; - C22D8E44EE0F21092537C376 /* [CP] Embed Pods Frameworks */ = { + 835DD4EE6B9B696A485C6794 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -400,7 +400,7 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - D54F2E16B80C97964B7F5826 /* [CP] Check Pods Manifest.lock */ = { + E1EAB9F2FA8DCA9689F733BC /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -415,7 +415,7 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -473,7 +473,7 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 96D02405CAB3337A8B12E29B /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = D8DAC3BC7E807CD0F32FD395 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -488,7 +488,7 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D549EC79AC8D2DF1C27D4870 /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = 82524DD4DD10587301372C85 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -503,7 +503,7 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B9852E5A6795285C786AC0A5 /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = 581C05F03DD9D309B121AAEE /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; diff --git a/packages/superdeck/example/macos/Runner/AppDelegate.swift b/packages/superdeck/example/macos/Runner/AppDelegate.swift index 8e02df28..b3c17614 100644 --- a/packages/superdeck/example/macos/Runner/AppDelegate.swift +++ b/packages/superdeck/example/macos/Runner/AppDelegate.swift @@ -6,4 +6,8 @@ class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/packages/superdeck/example/macos/Runner/Release.entitlements b/packages/superdeck/example/macos/Runner/Release.entitlements index 08ba3a3f..85c03d7b 100644 --- a/packages/superdeck/example/macos/Runner/Release.entitlements +++ b/packages/superdeck/example/macos/Runner/Release.entitlements @@ -3,8 +3,12 @@ com.apple.security.app-sandbox - + + com.apple.security.files.user-selected.read-write + com.apple.security.network.client + com.apple.security.network.server + diff --git a/packages/superdeck/example/pubspec.yaml b/packages/superdeck/example/pubspec.yaml index 0bdfffe6..14fbca1f 100644 --- a/packages/superdeck/example/pubspec.yaml +++ b/packages/superdeck/example/pubspec.yaml @@ -1,29 +1,25 @@ -name: superdeck_example +name: 'superdeck_example' description: An example presentation for SuperDeck - -publish_to: "none" # Remove this line if you wish to publish to pub.dev - -version: 1.0.0+1 - +publish_to: none +version: '1.0.0+1' environment: - sdk: ">=3.3.0 <4.0.0" - + sdk: '>=3.3.0 <4.0.0' + flutter: '>=3.19.0' dependencies: flutter: sdk: flutter - google_fonts: ^6.2.0 - superdeck: - path: ../ - + google_fonts: '^6.2.1' + mesh: '^0.4.1' + mix: '^1.5.4' + superdeck: '^0.0.1' dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^3.0.2 - superdeck_cli: - path: ../../superdeck_cli - + flutter_lints: '^4.0.0' + superdeck_cli: '^0.0.1' flutter: uses-material-design: true assets: + - assets/ - .superdeck/ - - .superdeck/generated/ \ No newline at end of file + - .superdeck/assets/ diff --git a/packages/superdeck/example/pubspec_overrides.yaml b/packages/superdeck/example/pubspec_overrides.yaml index 32c9384f..926997bd 100644 --- a/packages/superdeck/example/pubspec_overrides.yaml +++ b/packages/superdeck/example/pubspec_overrides.yaml @@ -1,10 +1,8 @@ -# melos_managed_dependency_overrides: superdeck,superdeck_cli +# melos_managed_dependency_overrides: superdeck,superdeck_cli,superdeck_core dependency_overrides: - # mix_generator: - # path: ../../mix/packages/mix_generator - # mix: - # path: ../../mix/packages/mix superdeck: path: .. superdeck_cli: path: ../../superdeck_cli + superdeck_core: + path: ../../superdeck_core diff --git a/packages/superdeck/example/slides.md b/packages/superdeck/example/slides.md index d1b447f4..963a5d8c 100644 --- a/packages/superdeck/example/slides.md +++ b/packages/superdeck/example/slides.md @@ -1,324 +1,335 @@ --- -style: quote -layout: image -options: - src: https://picsum.photos/600/600.webp - fit: cover -content: - alignment: bottom_right ---- -> Create your Flutter presentations faster and easier than ever. -> You can quote me on that -> ### Leo Farias +@column ---- -background: https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZGt1MnQ5N2k3cXVma24wb3V5cThlZ3ExY2NvY3czcmozang0bGQ1ZSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/XzWd8acQ37byKR4tmd/giphy.gif -style: cover ---- +## Another image Another image Another image Another image Another image Another image Another image Another image Another image Another image {.here} -# Complex layout +@twitter { + username: faafasdf + tweetId: 1746481414112256000 +} ---- -layout: image -content: - alignment: bottom_right - flex: 1 -style: show_sections -options: - src: https://picsum.photos/900/700?waves - fit: cover - position: left - flex: 1 ---- +@column -## Image Layout +```mermaid +graph TD + A[Start] --> B[Input] + B --> C[Process] + C --> D[Output] + D --> E[End] +``` {.code} -Create beautiful slides with images that fit your content. + -##### Options -```yaml -content: -options: - src: https//www.url.com/image.jpg - fit: cover - position: left - flex: 1 -``` +--- -> Define position fit and flex options for the image. +@column + +@column +# Hi +@column ---- -layout: two_column -style: show_sections -sections: - left: - flex: 2 - right: - alignment: bottom_left --- -::left:: +@section +@section {flex: 2} +@column { + align: center + flex: 2 +} -# Two Column -This is a two-column layout. You can use it to compare two different concepts or ideas. +```dart +Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: spans.map((span) { + return RichText( + text: TextSpan( + style: interpolatedSpec.textStyle, + children: [span], + ), + ); + }).toList(), +) +```{.code} -::right:: +@column -### Section Options +![structured_output](https://picsum.photos/800/1400) {.cover} -Easily customize the content of each section to suit your needs. +--- -Use front matter to define the layout of each section +@column -```yaml -sections: - left: - flex: 2 - right: - alignment: bottom_left -``` +```mermaid +graph TD + A[Start] --> B[Input] + B --> C[Process] + C --> D[Output] + D --> E[End] +``` {.code} + +## Another image{.here} + +@column + +![structured_output](https://picsum.photos/800/1400) {.cover} --- -layout: two_column_header -content: - alignment: center - flex: 2 -sections: - left: - flex: 2 - right: - alignment: bottom_left - header: - alignment: bottom_left -style: show_sections + +```dart +Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: spans.map((span) { + return RichText( + text: TextSpan( + style: interpolatedSpec.textStyle, + children: [span], + ), + ); + }).toList(), +) +```{.code} + --- -# Two Column + Header +```dart +Column( + children: []).toList(), +); +```{.code} +--- -::left:: +@column -### Left Section -Easily customize the content of each section to suit your needs. +![structured_output](https://picsum.photos/800/1400) {.cover} -Use front matter to define the layout of each section -::right:: +@dartpad { + id: idhere + code: "" +} -#### Section Options +@column -```yaml -sections: - left: - alignment: bottom_right - flex: 2 - right: - alignment: bottom_left - header: - alignment: bottom_left -``` - +## Yes different image{.here} ---- -style: rad -layout: two_column -content: - alignment: center -sections: - left: - right: - alignment: bottom_left - flex: 2 --- -# Mix +@column { + align: center_right +} -Integration with Mix gives you complete control over all styling elements in your slides with a simple and intuitive API. +#### Leo Farias +leoafarias{.here} -::right:: -```dart -VariantAttribute get radStyle { - return const SlideVariant('rad')( - $.h1.textStyle.as(GoogleFonts.poppins()), - $.h1.textStyle.fontSize(140), - $.code.decoration.border.all( - color: Colors.red, - width: 3, - ), - $.code.decoration( - color: Colors.black54, - ), - $.code.padding.all(40), - - $.outerContainer.margin.all(60), - - $.innerContainer.borderRadius(25), - $.innerContainer.shadow( - blurRadius: 0, - spreadRadius: 10, - color: Colors.red.withOpacity(1), - ), - $.innerContainer.gradient.radial( - stops: [0.0, 1.0], - radius: 0.7, - colors: [Colors.purple, Colors.deepPurple], - ), - - // Events - onMouseHover((event) { - final position = event.position; - final dx = position.x * 10; - final dy = position.y * 10; - - return Style( - $.innerContainer.transform(_transformMatrix(position)), - $.innerContainer.shadow.offset(dx, dy), - $.innerContainer.gradient.radial( - center: position, - ), - ); - }), - - (onPressed | onLongPressed)( - $.innerContainer.shadow( - blurRadius: 5, - spreadRadius: 1, - offset: Offset.zero, - color: Colors.purpleAccent, - ), - $.innerContainer.border.all(color: Colors.white, width: 1), - $.innerContainer.gradient.radial - .colors([Colors.purpleAccent, Colors.purpleAccent]), - ), - ); +@column { + align: center_left } -``` +- Founder/CEO/CTO +- Open Source Contributor +- Flutter & Dart GDE +- Passionate about UI/UX/DX ---- -style: cover -background: https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExeGswdWJvY2oxazJoY3g2Y2poNHBvZXlpYmd5YTg0Z2g0ODRrbng4MyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/oB6KlAvOuaLtxYy8l4/giphy.gif --- -# Markdown support +@column + +@column { + align: center_left + flex: 2 +} +> [!WARNING] +> This presentation contains live AI-generated content. Unexpected things may occur during the demonstration. + +@column ---- -style: show_sections -layout: two_column -sections: -content: - flex: 4 --- -::left:: -**Bold Text** +@column { + flex: 2 + align: center_right +} +### Generative UI +@column +## VS +@column { + flex: 2 +} +### AI Assisted Code Generation -*Italic Text* +--- -~~Strikethrough~~ -`Inline Code` +### What is Generative UI?{.animate} -[Link here](https://github.com/leoafarias/superdeck) +@column -::right:: +- LLMs are great at generating content based on context +- GUIs are great at providing structured, interactive interfaces for user input and navigation -Lists +--- -1. Ordered list item 1 -2. Ordered list item 2 +# LLM ❤️ GUI{.animate} -- Unordered list item 1 -- Unordered list item 2 -Quotes +--- -> If you want to go fast, go alone. -> If you want to go far, go together. -> ### African Proverb +@column +@column { + flex: 2 + align: center +} +Creates dynamic, context-aware UIs by interpreting actions and maintaining state with LLMs for fluid, interactive responses.{.animate} +@column --- -layout: two_column + +### Benefits of UI over Chat{.animate} + +- More intuitive and user-friendly, especially for complex tasks +- Faster feedback loop between users and LLMs +- Enhances efficiency and interaction + --- -::left:: -Code -```dart -int factorial(int n) { - return n == 0 ? 1 : n * factorial(n - 1); +@column { + flex: 3 + align: center } +### Flutter is Well-Suited
for Generative UI +Built for any screen: Ideal for generating
adaptive UIs across devices and platforms. +@column -``` +--- -Tasks -- [ ] Item 1 -- [x] Item 2 -Subtasks +## How can LLMs Understtand Your UI? -- [x] Item 1 - - [ ] Subitem 1 +@column +![structured_output](assets/structured_output.png) -::right:: -Images -![Unsplash Image](https://picsum.photos/300/200?landscape) +--- +@column -Table +### Structured Output -| Header 1 | Header 2 | -|----------|----------| -| Cell 1A | Cell 1B | -| Cell 2A | Cell 2B | +@column +```dart +final schema = Schema.array( + description: 'List of recipes', + items: Schema.object( + properties: { + 'recipeName': Schema.string( + description: 'Name of the recipe.', + nullable: false, + ), + }, + requiredProperties: ['recipeName'], + ), +); +``` -Divider +--- -___ +@section +```dart +final model = GenerativeModel( + model: 'gemini-1.5-pro', + apiKey: apiKey, + generationConfig: GenerationConfig( + responseMimeType: 'application/json', + responseSchema: schema, + ), +); +final prompt = 'List a few popular cookie recipes.'; +final response = await model.generateContent([Content.text(prompt)]); + +``` ---- -title: "Mermaid example" -layout: two_column --- -::left:: +@column +### Color Palette Generator -```mermaid -flowchart TD - A[This is crazy] -->|Get money| B(Go shopping) - B --> C{Let me car} - C -->|One| D[Laptop] - C -->|Two| E[iPhone] - C -->|Three| F[fa:fa-car Car] -``` - +Generate a color palette based on a given text. -::right:: +- Name of the palette +- Font family +- Font color +- Color for each corner of the palette -## Mermaid Support +--- -Superdeck allows you to use Mermaid diagrams in your slides. It automatically converts the code into a visual representation. +@column +```dart +final schema = Schema.object(properties: { + 'name': Schema.string( + description: + 'The text content to display on color palette. Format: #FF0000', + nullable: false, + ), + 'font': Schema.enumString( + enumValues: ColorPaletteFontFamily.enumString, + description: 'The font to use for the poster text.', + nullable: false, + ), + 'fontColor': Schema.string( + description: 'The hex color value of the poster text. Format: #FF0000', + nullable: false, + ), + 'topLeftColor': Schema.string( + description: + 'The hex color value top left corner of color palette. Format: #FF0000', + nullable: false, + ), + 'topRightColor': Schema.string( + description: + 'The hex color value top right corner of color palette. Format: #FF0000', + nullable: false, + ), + 'bottomLeftColor': Schema.string( + description: + 'The hex color value bottom left corner of color palette. Format: #FF0000', + nullable: false, + ), + 'bottomRightColor': Schema.string( + description: + 'The hex color value bottom right corner of color palette. Format: #FF0000', + nullable: false, + ) +}, requiredProperties: [ + 'name', + 'font', + 'fontColor', + 'topLeftColor', + 'topRightColor', + 'bottomLeftColor', + 'bottomRightColor', +]); + +``` --- -layout: widget -options: - name: demo - args: - text: Hello, Superdeck! - height: 200.0 - width: 300.0 +style: demo --- -## Showcase your widgets \ No newline at end of file +@colorPalette { + schema: true + prompts: [tropical, + vibrant, pastel, chocolatey pink unicorn, cyberpunk] +} diff --git a/packages/superdeck/example/superdeck.yaml b/packages/superdeck/example/superdeck.yaml index 26545c0a..e69de29b 100644 --- a/packages/superdeck/example/superdeck.yaml +++ b/packages/superdeck/example/superdeck.yaml @@ -1,4 +0,0 @@ -transition: - type: fade_in - duration: 0 - \ No newline at end of file diff --git a/packages/superdeck/example/windows/flutter/generated_plugin_registrant.cc b/packages/superdeck/example/windows/flutter/generated_plugin_registrant.cc index 4176f5bc..29ea91d3 100644 --- a/packages/superdeck/example/windows/flutter/generated_plugin_registrant.cc +++ b/packages/superdeck/example/windows/flutter/generated_plugin_registrant.cc @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include @@ -17,8 +17,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FileSaverPlugin")); FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); - ScreenRetrieverPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); + ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); WindowManagerPluginRegisterWithRegistrar( diff --git a/packages/superdeck/example/windows/flutter/generated_plugins.cmake b/packages/superdeck/example/windows/flutter/generated_plugins.cmake index cfabaeb0..7af4a589 100644 --- a/packages/superdeck/example/windows/flutter/generated_plugins.cmake +++ b/packages/superdeck/example/windows/flutter/generated_plugins.cmake @@ -5,7 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_saver file_selector_windows - screen_retriever + screen_retriever_windows url_launcher_windows window_manager ) diff --git a/packages/superdeck/lib/chat/chat_theme.dart b/packages/superdeck/lib/chat/chat_theme.dart deleted file mode 100644 index 0fbb4e6e..00000000 --- a/packages/superdeck/lib/chat/chat_theme.dart +++ /dev/null @@ -1,113 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_chat_ui/flutter_chat_ui.dart'; - -import '../helpers/extensions.dart'; - -final _baseTheme = DarkChatTheme(); - -ChatTheme buildChatTheme(BuildContext context) { - return DarkChatTheme( - backgroundColor: context.colorScheme.surface, - inputSurfaceTintColor: Colors.black, - dateDividerTextStyle: TextStyle( - color: context.colorScheme.onSurface, - fontSize: 12, - fontWeight: FontWeight.w800, - height: 1.333, - ), - emptyChatPlaceholderTextStyle: TextStyle( - color: context.colorScheme.onSurface.withOpacity(0.6), - fontSize: 16, - fontWeight: FontWeight.w500, - height: 1.5, - ), - errorColor: context.colorScheme.error, - inputBackgroundColor: Colors.black, - inputBorderRadius: BorderRadius.zero, - inputTextColor: context.colorScheme.onSurface, - primaryColor: context.colorScheme.primary, - receivedMessageBodyTextStyle: TextStyle( - color: context.colorScheme.onSecondary, - fontSize: 16, - fontWeight: FontWeight.w500, - height: 1.5, - ), - receivedMessageCaptionTextStyle: TextStyle( - color: context.colorScheme.onSecondary.withOpacity(0.6), - fontSize: 12, - fontWeight: FontWeight.w500, - height: 1.333, - ), - receivedMessageDocumentIconColor: context.colorScheme.primary, - receivedMessageLinkDescriptionTextStyle: TextStyle( - color: context.colorScheme.onSecondary, - fontSize: 14, - fontWeight: FontWeight.w400, - height: 1.428, - ), - receivedMessageLinkTitleTextStyle: TextStyle( - color: context.colorScheme.onSecondary, - fontSize: 16, - fontWeight: FontWeight.w800, - height: 1.375, - ), - secondaryColor: context.colorScheme.secondary, - sentMessageBodyTextStyle: TextStyle( - color: context.colorScheme.onPrimary, - fontSize: 16, - fontWeight: FontWeight.w500, - height: 1.5, - ), - sentMessageCaptionTextStyle: TextStyle( - color: context.colorScheme.onPrimary.withOpacity(0.6), - fontSize: 12, - fontWeight: FontWeight.w500, - height: 1.333, - ), - sentMessageDocumentIconColor: context.colorScheme.onPrimary, - sentMessageLinkDescriptionTextStyle: TextStyle( - color: context.colorScheme.onPrimary, - fontSize: 14, - fontWeight: FontWeight.w400, - height: 1.428, - ), - sentMessageLinkTitleTextStyle: TextStyle( - color: context.colorScheme.onPrimary, - fontSize: 16, - fontWeight: FontWeight.w800, - height: 1.375, - ), - // systemMessageTheme: SystemMessageTheme( - // textStyle: TextStyle( - // color: context.colorScheme.onSurface, - // fontSize: 12, - // fontWeight: FontWeight.w800, - // height: 1.333, - // ), - // ), - // typingIndicatorTheme: TypingIndicatorTheme( - // animatedCirclesColor: context.colorScheme.onSurface, - // bubbleColor: context.colorScheme.surface, - // countTextColor: context.colorScheme.onPrimary, - // animatedCircleSize: _baseTheme.typingIndicatorTheme.animatedCircleSize, - // bubbleBorder: _baseTheme.typingIndicatorTheme.bubbleBorder, - // countAvatarColor: context.colorScheme.primary, - // multipleUserTextStyle: context.textTheme.bodySmall!, - // ), - unreadHeaderTheme: UnreadHeaderTheme( - color: context.colorScheme.secondary, - textStyle: TextStyle( - color: context.colorScheme.onSecondary.withOpacity(0.6), - fontSize: 12, - fontWeight: FontWeight.w500, - height: 1.333, - ), - ), - userAvatarTextStyle: TextStyle( - color: context.colorScheme.onPrimary, - fontSize: 12, - fontWeight: FontWeight.w800, - height: 1.333, - ), - ); -} diff --git a/packages/superdeck/lib/chat/components/typing_indicator.dart b/packages/superdeck/lib/chat/components/typing_indicator.dart deleted file mode 100644 index 14d3c0c2..00000000 --- a/packages/superdeck/lib/chat/components/typing_indicator.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -class WaitingIndicator extends StatefulWidget { - final bool isTyping; - - const WaitingIndicator({Key? key, required this.isTyping}) : super(key: key); - - @override - State createState() => _WaitingIndicatorState(); -} - -class _WaitingIndicatorState extends State { - late Timer _timer; - final List _dotSizes = [6, 8, 10]; - - @override - void initState() { - super.initState(); - _startTimer(); - } - - @override - void dispose() { - _stopTimer(); - super.dispose(); - } - - void _startTimer() { - _timer = Timer.periodic(const Duration(milliseconds: 500), (_) { - setState(() { - _dotSizes.insert(0, _dotSizes.removeLast()); - }); - }); - } - - void _stopTimer() { - _timer.cancel(); - } - - @override - Widget build(BuildContext context) { - return widget.isTyping - ? Row( - mainAxisSize: MainAxisSize.min, - children: [ - for (final size in _dotSizes) - Padding( - padding: const EdgeInsets.only(right: 4), - child: AnimatedContainer( - duration: const Duration(milliseconds: 500), - curve: Curves.easeInOut, - height: size, - width: size, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.grey[400], - ), - ), - ), - ], - ) - : const SizedBox.shrink(); - } -} diff --git a/packages/superdeck/lib/chat/prompt.dart b/packages/superdeck/lib/chat/prompt.dart deleted file mode 100644 index feb031d7..00000000 --- a/packages/superdeck/lib/chat/prompt.dart +++ /dev/null @@ -1,64 +0,0 @@ -final presentationAssistantPrompt = ''' - - - -**CONTEXT:** - -You are a markdown presentation assistance bot designed to help users create, refine, and optimize markdown-based slides. Users often use frontmatter to define styles, layouts, and various options within their presentations. Your task is to assist in adjusting these elements to improve the overall presentation while providing feedback and suggestions on specific slide numbers. - -**OBJECTIVE:** - -To guide users in enhancing their markdown presentations by suggesting improvements to the structure, layout, and style. When proposing changes, you should clearly reference the slide number where the change is suggested. Additionally, ensure that the user is aware of available frontmatter options that can be utilized for better customization. - -**STYLE:** -Never change the frontmatter structure, or any of the image urls. -Always focus on one specific slide and fixes before moving to the next changes. -Provide feedback in a concise and instructional manner, ensuring that each suggestion is actionable. The feedback should focus on optimizing the presentation's readability, visual appeal, and effectiveness in conveying the intended message. - -**TONE:** - -Professional and supportive. Encourage the user by acknowledging their current efforts and offering constructive advice to elevate the presentation. - -**AUDIENCE:** - -The audience comprises users who are actively working on creating markdown-based presentations. They may have varying levels of experience with markdown and frontmatter customization, so explanations should be clear and accessible. - -**REQUIRED FORMAT:** - -The suggestions are always conversational, you should should not provide code snippets. -Feedback and suggestions should be provided as text responses within the markdown file or as separate comments. Each suggestion must include a reference to the specific slide number being discussed. - -**STEP-BACK PROMPTING:** - -Before making specific suggestions, review the overall structure and style of the presentation. Consider the flow and coherence of the slides, as well as the effective use of frontmatter for layout and style consistency. Are there opportunities to better utilize frontmatter options to enhance the presentation's impact? - -**END RESULT PROMPTING:** - -Envision the final version of the markdown presentation after all suggested changes have been implemented. Describe how the presentation has improved in terms of clarity, visual appeal, and audience engagement. What specific changes have made the most significant impact, and how does the presentation now meet the user's original goals? -'''; - -final provideMarkdownEdits = ''' -CONTEXT: -You need a finalized Markdown document incorporating all feedback and ready for immediate use, without extra lines or spaces, or extra content in the response. - -OBJECTIVE: -Deliver a fully edited, complete Markdown file, ready to save and use with no further adjustments. - -STYLE: -Clear, concise, and well-structured, with a focus on precision. - -TONE: -Professional and straightforward, reflecting previous feedback. - -AUDIENCE: -Intended for immediate use by professionals needing the finalized content. - -REQUIRED FORMAT: -A complete and polished Markdown file, ready for saving. - -STEP-BACK PROMPTING: -Review the Markdown file to ensure all feedback is integrated and the content is complete and aligned with clarity goals. - -END RESULT PROMPTING: -Envision saving the file as the final version, reflecting all edits and ready for immediate use with no further modifications needed. -'''; diff --git a/packages/superdeck/lib/components/atoms/cache_image_widget.dart b/packages/superdeck/lib/components/atoms/cache_image_widget.dart deleted file mode 100644 index fd9ba9fe..00000000 --- a/packages/superdeck/lib/components/atoms/cache_image_widget.dart +++ /dev/null @@ -1,103 +0,0 @@ -import 'dart:io'; -import 'dart:math'; - -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; - -import '../../helpers/constants.dart'; -import '../../superdeck.dart'; - -class CacheImage extends StatelessWidget { - final String url; - final BoxFit? fit; - final ImageSpec spec; - final Alignment? alignment; - - const CacheImage({ - required this.url, - this.fit = BoxFit.cover, - this.alignment = Alignment.center, - this.spec = const ImageSpec(), - super.key, - }); - - @override - Widget build(BuildContext context) { - return AnimatedImageSpecWidget( - image: getImageProvider(url), - spec: spec.copyWith( - fit: fit, - alignment: alignment, - ), - ); - } -} - -({int? width, int? height}) calculateImageSize(Size size, SlideAsset? asset) { - int? cacheWidth; - int? cacheHeight; - // check if height or asset is larger - if (asset != null) { - // cache the smallest dimension of the image - // So set the other dimension to null - if (asset.isPortrait) { - cacheHeight = min(size.height, asset.height).toInt(); - } else { - cacheWidth = min(size.width, asset.width).toInt(); - } - } else { - // If no asset is available, set both cacheWidth and cacheHeight - final ifHeightIsBigger = size.height > size.width; - -// cache the smallest - if (ifHeightIsBigger) { - cacheWidth = size.width.toInt(); - } else { - cacheHeight = size.height.toInt(); - } - } - - return (width: cacheWidth, height: cacheHeight); -} - -ImageProvider getImageProvider(String url, {Size? targetSize}) { - ImageProvider provider; - - final assets = $superdeck.assets; - - final assetUrl = assets.firstWhereOrNull((e) { - if (e.path == url) { - return true; - } - - if (e.reference == url) { - return true; - } - - return false; - }); - - url = assetUrl?.path ?? url; - - // check if its a local path or a network path - if (url.startsWith('http')) { - provider = CachedNetworkImageProvider(url); - } else { - if (kCanRunProcess) { - final file = File(url); - provider = FileImage(file); - } else { - provider = AssetImage(url); - } - } - - final (:width, :height) = - calculateImageSize(targetSize ?? kResolution, assetUrl); - - return ResizeImage.resizeIfNeeded( - width, - height, - provider, - ); -} diff --git a/packages/superdeck/lib/components/atoms/linear_progresss_indicator_widget.dart b/packages/superdeck/lib/components/atoms/linear_progresss_indicator_widget.dart deleted file mode 100644 index f2f9564d..00000000 --- a/packages/superdeck/lib/components/atoms/linear_progresss_indicator_widget.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; - -class AnimatedLinearProgressIndicator extends HookWidget { - final double progress; - - const AnimatedLinearProgressIndicator({ - super.key, - required this.progress, - }); - - @override - Widget build(BuildContext context) { - final animationController = useAnimationController( - duration: const Duration(milliseconds: 100), - ); - - final animation = - Tween(begin: 0.0, end: progress).animate(animationController); - - useEffect(() { - animationController.forward(); - return null; - }, []); - - return AnimatedBuilder( - animation: animation, - builder: (context, child) { - return LinearProgressIndicator( - minHeight: 10, - borderRadius: BorderRadius.circular(10), - value: animation.value, - ); - }, - ); - } -} diff --git a/packages/superdeck/lib/components/atoms/markdown_viewer.dart b/packages/superdeck/lib/components/atoms/markdown_viewer.dart deleted file mode 100644 index a23b7957..00000000 --- a/packages/superdeck/lib/components/atoms/markdown_viewer.dart +++ /dev/null @@ -1,230 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:markdown/markdown.dart' as md; - -import '../../helpers/constants.dart'; -import '../../helpers/measure_size.dart'; -import '../../helpers/syntax_highlighter.dart'; -import '../../helpers/utils.dart'; -import '../../superdeck.dart'; -import 'cache_image_widget.dart'; - -class AnimatedMarkdownViewer extends ImplicitlyAnimatedWidget { - final String content; - final SlideSpec spec; - - const AnimatedMarkdownViewer({ - super.key, - required this.content, - required this.spec, - required super.duration, - super.curve = Curves.linear, - }); - - @override - ImplicitlyAnimatedWidgetState createState() => - _AnimatedMarkdownViewerState(); -} - -class _AnimatedMarkdownViewerState - extends AnimatedWidgetBaseState { - SlideSpecTween? _styleTween; - Size? _size; - - @override - void forEachTween(TweenVisitor visitor) { - _styleTween = visitor( - _styleTween, - widget.spec, - (dynamic value) => SlideSpecTween(begin: value), - ) as SlideSpecTween?; - } - - @override - Widget build(BuildContext context) { - final spec = _styleTween!.evaluate(animation) ?? const SlideSpec(); - return MeasureSingleWidgetSize( - onChange: (size) { - setState(() { - _size = size; - }); - }, - child: MarkdownBody( - data: widget.content, - extensionSet: md.ExtensionSet( - md.ExtensionSet.gitHubFlavored.blockSyntaxes, - [ - md.EmojiSyntax(), - ...md.ExtensionSet.gitHubFlavored.inlineSyntaxes - ], - ), - imageBuilder: (uri, title, alt) { - return _imageBuilder(uri, title, alt, size: _size ?? kResolution); - }, - builders: { - 'code': CodeElementBuilder(spec.code), - }, - bulletBuilder: (parameters) { - if (parameters.style == BulletStyle.orderedList) { - final index = parameters.index + 1; - return Text( - '$index .', - style: spec.list?.bulletStyle, - ); - } else { - return Text('•', style: spec.list?.bulletStyle); - } - }, - styleSheet: _styleTween!.evaluate(animation)?.toStyle(), - ), - ); - } -} - -List updateTextColor( - List originalSpans, - List targetLines, - Color newColor, -) { - // Check if the target line is within the range of the list - if (targetLines.isEmpty) { - return originalSpans; - } - - // Clone the original list to avoid mutating it directly - List updatedSpans = List.from(originalSpans); - - // Detect line break from the list of text spans - // This is done by checking if the previous span is a line break - // If it is, then the current span is the start of a new line - int line = 1; - - for (int i = 0; i < updatedSpans.length; i++) { - if (i > 0) { - final currentValue = updatedSpans[i].text ?? ''; - - if (currentValue.startsWith('\n')) { - line++; - } - } - final originalSpan = originalSpans[i]; - - final textStyle = originalSpan.style ?? const TextStyle(); - - if (targetLines.contains(line)) { - updatedSpans[i] = TextSpan( - text: originalSpan.text, - children: originalSpan.children, - recognizer: originalSpan.recognizer, - mouseCursor: originalSpan.mouseCursor, - onEnter: originalSpan.onEnter, - onExit: originalSpan.onExit, - semanticsLabel: originalSpan.semanticsLabel, - locale: originalSpan.locale, - spellOut: originalSpan.spellOut, - style: textStyle.copyWith( - backgroundColor: newColor, - ), - ); - } - } - - return updatedSpans; -} - -Widget _imageBuilder( - Uri uri, - String? title, - String? alt, { - required Size size, -}) { - return Builder(builder: (context) { - final slideSpec = SlideSpec.of(context); - - return ConstrainedBox( - constraints: calculateConstraints(size, slideSpec), - child: CacheImage( - url: uri.toString(), - spec: slideSpec.image, - ), - ); - }); -} - -class TextBuilder extends MarkdownElementBuilder { - final TextSpec? spec; - TextBuilder(this.spec); - @override - Widget visitText(md.Text text, TextStyle? preferredStyle) { - return TextSpecWidget(text.text, spec: spec); - } -} - -class CodeElementBuilder extends MarkdownElementBuilder { - final MdCodeblockSpec? spec; - CodeElementBuilder(this.spec); - @override - Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) { - var language = 'dart'; - - if (element.attributes['class'] != null) { - String lg = element.attributes['class'] as String; - language = lg.substring(9); - } - return Row( - children: [ - Expanded( - child: Container( - padding: spec?.padding, - decoration: spec?.decoration, - child: RichText( - text: TextSpan( - style: spec?.textStyle, - children: SyntaxHighlight.render( - element.textContent.trim(), - language, - ), - ), - ), - ), - ), - ], - ); - } -} - -class SampleCodeElementBuilder extends MarkdownElementBuilder { - final MdCodeblockSpec? spec; - SampleCodeElementBuilder(this.spec); - @override - Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) { - var language = 'dart'; - - if (element.attributes['class'] != null) { - String lg = element.attributes['class'] as String; - language = lg.substring(9); - } - return Row( - children: [ - Expanded( - child: Container( - padding: EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.transparent, - borderRadius: BorderRadius.circular(8), - ), - child: RichText( - text: TextSpan( - style: spec?.textStyle, - children: SyntaxHighlight.render( - element.textContent.trim(), - language, - ), - ), - ), - ), - ), - ], - ); - } -} diff --git a/packages/superdeck/lib/components/atoms/sized_transition.dart b/packages/superdeck/lib/components/atoms/sized_transition.dart deleted file mode 100644 index 9f203265..00000000 --- a/packages/superdeck/lib/components/atoms/sized_transition.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'dart:math' as math; - -import 'package:flutter/widgets.dart'; - -class SizedTransition extends StatelessWidget { - const SizedTransition({ - super.key, - required this.sizeFactor, - this.child, - this.direction = Axis.horizontal, - }); - - final double sizeFactor; - final Axis direction; - - final Widget? child; - - @override - Widget build(BuildContext context) { - AlignmentDirectional alignment; - if (direction == Axis.horizontal) { - alignment = const AlignmentDirectional(0.0, -1.0); - } else { - alignment = const AlignmentDirectional(-1.0, 0.0); - } - return ClipRect( - child: Align( - alignment: alignment, - heightFactor: - direction == Axis.vertical ? math.max(sizeFactor, 0.0) : 1.0, - widthFactor: - direction == Axis.horizontal ? math.max(sizeFactor, 0.0) : 1.0, - child: child, - ), - ); - } -} diff --git a/packages/superdeck/lib/components/atoms/slide_thumbnail.dart b/packages/superdeck/lib/components/atoms/slide_thumbnail.dart deleted file mode 100644 index ab338f65..00000000 --- a/packages/superdeck/lib/components/atoms/slide_thumbnail.dart +++ /dev/null @@ -1,153 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:remix/remix.dart'; - -import '../../helpers/constants.dart'; -import '../../helpers/extensions.dart'; -import '../../services/reference_service.dart'; -import '../../services/snapshot_service.dart'; -import '../../superdeck.dart'; -import 'cache_image_widget.dart'; -import 'loading_indicator.dart'; - -class SlideThumbnail extends HookWidget { - final VoidCallback onTap; - final bool selected; - final Slide slide; - final int page; - - const SlideThumbnail({ - super.key, - required this.selected, - required this.onTap, - required this.slide, - required this.page, - }); - - @override - Widget build(BuildContext context) { - final processThumbnail = useFuture( - useMemoized(() => _generateThumbnail(slide, context), [slide]), - ); - return LayoutBuilder(builder: (context, constraints) { - final child = processThumbnail.when( - data: (file) { - return Image( - gaplessPlayback: true, - image: getImageProvider(file.path), - ); - }, - loading: () { - return IsometricLoading(); - }, - error: (error, _) { - return const Center( - child: Text('Error loading image'), - ); - }, - ); - - return GestureDetector( - onTap: onTap, - child: _PreviewContainer( - selected: selected, - child: AspectRatio( - aspectRatio: kAspectRatio, - child: Stack( - children: [ - AspectRatio( - aspectRatio: kAspectRatio, - child: child, - ), - Positioned( - top: 0, - right: 0, - left: 0, - child: SizedBox( - child: processThumbnail.isRefreshing - ? const LinearProgressIndicator( - minHeight: 3, - backgroundColor: Colors.transparent, - ) - : null, - ), - ), - Positioned( - right: 0, - bottom: 0, - child: Container( - padding: const EdgeInsets.fromLTRB(12, 4, 12, 4), - color: Colors.black.withOpacity(0.9), - child: Text( - '$page', - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ], - ), - ), - ), - ); - }); - } -} - -class _PreviewContainer extends StatelessWidget { - final Widget child; - final bool selected; - - const _PreviewContainer({ - required this.selected, - required this.child, - }); - - @override - Widget build(BuildContext context) { - final style = Style( - $box.color.$neutral(2), - $box.margin.all(8), - $box.border.width(2), - $box.shadow( - blurRadius: 4, - spreadRadius: 1, - ), - - selected ? $box.wrap.scale(1.05) : $box.wrap.scale(1), - selected ? $box.wrap.opacity(1) : $box.wrap.opacity(0.5), - selected ? $box.border.color.$accent() : $box.border.color.transparent(), - // $on.hover( - // $box.wrap.opacity(1), - // ), - ).animate(); - - return Box( - style: style, - child: child, - ); - } -} - -Future _generateThumbnail(Slide slide, BuildContext context) async { - final thumbnailFile = - ReferenceService.instance.getAssetFile('thumbnail_${slide.key}.png'); - - if (!kCanRunProcess || await thumbnailFile.exists()) { - return thumbnailFile; - } - - final imageData = await SnapshotService.instance.generate( - quality: SnapshotQuality.low, - slide: slide, - ); - - await thumbnailFile.writeAsBytes(imageData); - - return thumbnailFile; -} diff --git a/packages/superdeck/lib/components/atoms/slide_view.dart b/packages/superdeck/lib/components/atoms/slide_view.dart deleted file mode 100644 index 1561225c..00000000 --- a/packages/superdeck/lib/components/atoms/slide_view.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../providers/slide_provider.dart'; -import '../../providers/snapshot_provider.dart'; -import '../../providers/style_provider.dart'; -import '../../superdeck.dart'; -import 'cache_image_widget.dart'; -import 'transition_widget.dart'; - -class SlideView extends StatelessWidget { - const SlideView( - this.slide, { - super.key, - }); - - final T slide; - - @override - Widget build(BuildContext context) { - final slide = this.slide; - - final variantStyle = StyleProvider.of(context, slide.style); - - final isCapturing = SnapshotProvider.isCapturingOf(context); - final duration = - isCapturing ? Duration.zero : const Duration(milliseconds: 300); - - final backgroundWidget = slide.background != null - ? CacheImage( - url: slide.background!, - fit: BoxFit.cover, - alignment: Alignment.center, - ) - : const SizedBox(); - - return TransitionWidget( - key: ValueKey(slide.transition), - transition: slide.transition, - child: SpecBuilder( - style: variantStyle, - builder: (context) { - final spec = SlideSpec.of(context); - return Builder(builder: (context) { - return AnimatedBoxSpecWidget( - spec: spec.outerContainer, - duration: duration, - child: Stack( - children: [ - Positioned.fill(child: backgroundWidget), - AnimatedBoxSpecWidget( - spec: spec.innerContainer, - duration: duration, - child: SlideBuilder(slide), - ), - ], - ), - ); - }); - }, - ), - ); - } -} diff --git a/packages/superdeck/lib/components/atoms/transition_widget.dart b/packages/superdeck/lib/components/atoms/transition_widget.dart deleted file mode 100644 index a6ec5d73..00000000 --- a/packages/superdeck/lib/components/atoms/transition_widget.dart +++ /dev/null @@ -1,176 +0,0 @@ -import 'package:animate_do/animate_do.dart'; -import 'package:flutter/material.dart'; - -import '../../models/options_model.dart'; - -class TransitionWidget extends StatelessWidget { - final TransitionOptions? transition; - final Widget child; - - const TransitionWidget({ - super.key, - required this.transition, - required this.child, - }); - - @override - Widget build(BuildContext context) { - final duration = transition?.duration ?? const Duration(milliseconds: 500); - final delay = transition?.delay ?? const Duration(milliseconds: 0); - final curve = _getCurveFromType(transition?.curve) ?? Curves.easeInOut; - final type = transition?.type; - - if (type == null) return child; - - final buildAnimation = _getWidgetByType(type); - return Container( - child: buildAnimation( - duration: duration, - delay: delay, - curve: curve, - child: child, - ), - ); - } -} - -Curve? _getCurveFromType(CurveType? curveType) { - switch (curveType) { - case CurveType.easeInOut: - return Curves.easeInOut; - case CurveType.easeIn: - return Curves.easeIn; - case CurveType.easeOut: - return Curves.easeOut; - case CurveType.linear: - return Curves.linear; - case CurveType.decelerate: - return Curves.decelerate; - case CurveType.fastLinearToSlowEaseIn: - return Curves.fastLinearToSlowEaseIn; - case CurveType.bounceIn: - return Curves.bounceIn; - case CurveType.bounceOut: - return Curves.bounceOut; - case CurveType.elasticIn: - return Curves.elasticIn; - case CurveType.elasticOut: - return Curves.elasticOut; - case CurveType.elasticInOut: - return Curves.elasticInOut; - case CurveType.fastOutSlowIn: - return Curves.fastOutSlowIn; - case CurveType.slowMiddle: - return Curves.slowMiddle; - case CurveType.linearToEaseOut: - return Curves.linearToEaseOut; - case CurveType.ease: - return Curves.ease; - default: - return null; - } -} - -Widget Function({ - required Duration duration, - required Duration delay, - required Curve curve, - required Widget child, -}) _getWidgetByType( - TransitionType type, -) { - switch (type) { - case TransitionType.fadeIn: - return FadeIn.new; - case TransitionType.fadeInDown: - return FadeInDown.new; - case TransitionType.fadeInDownBig: - return FadeInDownBig.new; - case TransitionType.fadeInUp: - return FadeInUp.new; - case TransitionType.fadeInUpBig: - return FadeInUpBig.new; - case TransitionType.fadeInLeft: - return FadeInLeft.new; - case TransitionType.fadeInLeftBig: - return FadeInLeftBig.new; - case TransitionType.fadeInRight: - return FadeInRight.new; - case TransitionType.fadeInRightBig: - return FadeInRightBig.new; - case TransitionType.fadeOut: - return FadeOut.new; - case TransitionType.fadeOutDown: - return FadeOutDown.new; - case TransitionType.fadeOutDownBig: - return FadeOutDownBig.new; - case TransitionType.fadeOutUp: - return FadeOutUp.new; - case TransitionType.fadeOutUpBig: - return FadeOutUpBig.new; - case TransitionType.fadeOutLeft: - return FadeOutLeft.new; - case TransitionType.fadeOutLeftBig: - return FadeOutLeftBig.new; - case TransitionType.fadeOutRight: - return FadeOutRight.new; - case TransitionType.fadeOutRightBig: - return FadeOutRightBig.new; - case TransitionType.bounceInDown: - return BounceInDown.new; - case TransitionType.bounceInUp: - return BounceInUp.new; - case TransitionType.bounceInLeft: - return BounceInLeft.new; - case TransitionType.bounceInRight: - return BounceInRight.new; - case TransitionType.elasticIn: - return ElasticIn.new; - case TransitionType.elasticInDown: - return ElasticInDown.new; - case TransitionType.elasticInUp: - return ElasticInUp.new; - case TransitionType.elasticInLeft: - return ElasticInLeft.new; - case TransitionType.elasticInRight: - return ElasticInRight.new; - case TransitionType.slideInDown: - return SlideInDown.new; - case TransitionType.slideInUp: - return SlideInUp.new; - case TransitionType.slideInLeft: - return SlideInLeft.new; - case TransitionType.slideInRight: - return SlideInRight.new; - case TransitionType.flipInX: - return FlipInX.new; - case TransitionType.flipInY: - return FlipInY.new; - case TransitionType.zoomIn: - return ZoomIn.new; - case TransitionType.zoomOut: - return ZoomOut.new; - case TransitionType.jelloIn: - return JelloIn.new; - case TransitionType.bounce: - return Bounce.new; - case TransitionType.dance: - return Dance.new; - case TransitionType.flash: - return Flash.new; - case TransitionType.pulse: - return Pulse.new; - case TransitionType.roulette: - return Roulette.new; - case TransitionType.shakeX: - return ShakeX.new; - case TransitionType.shakeY: - return ShakeY.new; - case TransitionType.spin: - return Spin.new; - case TransitionType.spinPerfect: - return SpinPerfect.new; - case TransitionType.swing: - return Swing.new; - } -} diff --git a/packages/superdeck/lib/components/molecules/code_preview.dart b/packages/superdeck/lib/components/molecules/code_preview.dart deleted file mode 100644 index 453a4ee8..00000000 --- a/packages/superdeck/lib/components/molecules/code_preview.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - -class ExamplePreview extends StatelessWidget { - const ExamplePreview({ - super.key, - required this.args, - required this.builder, - }); - - final Map args; - - final Widget Function(BuildContext) builder; - - @override - Widget build(BuildContext context) { - return ExampleArgsProvider( - args: args, - child: Center( - child: Builder(builder: builder), - ), - ); - } -} - -extension BuildContextExampleX on BuildContext { - Map get args { - return ExampleArgsProvider.of(this); - } -} - -class ExampleArgsProvider extends InheritedWidget { - const ExampleArgsProvider({ - required this.args, - required super.child, - super.key, - }); - - final Map args; - - static Map of(BuildContext context) { - final provider = - context.dependOnInheritedWidgetOfExactType(); - return provider?.args ?? {}; - } - - @override - bool updateShouldNotify(ExampleArgsProvider oldWidget) { - return !mapEquals(args, oldWidget.args); - } -} diff --git a/packages/superdeck/lib/components/molecules/exception_widget.dart b/packages/superdeck/lib/components/molecules/exception_widget.dart deleted file mode 100644 index 49c57ae2..00000000 --- a/packages/superdeck/lib/components/molecules/exception_widget.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/material.dart'; - -class ExceptionWidget extends StatelessWidget { - final Object? error; - const ExceptionWidget(this.error, {super.key, required this.onRetry}); - - final void Function() onRetry; - @override - Widget build(BuildContext context) { - return Scaffold( - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('An error occurred'), - Text(error.toString()), - const SizedBox(height: 20), - ElevatedButton( - onPressed: onRetry, - child: const Text('Retry'), - ), - ], - ), - ), - ); - } -} diff --git a/packages/superdeck/lib/components/molecules/navigation_rail.dart b/packages/superdeck/lib/components/molecules/navigation_rail.dart deleted file mode 100644 index fc65f8dd..00000000 --- a/packages/superdeck/lib/components/molecules/navigation_rail.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:mix/mix.dart'; - -import '../remix/button.dart'; - -class CustomNavigationRail extends HookWidget { - final int selectedIndex; - final ValueChanged? onDestinationSelected; - final List destinations; - final bool displayLabel; - final double? leading; - final double? trailing; - - CustomNavigationRail({ - required this.selectedIndex, - this.onDestinationSelected, - required this.destinations, - this.displayLabel = false, - this.leading, - this.trailing, - }); - - @override - Widget build(BuildContext context) { - final _buildDestination = useCallback((int index) { - final destination = destinations[index]; - final isSelected = selectedIndex == index; - - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: SDIconButton( - icon: destination.icon, - onPressed: () => onDestinationSelected?.call(index), - selected: isSelected, - ), - ); - }, [selectedIndex, destinations]); - - return VBox( - style: _containerStyle, - children: [ - if (leading != null) SizedBox(height: leading), - for (int i = 0; i < destinations.length; i++) _buildDestination(i), - if (trailing != null) SizedBox(height: trailing), - ], - ); - } -} - -get _containerStyle => Style( - $box.color.black(), - $box.padding(16), - $box.border.right( - color: Colors.white10, - width: 1, - ), - ); - -class CustomNavigationRailDestination { - final IconData icon; - final String label; - - CustomNavigationRailDestination({ - required this.icon, - required this.label, - }); -} diff --git a/packages/superdeck/lib/components/molecules/slide_content.dart b/packages/superdeck/lib/components/molecules/slide_content.dart deleted file mode 100644 index da50a9ef..00000000 --- a/packages/superdeck/lib/components/molecules/slide_content.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:mix/mix.dart'; - -import '../../models/options_model.dart'; -import '../../providers/snapshot_provider.dart'; -import '../../styles/style_spec.dart'; -import '../atoms/markdown_viewer.dart'; - -class SlideContent extends StatelessWidget { - const SlideContent({ - required this.content, - required this.options, - super.key, - }); - - final String content; - - final ContentOptions? options; - - @override - Widget build(context) { - final alignment = options?.alignment ?? ContentAlignment.center; - final spec = SlideSpec.of(context); - final isCapturing = SnapshotProvider.isCapturingOf(context); - - Widget child = AnimatedMarkdownViewer( - content: content, - spec: spec, - duration: Durations.medium1, - ); - - if (!isCapturing) { - child = SingleChildScrollView( - child: child, - ); - } else { - child = Wrap( - clipBehavior: Clip.hardEdge, - children: [ - child, - ], - ); - } - return AnimatedBoxSpecWidget( - duration: const Duration(milliseconds: 300), - spec: spec.contentContainer.copyWith( - alignment: alignment.toAlignment(), - ), - child: child, - ); - } -} diff --git a/packages/superdeck/lib/components/molecules/slide_preview.dart b/packages/superdeck/lib/components/molecules/slide_preview.dart deleted file mode 100644 index c3fef6ec..00000000 --- a/packages/superdeck/lib/components/molecules/slide_preview.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../superdeck.dart'; -import '../atoms/slide_view.dart'; -import 'scaled_app.dart'; - -class SlidePreview extends StatelessWidget { - const SlidePreview( - this.slide, { - super.key, - }); - - final T slide; - - @override - Widget build(BuildContext context) { - return Center( - child: Container( - decoration: BoxDecoration( - color: const Color.fromARGB(255, 68, 60, 60), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.3), - blurRadius: 6, - spreadRadius: 3, - ), - ], - ), - child: ScaledWidget( - child: SlideView(slide), - ), - ), - ); - } -} diff --git a/packages/superdeck/lib/components/molecules/split_view.dart b/packages/superdeck/lib/components/molecules/split_view.dart deleted file mode 100644 index 147b6e13..00000000 --- a/packages/superdeck/lib/components/molecules/split_view.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; - -import '../../helpers/hooks.dart'; -import '../../helpers/routes.dart'; -import '../../helpers/utils.dart'; -import '../atoms/sized_transition.dart'; -import '../organisms/chat_panel.dart'; -import '../organisms/presentation_side_panel.dart'; -import 'navigation_rail.dart'; - -class SplitView extends HookWidget { - final StatefulNavigationShell navigationShell; - - const SplitView({ - super.key, - required this.navigationShell, - }); - - final _maxWidth = 400.0; - final _thumbnailWidth = 300.0; - - @override - Widget build(BuildContext context) { - final sideSize = useState(0.0); - - useEffect(() { - if (context.isMobileLandscape) { - sideSize.value = 200.0; - } else { - sideSize.value = _maxWidth; - } - }, [navigationShell.currentIndex]); - - final animationController = useAnimationController( - duration: Durations.medium1, - ); - - final animation = useAnimation(CurvedAnimation( - parent: animationController, - curve: Curves.ease, - )); - - usePostFrameEffect(() { - if (context.isDrawerOpen) { - animationController.forward(); - } else { - animationController.reverse(); - } - }, [context.isDrawerOpen]); - - return LayoutBuilder( - builder: (context, constraints) { - final sidebar = switch (navigationShell.currentIndex) { - 0 => SizedBox( - width: _thumbnailWidth, - child: const PresentationSidePanel(), - ), - 1 => SizedBox( - width: _thumbnailWidth, - child: const ChatScreen(), - ), - _ => const SizedBox.shrink(), - }; - - return Row( - children: [ - SizedTransition( - sizeFactor: animation, - child: Row( - children: [ - CustomNavigationRail( - selectedIndex: navigationShell.currentIndex, - onDestinationSelected: (value) { - navigationShell.goBranch(value); - }, - leading: 20, - destinations: [ - CustomNavigationRailDestination( - icon: Icons.view_carousel, - label: 'Home', - ), - CustomNavigationRailDestination( - icon: Icons.chat, - label: 'Chat', - ), - if (!kIsWeb) - CustomNavigationRailDestination( - icon: Icons.picture_as_pdf, - label: 'Export', - ), - ], - ), - sidebar - ], - ), - ), - Expanded(child: navigationShell) - ], - ); - }, - ); - } -} diff --git a/packages/superdeck/lib/components/organisms/app_shell.dart b/packages/superdeck/lib/components/organisms/app_shell.dart deleted file mode 100644 index ff797bbb..00000000 --- a/packages/superdeck/lib/components/organisms/app_shell.dart +++ /dev/null @@ -1,91 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:go_router/go_router.dart'; - -import '../../helpers/routes.dart'; -import '../../helpers/utils.dart'; -import '../../superdeck.dart'; -import '../molecules/split_view.dart'; - -final kScaffoldKey = GlobalKey(); - -/// Builds the "shell" for the app by building a Scaffold with a -/// BottomNavigationBar, where [child] is placed in the body of the Scaffold. -class AppShell extends HookWidget { - const AppShell({ - required this.navigationShell, - super.key = const ValueKey('app_shell'), - }); - - /// The navigation shell and container for the branch Navigators. - final StatefulNavigationShell navigationShell; - - @override - Widget build(BuildContext context) { - final isSmall = context.isSmall; - - final slides = useSlides(); - - final invalidSlides = slides.whereType().toList(); - final handleNext = useCallback(() { - if (context.currentSlidePage < slides.length) { - context.nextSlide(); - } - }, [slides]); - - final handlePrevious = useCallback(() { - if (context.currentSlidePage > 1) { - context.previousSlide(); - } - }, [slides]); - - final bindings = { - const SingleActivator( - LogicalKeyboardKey.arrowRight, - meta: true, - ): handleNext, - const SingleActivator( - LogicalKeyboardKey.arrowDown, - meta: true, - ): handleNext, - const SingleActivator( - LogicalKeyboardKey.space, - meta: true, - ): handleNext, - const SingleActivator( - LogicalKeyboardKey.arrowLeft, - meta: true, - ): handlePrevious, - const SingleActivator( - LogicalKeyboardKey.arrowUp, - meta: true, - ): handlePrevious, - }; - - return CallbackShortcuts( - bindings: bindings, - child: Scaffold( - backgroundColor: const Color.fromARGB(255, 9, 9, 9), - bottomNavigationBar: null, - extendBodyBehindAppBar: true, - extendBody: true, - key: kScaffoldKey, - floatingActionButtonLocation: isSmall - ? FloatingActionButtonLocation.miniEndFloat - : FloatingActionButtonLocation.miniStartFloat, - floatingActionButton: FloatingActionButton.small( - onPressed: context.toggleDrawer, - child: Badge( - label: Text(invalidSlides.length.toString()), - isLabelVisible: invalidSlides.isNotEmpty, - child: const Icon(Icons.menu), - ), - ), - body: SplitView( - navigationShell: navigationShell, - ), - ), - ); - } -} diff --git a/packages/superdeck/lib/components/organisms/chat_panel.dart b/packages/superdeck/lib/components/organisms/chat_panel.dart deleted file mode 100644 index 258fef96..00000000 --- a/packages/superdeck/lib/components/organisms/chat_panel.dart +++ /dev/null @@ -1,356 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:google_generative_ai/google_generative_ai.dart'; - -import '../../chat/components/typing_indicator.dart'; -import '../../chat/prompt.dart'; -import '../../helpers/constants.dart'; -import '../../helpers/extensions.dart'; -import '../../services/reference_service.dart'; -import '../atoms/markdown_viewer.dart'; - -const _apiKey = ''; - -final _geminiFlash = 'gemini-1.5-flash-latest'; -final _geminiPro = 'gemini-1.5-pro'; - -class ChatScreen extends StatelessWidget { - const ChatScreen({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text( - 'Gemini', - style: context.textTheme.bodySmall, - ), - ), - body: const ChatWidget(apiKey: _apiKey), - ); - } -} - -class ChatWidget extends StatefulHookWidget { - const ChatWidget({ - required this.apiKey, - super.key, - }); - - final String apiKey; - - @override - State createState() => _ChatWidgetState(); -} - -typedef GeneratedContent = ({Image? image, String? text, bool fromUser}); - -class _ChatWidgetState extends State { - late final GenerativeModel _model; - late final ChatSession _chat; - - final _generatedContent = []; - - @override - void initState() { - super.initState(); - _model = GenerativeModel( - model: _geminiPro, - apiKey: widget.apiKey, - systemInstruction: Content.system( - presentationAssistantPrompt, - )); - if (kCanRunProcess) { - ReferenceService.instance.loadMarkdown().then((value) { - final content = '\n$value\n'; - _chat = _model.startChat(history: [ - // add around value - Content.text(content), - ]); - }); - } else { - _chat = _model.startChat(); - } - } - - @override - Widget build(BuildContext context) { - final _textController = useTextEditingController(); - final _scrollController = useScrollController(); - final _loading = useState(false); - final _applyChanges = useState(false); - - void _scrollDown() { - WidgetsBinding.instance.addPostFrameCallback( - (_) => _scrollController.animateTo( - _scrollController.position.maxScrollExtent, - duration: const Duration( - milliseconds: 200, - ), - curve: Curves.easeOutCirc, - ), - ); - } - - final _sendChatMessage = useCallback((String message) async { - _loading.value = true; - - _textController.clear(); - - try { - if (!_applyChanges.value) { - _generatedContent.add((image: null, text: message, fromUser: true)); - } - final response = await _chat.sendMessage( - Content.text(message), - ); - final text = response.text; - if (!_applyChanges.value) { - _generatedContent.add((image: null, text: text, fromUser: false)); - } else { - // Replace and with empty string - final provideMarkdownEdits = text! - .replaceAll('', '') - .replaceAll('', ''); - await ReferenceService.instance.saveMarkdown(provideMarkdownEdits); - _generatedContent.add(( - image: null, - text: 'I have applied the changes.', - fromUser: false - )); - } - - if (text == null) { - _showError('No response from API.'); - return; - } else { - _loading.value = false; - _scrollDown(); - } - } catch (e) { - _showError(e.toString()); - } finally { - _loading.value = false; - } - }, []); - - final _textFieldFocus = useFocusNode( - onKeyEvent: (node, event) { - if (event is KeyUpEvent) { - return KeyEventResult.ignored; - } - - final isEnterKey = event.logicalKey == LogicalKeyboardKey.enter || - event.logicalKey == LogicalKeyboardKey.numpadEnter; - - if (HardwareKeyboard.instance.isShiftPressed && isEnterKey) { - _textController.value = TextEditingValue( - text: _textController.text + '\n', - selection: TextSelection.collapsed( - offset: _textController.text.length + 1), - ); - return KeyEventResult.handled; - } - - if (isEnterKey) { - if (event is KeyDownEvent) { - _sendChatMessage(_textController.text); - return KeyEventResult.handled; - } - } - return KeyEventResult.ignored; - }, - ); - useEffect(() { - if (!_loading.value) { - _textFieldFocus.requestFocus(); - } - }, [_loading.value]); - - Widget _buildSendButton() { - if (!_loading.value) { - return IconButton( - onPressed: () async { - _sendChatMessage(_textController.text); - }, - icon: Icon( - Icons.send, - color: Theme.of(context).colorScheme.primary, - ), - ); - } - - return Padding( - padding: EdgeInsets.only(right: 16.0), - child: WaitingIndicator(isTyping: true), - ); - } - - Widget _buildApplyChangesButton() { - return Padding( - padding: const EdgeInsets.all(8.0), - child: IconButton( - onPressed: () async { - _applyChanges.value = true; - _sendChatMessage(provideMarkdownEdits); - }, - icon: Icon( - Icons.bolt, - color: Theme.of(context).colorScheme.primary, - ), - ), - ); - } - - final textFieldDecoration = InputDecoration( - filled: true, - fillColor: Colors.black45, - hoverColor: Colors.black45, - hintText: _loading.value ? '' : 'Type a message', - enabled: !_loading.value, - hintStyle: TextStyle(color: Colors.white), - suffixIcon: _buildSendButton(), - prefixIcon: _buildApplyChangesButton(), - suffixIconConstraints: const BoxConstraints( - minWidth: 20, - minHeight: 20, - ), - prefixIconConstraints: const BoxConstraints( - minWidth: 20, - minHeight: 20, - ), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(16), - borderSide: BorderSide.none, - ), - contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), - ); - - return Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: _apiKey.isNotEmpty - ? ListView.builder( - controller: _scrollController, - itemBuilder: (context, idx) { - final content = _generatedContent[idx]; - return MessageWidget( - text: content.text, - image: content.image, - isFromUser: content.fromUser, - ); - }, - itemCount: _generatedContent.length, - ) - : ListView( - children: const [ - Text( - 'No API key found. Please provide an API Key using ' - "'--dart-define' to set the 'API_KEY' declaration.", - ), - ], - ), - ), - Row( - children: [ - Expanded( - child: ConstrainedBox( - constraints: const BoxConstraints(maxHeight: 80), - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8), - child: TextField( - enabled: !_loading.value, - keyboardType: TextInputType.multiline, - autofocus: true, - expands: true, - style: context.textTheme.bodyMedium, - focusNode: _textFieldFocus, - decoration: textFieldDecoration, - maxLines: null, - controller: _textController, - ), - ), - ), - ), - ], - ), - ], - ), - ); - } - - void _showError(String message) { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('Something went wrong'), - content: SingleChildScrollView( - child: SelectableText(message), - ), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text('OK'), - ) - ], - ); - }, - ); - } -} - -class MessageWidget extends StatelessWidget { - const MessageWidget({ - super.key, - this.image, - this.text, - required this.isFromUser, - }); - - final Image? image; - final String? text; - final bool isFromUser; - - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: - isFromUser ? MainAxisAlignment.end : MainAxisAlignment.start, - children: [ - Flexible( - child: Container( - constraints: const BoxConstraints(maxWidth: 520), - decoration: BoxDecoration( - color: isFromUser - ? Theme.of(context).colorScheme.primaryContainer - : Theme.of(context).colorScheme.surfaceContainerHighest, - borderRadius: BorderRadius.circular(18), - ), - padding: const EdgeInsets.symmetric( - vertical: 15, - horizontal: 20, - ), - margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 8), - child: Column(children: [ - if (text case final text?) - MarkdownBody( - data: text, - builders: { - 'code': SampleCodeElementBuilder(null), - }, - ), - if (image case final image?) image, - ]))), - ], - ); - } -} diff --git a/packages/superdeck/lib/components/organisms/drawer.dart b/packages/superdeck/lib/components/organisms/drawer.dart deleted file mode 100644 index b258e5ff..00000000 --- a/packages/superdeck/lib/components/organisms/drawer.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter/material.dart'; - -enum SideMenu { - preview(icon: Icons.play_arrow, label: 'Preview'), - - clearCache(icon: Icons.cached, label: 'Clear Cache'); - - const SideMenu({ - required this.icon, - required this.label, - }); - - final IconData icon; - final String label; - - static List devMenu = [ - ...values, - ]; - - static List prodMenu = [ - preview, - ]; -} diff --git a/packages/superdeck/lib/components/organisms/presentation_side_panel.dart b/packages/superdeck/lib/components/organisms/presentation_side_panel.dart deleted file mode 100644 index 2e1f81d9..00000000 --- a/packages/superdeck/lib/components/organisms/presentation_side_panel.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; - -import '../../helpers/hooks.dart'; -import '../../helpers/routes.dart'; -import '../../helpers/utils.dart'; -import '../../providers/controller.dart'; -import '../atoms/slide_thumbnail.dart'; - -class PresentationSidePanel extends HookWidget { - const PresentationSidePanel({ - super.key, - }); - - final _duration = const Duration(milliseconds: 300); - final _curve = Curves.easeInOutCubic; - - @override - Widget build(BuildContext context) { - final currentSlideIndex = context.currentSlidePage - 1; - - final slides = useSlides(); - final controller = useScrollVisibleController(); - final visibleItems = controller.visibleItems; - - usePostFrameEffect(() { - if (visibleItems.isEmpty) return; - - final visibleItem = - visibleItems.firstWhereOrNull((e) => e.index == currentSlideIndex); - - double alignment; - - if (visibleItem == null) { - final isBeginning = visibleItems.first.index > currentSlideIndex; - - alignment = isBeginning ? 0 : 0.7; - } else { - if (visibleItem.itemTrailingEdge > 1) { - final totalSpace = - visibleItem.itemTrailingEdge - visibleItem.itemLeadingEdge; - alignment = 1 - totalSpace; - } else if (visibleItem.itemLeadingEdge < 0) { - alignment = 0; - } else { - alignment = visibleItem.itemLeadingEdge; - } - } - controller.itemScrollController.scrollTo( - index: currentSlideIndex, - alignment: alignment, - duration: _duration, - curve: _curve, - ); - - return; - }, [currentSlideIndex, slides]); - - return Container( - color: Colors.black, - child: ScrollablePositionedList.builder( - scrollDirection: context.isSmall ? Axis.horizontal : Axis.vertical, - itemCount: slides.length, - itemPositionsListener: controller.itemPositionsListener, - itemScrollController: controller.itemScrollController, - padding: const EdgeInsets.all(20), - itemBuilder: (context, index) { - return Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 10, - ), - child: SlideThumbnail( - page: index + 1, - selected: currentSlideIndex == index, - onTap: () => context.goToSlide(index + 1), - slide: slides[index], - ), - ); - }), - ); - } -} diff --git a/packages/superdeck/lib/components/remix/button.dart b/packages/superdeck/lib/components/remix/button.dart deleted file mode 100644 index 89c271ce..00000000 --- a/packages/superdeck/lib/components/remix/button.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; -import 'package:remix/remix.dart'; - -class SDButton extends StatelessWidget { - const SDButton({ - required this.onPressed, - super.key, - required this.label, - this.icon, - }); - - final VoidCallback onPressed; - final String label; - - final IconData? icon; - - @override - Widget build(BuildContext context) { - return RxButton( - onPressed: onPressed, - type: ButtonVariant.surface, - iconLeft: icon, - label: label, - ); - } -} - -class SDButtonSolid extends StatelessWidget { - const SDButtonSolid({ - required this.onPressed, - super.key, - required this.label, - this.icon, - }); - - final VoidCallback onPressed; - final String label; - - final IconData? icon; - - @override - Widget build(BuildContext context) { - return RxButton( - onPressed: onPressed, - type: ButtonVariant.solid, - iconLeft: icon, - label: label, - ); - } -} - -class SDOutlinedButton extends StatelessWidget { - const SDOutlinedButton({ - required this.onPressed, - super.key, - required this.label, - this.icon, - }); - - final VoidCallback onPressed; - final String label; - - final IconData? icon; - - @override - Widget build(BuildContext context) { - return RxButton( - onPressed: onPressed, - type: ButtonVariant.outline, - iconLeft: icon, - label: label, - ); - } -} - -class SDIconButton extends StatelessWidget { - const SDIconButton({ - required this.onPressed, - super.key, - required this.icon, - this.selected = false, - }); - - final VoidCallback onPressed; - final bool selected; - - final IconData icon; - - @override - Widget build(BuildContext context) { - return RxButton( - onPressed: onPressed, - type: selected ? ButtonVariant.surface : ButtonVariant.ghost, - iconLeft: icon, - size: ButtonSize.large, - label: '', - ); - } -} diff --git a/packages/superdeck/lib/components/remix/button_style.dart b/packages/superdeck/lib/components/remix/button_style.dart deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/superdeck/lib/components/superdeck_app.dart b/packages/superdeck/lib/components/superdeck_app.dart deleted file mode 100644 index 53c8ec23..00000000 --- a/packages/superdeck/lib/components/superdeck_app.dart +++ /dev/null @@ -1,111 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:remix/remix.dart'; -import 'package:window_manager/window_manager.dart'; - -import '../../helpers/syntax_highlighter.dart'; -import '../../superdeck.dart'; -import '../helpers/constants.dart'; -import '../helpers/routes.dart'; -import '../helpers/theme.dart'; -import '../providers/examples_provider.dart'; -import '../providers/snapshot_provider.dart'; -import '../providers/style_provider.dart'; -import 'atoms/loading_indicator.dart'; - -final _uniqueKey = UniqueKey(); -var _initialized = false; - -class SuperDeckApp extends HookWidget { - const SuperDeckApp({ - super.key, - this.baseStyle = const Style.empty(), - this.styles = const {}, - this.examples = const {}, - }); - - final Style baseStyle; - final Map examples; - final Map styles; - - static Future initialize() async { - // Return if its initialized - if (_initialized) return; - - _initialized = true; - - WidgetsFlutterBinding.ensureInitialized(); - - await Future.wait([ - SuperDeckController.initialize(), - SyntaxHighlight.initialize(), - _initializeWindowManager(), - ]); - } - - @override - Widget build(BuildContext context) { - return FutureBuilder( - future: SuperDeckApp.initialize(), - builder: (context, snapshot) { - return StyleProvider( - baseStyle: baseStyle, - styles: styles, - child: ExamplesProvider( - examples: examples, - child: ListenableBuilder( - listenable: $superdeck, - builder: (context, snapshot) { - return RemixTokens( - data: RemixTokens.dark, - child: MaterialApp.router( - debugShowCheckedModeBanner: true, - title: 'Superdeck', - routerConfig: goRouterConfig, - theme: theme, - builder: (context, child) { - return SnapshotProvider( - isCapturing: true, - child: LoadingOverlay( - isLoading: $superdeck.loading, - key: _uniqueKey, - child: $superdeck.completed - ? child! - : const SizedBox(), - ), - ); - }, - ), - ); - }), - ), - ); - }, - ); - } -} - -Future _initializeWindowManager() async { - if (kIsWeb) return; - - // Must add this line. - await windowManager.ensureInitialized(); - - const windowOptions = WindowOptions( - size: kResolution, - backgroundColor: Colors.black, - skipTaskbar: false, - minimumSize: kResolution, - titleBarStyle: TitleBarStyle.hidden, - ); - - windowManager.waitUntilReadyToShow(windowOptions, () async { - await windowManager.show(); - await windowManager.focus(); - }); - - await windowManager.setAspectRatio(kAspectRatio); -} diff --git a/packages/superdeck/lib/helpers/cache_memory_image.dart b/packages/superdeck/lib/helpers/cache_memory_image.dart deleted file mode 100644 index 8e9f7e07..00000000 --- a/packages/superdeck/lib/helpers/cache_memory_image.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; - -class CachedMemoryImage extends StatelessWidget { - final String base64Image; - final double? width; - final double? height; - final BoxFit fit; - - const CachedMemoryImage({ - super.key, - required this.base64Image, - this.width, - this.height, - this.fit = BoxFit.cover, - }); - - @override - Widget build(BuildContext context) { - final Uint8List bytes = base64Decode(base64Image); - final MemoryImage memoryImage = MemoryImage(bytes); - - return Image( - image: memoryImage, - width: width, - height: height, - fit: fit, - key: ValueKey(base64Image), - gaplessPlayback: true, - ); - } -} diff --git a/packages/superdeck/lib/helpers/dialog_page.dart b/packages/superdeck/lib/helpers/dialog_page.dart deleted file mode 100644 index 4db74ae2..00000000 --- a/packages/superdeck/lib/helpers/dialog_page.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../superdeck.dart'; - -/// A dialog page with Material entrance and exit animations, modal barrier color, -/// and modal barrier behavior (dialog is dismissible with a tap on the barrier). -class DialogPage extends Page { - final Offset? anchorPoint; - final Color? barrierColor; - final bool barrierDismissible; - final String? barrierLabel; - final bool useSafeArea; - final CapturedThemes? themes; - final WidgetBuilder builder; - - const DialogPage({ - required this.builder, - this.anchorPoint, - this.barrierColor = Colors.black54, - this.barrierDismissible = true, - this.barrierLabel, - this.useSafeArea = true, - this.themes, - super.key, - super.name, - super.arguments, - super.restorationId, - }); - - @override - Route createRoute(BuildContext context) { - return DialogRoute( - context: kScaffoldKey.currentContext!, - settings: this, - builder: builder, - anchorPoint: anchorPoint, - barrierColor: barrierColor, - barrierDismissible: barrierDismissible, - barrierLabel: barrierLabel, - useSafeArea: useSafeArea, - themes: themes, - ); - } -} - -class ModalPage extends Page { - const ModalPage({required this.child}); - - final Widget child; - - @override - Route createRoute(BuildContext context) => ModalBottomSheetRoute( - settings: this, - builder: (context) => child, - isScrollControlled: false, - ); -} diff --git a/packages/superdeck/lib/helpers/extensions.dart b/packages/superdeck/lib/helpers/extensions.dart deleted file mode 100644 index 1efeeb9a..00000000 --- a/packages/superdeck/lib/helpers/extensions.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:flutter/material.dart'; - -extension AsyncSnapshotX on AsyncSnapshot { - bool get isLoading => connectionState == ConnectionState.waiting; - - bool get isRefreshing => connectionState == ConnectionState.active; - Widget when({ - required Widget Function(T data) data, - required Widget Function(Object? error, StackTrace? stackTrace) error, - required Widget Function() loading, - }) { - if (hasError) { - return error(error, stackTrace); - } - - if (isLoading) { - return loading(); - } - - return data(this.data as T); - } -} - -extension BuildContextX on BuildContext { - ThemeData get theme => Theme.of(this); - TextTheme get textTheme => theme.textTheme; - ColorScheme get colorScheme => theme.colorScheme; -} - -extension StringX on String { - String capitalize() { - return "${this[0].toUpperCase()}${this.substring(1)}"; - } - - String snakeCase() { - return this - .replaceAll(RegExp(r'\s+'), '_') - .replaceAllMapped( - RegExp( - r'[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+'), - (match) => "${match.group(0)!.toLowerCase()}_") - .replaceAll(RegExp(r'(_)\1+'), '_') - .replaceAll(RegExp(r'^_|_$'), ''); - } -} diff --git a/packages/superdeck/lib/helpers/hooks.dart b/packages/superdeck/lib/helpers/hooks.dart deleted file mode 100644 index a5a3b83b..00000000 --- a/packages/superdeck/lib/helpers/hooks.dart +++ /dev/null @@ -1,139 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; - -void useEffectOnce(void Function()? Function() effect) { - useEffect(effect, []); -} - -void usePostFrameEffect(void Function() effect, - [List keys = const []]) { - useEffect(() { - WidgetsBinding.instance.addPostFrameCallback((_) { - effect(); - }); - }, keys); -} - -typedef ScrollVisibleControllerData = ({ - ItemScrollController itemScrollController, - ItemPositionsListener itemPositionsListener, - List visibleItems, -}); - -ScrollVisibleControllerData useScrollVisibleController({ - List? keys, -}) { - return use( - _ScrollVisibleController(keys: keys), - ); -} - -class _ScrollVisibleController extends Hook { - const _ScrollVisibleController({ - super.keys, - }); - - @override - HookState> - createState() => _ScrollVisibleControllerState(); -} - -class _ScrollVisibleControllerState - extends HookState { - late final controller = ItemScrollController(); - late final itemPositionsListener = ItemPositionsListener.create(); - var visibleItems = []; - - void _listener() { - visibleItems = itemPositionsListener.itemPositions.value.toList(); - } - - @override - void initHook() { - super.initHook(); - itemPositionsListener.itemPositions.addListener(_listener); - } - - @override - ScrollVisibleControllerData build(BuildContext context) => ( - itemScrollController: controller, - itemPositionsListener: itemPositionsListener, - visibleItems: visibleItems, - ); - - @override - void dispose() { - super.dispose(); - itemPositionsListener.itemPositions.removeListener(_listener); - } - - @override - String get debugLabel => 'useScrollVisibleController'; -} - -bool useIsFirstMount() { - final first = useRef(true); - - if (first.value) { - first.value = false; - - return true; - } - - return first.value; -} - -void useUpdateEffect(Dispose? Function() effect, [List? keys]) { - final isFirstMount = useIsFirstMount(); - - useEffect(() { - if (!isFirstMount) { - return effect(); - } - }, keys); -} - -T? useDistinct(T value, [Predicate? compare]) { - compare ??= (prev, next) => prev == next; - - final valueRef = useRef(value); - - if (valueRef.value == null || !compare(valueRef.value, value)) { - valueRef.value = value; - } - - return valueRef.value; -} - -typedef Predicate = bool Function(T prev, T next); - -OverlayPortalController useOverlayPortalController() { - return use( - _OverlayPortalController(), - ); -} - -class _OverlayPortalController extends Hook { - const _OverlayPortalController(); - - @override - HookState> - createState() => _OverlayPortalControllerState(); -} - -class _OverlayPortalControllerState - extends HookState { - late final controller = OverlayPortalController(); - - @override - OverlayPortalController build(BuildContext context) => controller; - - @override - void dispose() { - super.dispose(); - } - - @override - String get debugLabel => 'useOverlayPortalController'; -} diff --git a/packages/superdeck/lib/helpers/measure_size.dart b/packages/superdeck/lib/helpers/measure_size.dart deleted file mode 100644 index 27dffbe9..00000000 --- a/packages/superdeck/lib/helpers/measure_size.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; - -typedef OnWidgetSizeChange = void Function(Size size); - -class MeasureSizeRenderObject extends RenderProxyBox { - Size? oldSize; - OnWidgetSizeChange onChange; - - MeasureSizeRenderObject(this.onChange); - - @override - void performLayout() { - super.performLayout(); - - Size newSize = child!.size; - if (oldSize == newSize) return; - - oldSize = newSize; - WidgetsBinding.instance.addPostFrameCallback((_) { - onChange(newSize); - }); - } -} - -class MeasureSize extends SingleChildRenderObjectWidget { - final OnWidgetSizeChange onChange; - - const MeasureSize({ - super.key, - required this.onChange, - required Widget super.child, - }); - - @override - RenderObject createRenderObject(BuildContext context) { - return MeasureSizeRenderObject(onChange); - } - - @override - void updateRenderObject( - BuildContext context, covariant MeasureSizeRenderObject renderObject) { - renderObject.onChange = onChange; - } -} - -class MeasureSingleWidgetSize extends StatefulWidget { - final Widget child; - final void Function(Size) onChange; - - const MeasureSingleWidgetSize( - {super.key, required this.child, required this.onChange}); - - @override - _MeasureSingleWidgetSizeState createState() => - _MeasureSingleWidgetSizeState(); -} - -class _MeasureSingleWidgetSizeState extends State { - final GlobalKey _childKey = GlobalKey(); - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addPostFrameCallback(_measureHeight); - } - - void _measureHeight(Duration _) async { - final renderBox = - _childKey.currentContext?.findRenderObject() as RenderBox?; - final size = renderBox?.size; - - if (size == null || size == Size.zero) { - WidgetsBinding.instance.addPostFrameCallback(_measureHeight); - return; - } - - widget.onChange(size); - } - - @override - Widget build(BuildContext context) { - return Container( - key: _childKey, - child: widget.child, - ); - } -} diff --git a/packages/superdeck/lib/helpers/routes.dart b/packages/superdeck/lib/helpers/routes.dart deleted file mode 100644 index e2f07ce3..00000000 --- a/packages/superdeck/lib/helpers/routes.dart +++ /dev/null @@ -1,165 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:go_router_paths/go_router_paths.dart'; - -import '../../superdeck.dart'; -import '../screens/export_screen.dart'; -import '../screens/presentation_screen.dart'; -import 'dialog_page.dart'; - -class SDPaths { - static Path get root => Path('/'); - static ExportPath get export => ExportPath(); - static ChatPath get chat => ChatPath(); -} - -class ExportPath extends Path { - ExportPath() : super('export'); -} - -class ChatPath extends Path { - ChatPath() : super('chat'); -} - -class QueryParams { - static const drawer = 'drawer'; - static const slide = 'slide'; -} - -final kRootNavigatorKey = GlobalKey(debugLabel: 'root'); - -Map _previousQueryParams = {}; - -final goRouterConfig = GoRouter( - navigatorKey: kRootNavigatorKey, - initialLocation: SDPaths.root.goRoute, - restorationScopeId: 'root', - routes: [ - StatefulShellRoute.indexedStack( - redirect: (context, state) { - // Get the previous route's state from the navigation history - - // Extract current query parameters - final currentQueryParams = state.uri.queryParameters; - - final openDrawerParam = _previousQueryParams[QueryParams.drawer]; - final slideParam = _previousQueryParams[QueryParams.slide]; - - // Add any additional query parameters if needed - final allQueryParams = { - if (openDrawerParam != null) QueryParams.drawer: openDrawerParam, - if (slideParam != null) QueryParams.slide: slideParam, - ...currentQueryParams, - }; - - _previousQueryParams = allQueryParams; - - // Construct the target URI with query parameters - final uri = state.uri.replace( - queryParameters: allQueryParams.isEmpty ? null : allQueryParams, - ); - - return uri.toString(); - }, - restorationScopeId: 'shell1', - parentNavigatorKey: kRootNavigatorKey, - builder: (context, state, navigationShell) { - return AppShell(navigationShell: navigationShell); - }, - branches: [ - StatefulShellBranch( - routes: [ - GoRoute( - path: SDPaths.root.goRoute, - pageBuilder: (context, state) => _getPage( - PresentationScreen(), - state, - ), - ), - ], - ), - StatefulShellBranch( - routes: [ - GoRoute( - path: SDPaths.chat.goRoute, - pageBuilder: (context, state) => _getPage( - PresentationScreen(), - state, - ), - ), - ], - ), - StatefulShellBranch( - routes: [ - GoRoute( - path: SDPaths.export.goRoute, - pageBuilder: (context, state) { - return _getPage(ExportScreen(), state); - }, - ), - ], - ), - ], - ), - ], -); - -MaterialPage _getPage(Widget child, GoRouterState state) { - return MaterialPage(key: state.pageKey, child: child, maintainState: false); -} - -DialogPage _getDialogPage(Widget child, GoRouterState state) { - return DialogPage(key: state.pageKey, builder: (_) => Dialog(child: child)); -} - -extension BuildContextRoutesX on BuildContext { - int get currentSlidePage => int.parse(_slidePage ?? '1'); - int get currentSlideIndex => currentSlidePage - 1; - - void goToSlide(int page) => go(_replaceQueryParam('slide', '$page')); - - String get currentPath { - return GoRouterState.of(this).uri.toString(); - } - - void nextSlide() => goToSlide(currentSlidePage + 1); - - void previousSlide() => goToSlide(currentSlidePage - 1); - - String _replaceQueryParam(String key, String value) { - final uri = GoRouterState.of(this).uri; - final queryParameters = Map.from(uri.queryParameters); - queryParameters[key] = value; - return uri.replace(queryParameters: queryParameters).toString(); - } - - void openDrawer() => go(_replaceQueryParam(QueryParams.drawer, '1')); - - void closeDrawer() => go(_replaceQueryParam(QueryParams.drawer, '0')); - - void toggleDrawer() { - if (isDrawerOpen) { - closeDrawer(); - } else { - openDrawer(); - } - } - - Map get _queryParams { - return GoRouterState.of(this).uri.queryParameters; - } - - String? get _drawerParam => _queryParams[QueryParams.drawer]; - - String? get _slidePage => _queryParams[QueryParams.slide]; - - bool get isDrawerOpen => _drawerParam == '1'; - - void goPath(Path path) { - go(path.path); - } - - void pushPath(Path path) { - push(path.path); - } -} diff --git a/packages/superdeck/lib/helpers/section_tag.dart b/packages/superdeck/lib/helpers/section_tag.dart deleted file mode 100644 index 1ad778c5..00000000 --- a/packages/superdeck/lib/helpers/section_tag.dart +++ /dev/null @@ -1,65 +0,0 @@ -class SectionTag { - const SectionTag._(); - static const header = 'header'; - static const left = 'left'; - static const right = 'right'; - static const first = 'first'; -} - -const _syntaxTag = '::'; - -bool _isSyntaxTag(String line) { - final trimmed = line.trim(); - return trimmed.startsWith(_syntaxTag) && trimmed.endsWith(_syntaxTag); -} - -String _getTagName(String line) { - return line.trim().replaceAll(_syntaxTag, ''); -} - -Map parseContentSections(String input) { - final result = {}; - final lines = input.split('\n'); - var currentTag = SectionTag.first; - var currentContent = ''; - - // If ::tag:: is inside a ``` block, it should be ignored - var isCodeBlock = false; - - // For loop with index - for (var idx = 0; idx < lines.length; idx++) { - final line = lines[idx]; - - // Check if line is codeblock - if (line.startsWith('```')) { - isCodeBlock = !isCodeBlock; - } - - final isTag = _isSyntaxTag(line); - - if (isTag && !isCodeBlock) { - final tagName = _getTagName(line); - if (result.containsKey(tagName) || currentTag == tagName) { - throw Exception('Tag $tagName already exists'); - } - - if (currentContent.isNotEmpty) { - result[currentTag] = currentContent; - } - - currentContent = ''; - currentTag = tagName; - } else { - // check if its last line - if (idx == lines.length - 1) { - currentContent += line; - } else { - currentContent += '$line\n'; - } - } - } - if (currentContent.isNotEmpty) { - result[currentTag] = currentContent; - } - return result; -} diff --git a/packages/superdeck/lib/helpers/universal.dart b/packages/superdeck/lib/helpers/universal.dart deleted file mode 100644 index 2a1a0f57..00000000 --- a/packages/superdeck/lib/helpers/universal.dart +++ /dev/null @@ -1,285 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - -extension TargetPlatformX on TargetPlatform { - bool get isCupertino => - this == TargetPlatform.iOS || this == TargetPlatform.macOS; - bool get isMaterial => !isCupertino; -} - -class XPlatform extends InheritedWidget { - final TargetPlatform? overridePlatform; - - const XPlatform({ - super.key, - required super.child, - this.overridePlatform, - }); - - static TargetPlatform of(BuildContext context) { - final XPlatform? inherited = - context.dependOnInheritedWidgetOfExactType(); - return inherited?.overridePlatform ?? defaultTargetPlatform; - } - - @override - bool updateShouldNotify(XPlatform oldWidget) { - return overridePlatform != oldWidget.overridePlatform; - } -} - -class XButton extends StatelessWidget { - final VoidCallback onPressed; - final Widget child; - final Color? color; - - XButton({ - required this.onPressed, - required this.child, - this.color, - }); - - @override - Widget build(BuildContext context) { - final platform = XPlatform.of(context); - - if (platform.isCupertino) { - return CupertinoButton( - color: color, - onPressed: onPressed, - child: child, - ); - } else { - return ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: color, - ), - onPressed: onPressed, - child: child, - ); - } - } -} - -class XSwitch extends StatelessWidget { - final bool value; - final ValueChanged onChanged; - - XSwitch({ - required this.value, - required this.onChanged, - }); - - @override - Widget build(BuildContext context) { - final platform = XPlatform.of(context); - - if (platform.isCupertino) { - return CupertinoSwitch( - value: value, - onChanged: onChanged, - ); - } else { - return Switch( - value: value, - onChanged: onChanged, - ); - } - } -} - -class XDropdownButton extends StatefulWidget { - final bool isDense; - final Widget? underline; - final T? value; - final ValueChanged onChanged; - final List> items; - - XDropdownButton({ - super.key, - this.isDense = false, - this.underline, - required this.value, - required this.onChanged, - required this.items, - }); - - @override - _XDropdownButtonState createState() => _XDropdownButtonState(); -} - -class _XDropdownButtonState extends State> { - void _showCupertinoPicker(BuildContext context) { - showCupertinoModalPopup( - context: context, - builder: (BuildContext context) { - return Container( - height: 250, - color: CupertinoTheme.of(context).scaffoldBackgroundColor, - child: CupertinoPicker( - itemExtent: 32.0, - onSelectedItemChanged: (int index) { - widget.onChanged(widget.items[index].value); - }, - children: widget.items.map((item) => item.toCupertino()).toList(), - ), - ); - }, - ); - } - - @override - Widget build(BuildContext context) { - final platform = XPlatform.of(context); - - if (platform.isCupertino) { - Widget getSelectedWidget() { - final item = widget.items.firstWhere( - (item) => item.value == widget.value, - orElse: () => widget.items.first, - ); - - return item.toCupertino(); - } - - ; - return GestureDetector( - onTap: () => _showCupertinoPicker(context), - child: Container( - padding: widget.isDense - ? EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0) - : EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0), - decoration: BoxDecoration( - border: Border.all(color: CupertinoColors.inactiveGray), - borderRadius: BorderRadius.circular(8.0), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - getSelectedWidget(), - Icon( - CupertinoIcons.chevron_down, - color: CupertinoTheme.of(context).primaryColor, - ), - ], - ), - ), - ); - } else { - return DropdownButton( - isDense: widget.isDense, - underline: widget.underline, - value: widget.value, - onChanged: widget.onChanged, - items: widget.items.map((item) => item.toMaterial()).toList(), - ); - } - } -} - -class XDropdownMenuItem { - final T value; - final Widget child; - - XDropdownMenuItem({ - required this.value, - required this.child, - }); - - DropdownMenuItem toMaterial() { - return DropdownMenuItem( - value: value, - child: child, - ); - } - - Widget toCupertino() { - return child; - } -} - -class XAlertDialog extends StatelessWidget { - final Widget? title; - final Widget? content; - final List actions; - - const XAlertDialog({ - super.key, - this.title, - this.content, - this.actions = const [], - }); - - @override - Widget build(BuildContext context) { - final platform = XPlatform.of(context); - - if (platform.isCupertino) { - return CupertinoAlertDialog( - title: title, - content: content, - actions: actions.map((action) => action.toCupertino()).toList(), - ); - } else { - return AlertDialog( - title: title, - content: content, - actions: actions.map((action) => action.toMaterial()).toList(), - ); - } - } - - static Future show({ - required BuildContext context, - Widget? title, - Widget? content, - required List actions, - }) { - final platform = XPlatform.of(context); - - if (platform == TargetPlatform.iOS) { - return showCupertinoDialog( - context: context, - builder: (context) => XAlertDialog( - title: title, - content: content, - actions: actions, - ), - ); - } else { - return showDialog( - context: context, - builder: (context) => XAlertDialog( - title: title, - content: content, - actions: actions, - ), - ); - } - } -} - -class XDialogAction { - final Widget child; - final VoidCallback onPressed; - - XDialogAction({ - required this.child, - required this.onPressed, - }); - - CupertinoDialogAction toCupertino() { - return CupertinoDialogAction( - child: child, - onPressed: onPressed, - ); - } - - Widget toMaterial() { - return TextButton( - onPressed: onPressed, - child: child, - ); - } -} diff --git a/packages/superdeck/lib/helpers/value_notifiers.dart b/packages/superdeck/lib/helpers/value_notifiers.dart deleted file mode 100644 index a9a85081..00000000 --- a/packages/superdeck/lib/helpers/value_notifiers.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/foundation.dart'; -import 'package:localstorage/localstorage.dart'; - -// Create a ListNotifier like ValueNotifier that does comparison before setting the value -class ListNotifier extends ChangeNotifier { - ListNotifier(this._value); - - List _value; - - List get value => _value; - - set value(List newValue) { - if (listEquals(_value, newValue)) { - return; - } - _value = newValue; - notifyListeners(); - } - - int get length => _value.length; - - T operator [](int index) => _value[index]; - - void operator []=(int index, T newValue) { - _value[index] = newValue; - notifyListeners(); - } - - void add(T newValue) { - _value.add(newValue); - notifyListeners(); - } -} - -class StoredValueNotifier extends ValueNotifier { - final String key; - - StoredValueNotifier(this.key, T defaultValue) - : super(_getStoredValue(key, defaultValue)) { - addListener(() { - _setStoredValue(key, value); - }); - } - - static T _getStoredValue(String key, T defaultValue) { - final stringValue = localStorage.getItem(key); - return stringValue == null ? defaultValue : jsonDecode(stringValue) as T; - } - - static void _setStoredValue(String key, T value) { - localStorage.setItem(key, jsonEncode(value)); - } -} diff --git a/packages/superdeck/lib/helpers/watcher.dart b/packages/superdeck/lib/helpers/watcher.dart deleted file mode 100644 index 5a0fafed..00000000 --- a/packages/superdeck/lib/helpers/watcher.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -class FileWatcher { - final File file; - Timer? _timer; - DateTime? _lastModified; - - FileWatcher(this.file); - - /// Starts watching the file for changes - void start(void Function() onFileChange) { - _timer = Timer.periodic(const Duration(seconds: 2), (_) async { - final hasChanged = await _checkFileChanges(file); - - if (hasChanged) { - onFileChange(); - } - }); - } - - /// Stops watching the file - void stop() { - _timer?.cancel(); - } - - /// Checks if the file is currently being watched - bool get isWatching => _timer != null; - - Future _checkFileChanges(File file) async { - final currentLastModified = await file.lastModified(); - - if (_lastModified == null) { - _lastModified = currentLastModified; - return false; - } - - final result = currentLastModified != _lastModified; - - _lastModified = currentLastModified; - - return result; - } -} diff --git a/packages/superdeck/lib/models/asset_model.dart b/packages/superdeck/lib/models/asset_model.dart deleted file mode 100644 index 2d990a2b..00000000 --- a/packages/superdeck/lib/models/asset_model.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:dart_mappable/dart_mappable.dart'; -import 'package:path/path.dart' as p; - -import '../helpers/mappers.dart'; - -part 'asset_model.mapper.dart'; - -@MappableEnum() -enum AssetFileType { - png, - jpg, - jpeg, - gif, - webp; - - static AssetFileType parse(String value) { - return values.firstWhereOrNull((e) => e.name == value) ?? - (throw Exception('Invalid file type: $value')); - } - - static AssetFileType? tryParse(String value) { - return values.firstWhereOrNull((e) => value.startsWith(e.name)); - } - - bool isPng() => this == AssetFileType.png; - - bool isJpg() => this == AssetFileType.jpg || this == AssetFileType.jpeg; - - bool isGif() => this == AssetFileType.gif; -} - -@MappableClass( - includeCustomMappers: [ - FileMapper(), - SizeMapper(), - ], -) -final class SlideAsset with SlideAssetMappable { - final String path; - final int width; - final int height; - final String? reference; - - SlideAsset({ - required this.path, - required this.width, - required this.height, - required this.reference, - }); - - String get extension => p.extension(path); - - bool get isPortrait => height >= width; - - bool get isLandscape => !isPortrait; - - static const fromMap = SlideAssetMapper.fromMap; - static const fromJson = SlideAssetMapper.fromJson; -} diff --git a/packages/superdeck/lib/models/asset_model.mapper.dart b/packages/superdeck/lib/models/asset_model.mapper.dart deleted file mode 100644 index 96a62dd2..00000000 --- a/packages/superdeck/lib/models/asset_model.mapper.dart +++ /dev/null @@ -1,192 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member -// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter - -part of 'asset_model.dart'; - -class AssetFileTypeMapper extends EnumMapper { - AssetFileTypeMapper._(); - - static AssetFileTypeMapper? _instance; - static AssetFileTypeMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = AssetFileTypeMapper._()); - } - return _instance!; - } - - static AssetFileType fromValue(dynamic value) { - ensureInitialized(); - return MapperContainer.globals.fromValue(value); - } - - @override - AssetFileType decode(dynamic value) { - switch (value) { - case 'png': - return AssetFileType.png; - case 'jpg': - return AssetFileType.jpg; - case 'jpeg': - return AssetFileType.jpeg; - case 'gif': - return AssetFileType.gif; - case 'webp': - return AssetFileType.webp; - default: - throw MapperException.unknownEnumValue(value); - } - } - - @override - dynamic encode(AssetFileType self) { - switch (self) { - case AssetFileType.png: - return 'png'; - case AssetFileType.jpg: - return 'jpg'; - case AssetFileType.jpeg: - return 'jpeg'; - case AssetFileType.gif: - return 'gif'; - case AssetFileType.webp: - return 'webp'; - } - } -} - -extension AssetFileTypeMapperExtension on AssetFileType { - String toValue() { - AssetFileTypeMapper.ensureInitialized(); - return MapperContainer.globals.toValue(this) as String; - } -} - -class SlideAssetMapper extends ClassMapperBase { - SlideAssetMapper._(); - - static SlideAssetMapper? _instance; - static SlideAssetMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = SlideAssetMapper._()); - MapperContainer.globals.useAll([FileMapper(), SizeMapper()]); - } - return _instance!; - } - - @override - final String id = 'SlideAsset'; - - static String _$path(SlideAsset v) => v.path; - static const Field _f$path = Field('path', _$path); - static int _$width(SlideAsset v) => v.width; - static const Field _f$width = Field('width', _$width); - static int _$height(SlideAsset v) => v.height; - static const Field _f$height = Field('height', _$height); - static String? _$reference(SlideAsset v) => v.reference; - static const Field _f$reference = - Field('reference', _$reference); - - @override - final MappableFields fields = const { - #path: _f$path, - #width: _f$width, - #height: _f$height, - #reference: _f$reference, - }; - @override - final bool ignoreNull = true; - - static SlideAsset _instantiate(DecodingData data) { - return SlideAsset( - path: data.dec(_f$path), - width: data.dec(_f$width), - height: data.dec(_f$height), - reference: data.dec(_f$reference)); - } - - @override - final Function instantiate = _instantiate; - - static SlideAsset fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static SlideAsset fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin SlideAssetMappable { - String toJson() { - return SlideAssetMapper.ensureInitialized() - .encodeJson(this as SlideAsset); - } - - Map toMap() { - return SlideAssetMapper.ensureInitialized() - .encodeMap(this as SlideAsset); - } - - SlideAssetCopyWith get copyWith => - _SlideAssetCopyWithImpl(this as SlideAsset, $identity, $identity); - @override - String toString() { - return SlideAssetMapper.ensureInitialized() - .stringifyValue(this as SlideAsset); - } - - @override - bool operator ==(Object other) { - return SlideAssetMapper.ensureInitialized() - .equalsValue(this as SlideAsset, other); - } - - @override - int get hashCode { - return SlideAssetMapper.ensureInitialized().hashValue(this as SlideAsset); - } -} - -extension SlideAssetValueCopy<$R, $Out> - on ObjectCopyWith<$R, SlideAsset, $Out> { - SlideAssetCopyWith<$R, SlideAsset, $Out> get $asSlideAsset => - $base.as((v, t, t2) => _SlideAssetCopyWithImpl(v, t, t2)); -} - -abstract class SlideAssetCopyWith<$R, $In extends SlideAsset, $Out> - implements ClassCopyWith<$R, $In, $Out> { - $R call({String? path, int? width, int? height, String? reference}); - SlideAssetCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); -} - -class _SlideAssetCopyWithImpl<$R, $Out> - extends ClassCopyWithBase<$R, SlideAsset, $Out> - implements SlideAssetCopyWith<$R, SlideAsset, $Out> { - _SlideAssetCopyWithImpl(super.value, super.then, super.then2); - - @override - late final ClassMapperBase $mapper = - SlideAssetMapper.ensureInitialized(); - @override - $R call({String? path, int? width, int? height, Object? reference = $none}) => - $apply(FieldCopyWithData({ - if (path != null) #path: path, - if (width != null) #width: width, - if (height != null) #height: height, - if (reference != $none) #reference: reference - })); - @override - SlideAsset $make(CopyWithData data) => SlideAsset( - path: data.get(#path, or: $value.path), - width: data.get(#width, or: $value.width), - height: data.get(#height, or: $value.height), - reference: data.get(#reference, or: $value.reference)); - - @override - SlideAssetCopyWith<$R2, SlideAsset, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t) => - _SlideAssetCopyWithImpl($value, $cast, t); -} diff --git a/packages/superdeck/lib/models/config_model.dart b/packages/superdeck/lib/models/config_model.dart deleted file mode 100644 index e184ddc4..00000000 --- a/packages/superdeck/lib/models/config_model.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:dart_mappable/dart_mappable.dart'; - -import '../models/options_model.dart'; -import '../schema/schema_model.dart'; -import '../schema/schema_values.dart'; - -part 'config_model.mapper.dart'; - -@MappableClass() -abstract class BaseConfig with BaseConfigMappable { - final String? background; - final String? style; - final TransitionOptions? transition; - - const BaseConfig({ - required this.background, - required this.style, - required this.transition, - }); - - static final schema = SchemaShape( - { - "background": Schema.string, - "style": Schema.string, - "transition": TransitionOptions.schema.optional(), - }, - additionalProperties: false, - ); -} - -@MappableClass() -class Config extends BaseConfig with ConfigMappable { - final bool? cacheRemoteAssets; - - const Config({ - required super.background, - required super.style, - required super.transition, - this.cacheRemoteAssets, - }); - - const Config.empty() - : this( - cacheRemoteAssets: null, - background: null, - style: null, - transition: null, - ); - - static const fromMap = ConfigMapper.fromMap; - static const fromJson = ConfigMapper.fromJson; - - Map toSlideMap() { - final config = toMap(); - config.remove('cache_remote_assets'); - return config; - } - - static final schema = BaseConfig.schema.merge( - { - "cache_remote_assets": Schema.boolean.optional(), - "markdown_file": Schema.string.required().isPosixPath(), - "assets_dir": Schema.string.required().isPosixPath(), - }, - ); -} diff --git a/packages/superdeck/lib/models/config_model.mapper.dart b/packages/superdeck/lib/models/config_model.mapper.dart deleted file mode 100644 index daedab98..00000000 --- a/packages/superdeck/lib/models/config_model.mapper.dart +++ /dev/null @@ -1,211 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member -// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter - -part of 'config_model.dart'; - -class BaseConfigMapper extends ClassMapperBase { - BaseConfigMapper._(); - - static BaseConfigMapper? _instance; - static BaseConfigMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = BaseConfigMapper._()); - ConfigMapper.ensureInitialized(); - TransitionOptionsMapper.ensureInitialized(); - } - return _instance!; - } - - @override - final String id = 'BaseConfig'; - - static String? _$background(BaseConfig v) => v.background; - static const Field _f$background = - Field('background', _$background); - static String? _$style(BaseConfig v) => v.style; - static const Field _f$style = Field('style', _$style); - static TransitionOptions? _$transition(BaseConfig v) => v.transition; - static const Field _f$transition = - Field('transition', _$transition); - - @override - final MappableFields fields = const { - #background: _f$background, - #style: _f$style, - #transition: _f$transition, - }; - @override - final bool ignoreNull = true; - - static BaseConfig _instantiate(DecodingData data) { - throw MapperException.missingConstructor('BaseConfig'); - } - - @override - final Function instantiate = _instantiate; - - static BaseConfig fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static BaseConfig fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin BaseConfigMappable { - String toJson(); - Map toMap(); - BaseConfigCopyWith get copyWith; -} - -abstract class BaseConfigCopyWith<$R, $In extends BaseConfig, $Out> - implements ClassCopyWith<$R, $In, $Out> { - TransitionOptionsCopyWith<$R, TransitionOptions, TransitionOptions>? - get transition; - $R call({String? background, String? style, TransitionOptions? transition}); - BaseConfigCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); -} - -class ConfigMapper extends ClassMapperBase { - ConfigMapper._(); - - static ConfigMapper? _instance; - static ConfigMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = ConfigMapper._()); - BaseConfigMapper.ensureInitialized(); - TransitionOptionsMapper.ensureInitialized(); - } - return _instance!; - } - - @override - final String id = 'Config'; - - static String? _$background(Config v) => v.background; - static const Field _f$background = - Field('background', _$background); - static String? _$style(Config v) => v.style; - static const Field _f$style = Field('style', _$style); - static TransitionOptions? _$transition(Config v) => v.transition; - static const Field _f$transition = - Field('transition', _$transition); - static bool? _$cacheRemoteAssets(Config v) => v.cacheRemoteAssets; - static const Field _f$cacheRemoteAssets = Field( - 'cacheRemoteAssets', _$cacheRemoteAssets, - key: 'cache_remote_assets', opt: true); - - @override - final MappableFields fields = const { - #background: _f$background, - #style: _f$style, - #transition: _f$transition, - #cacheRemoteAssets: _f$cacheRemoteAssets, - }; - @override - final bool ignoreNull = true; - - static Config _instantiate(DecodingData data) { - return Config( - background: data.dec(_f$background), - style: data.dec(_f$style), - transition: data.dec(_f$transition), - cacheRemoteAssets: data.dec(_f$cacheRemoteAssets)); - } - - @override - final Function instantiate = _instantiate; - - static Config fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static Config fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin ConfigMappable { - String toJson() { - return ConfigMapper.ensureInitialized().encodeJson(this as Config); - } - - Map toMap() { - return ConfigMapper.ensureInitialized().encodeMap(this as Config); - } - - ConfigCopyWith get copyWith => - _ConfigCopyWithImpl(this as Config, $identity, $identity); - @override - String toString() { - return ConfigMapper.ensureInitialized().stringifyValue(this as Config); - } - - @override - bool operator ==(Object other) { - return ConfigMapper.ensureInitialized().equalsValue(this as Config, other); - } - - @override - int get hashCode { - return ConfigMapper.ensureInitialized().hashValue(this as Config); - } -} - -extension ConfigValueCopy<$R, $Out> on ObjectCopyWith<$R, Config, $Out> { - ConfigCopyWith<$R, Config, $Out> get $asConfig => - $base.as((v, t, t2) => _ConfigCopyWithImpl(v, t, t2)); -} - -abstract class ConfigCopyWith<$R, $In extends Config, $Out> - implements BaseConfigCopyWith<$R, $In, $Out> { - @override - TransitionOptionsCopyWith<$R, TransitionOptions, TransitionOptions>? - get transition; - @override - $R call( - {String? background, - String? style, - TransitionOptions? transition, - bool? cacheRemoteAssets}); - ConfigCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); -} - -class _ConfigCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Config, $Out> - implements ConfigCopyWith<$R, Config, $Out> { - _ConfigCopyWithImpl(super.value, super.then, super.then2); - - @override - late final ClassMapperBase $mapper = ConfigMapper.ensureInitialized(); - @override - TransitionOptionsCopyWith<$R, TransitionOptions, TransitionOptions>? - get transition => - $value.transition?.copyWith.$chain((v) => call(transition: v)); - @override - $R call( - {Object? background = $none, - Object? style = $none, - Object? transition = $none, - Object? cacheRemoteAssets = $none}) => - $apply(FieldCopyWithData({ - if (background != $none) #background: background, - if (style != $none) #style: style, - if (transition != $none) #transition: transition, - if (cacheRemoteAssets != $none) #cacheRemoteAssets: cacheRemoteAssets - })); - @override - Config $make(CopyWithData data) => Config( - background: data.get(#background, or: $value.background), - style: data.get(#style, or: $value.style), - transition: data.get(#transition, or: $value.transition), - cacheRemoteAssets: - data.get(#cacheRemoteAssets, or: $value.cacheRemoteAssets)); - - @override - ConfigCopyWith<$R2, Config, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => - _ConfigCopyWithImpl($value, $cast, t); -} diff --git a/packages/superdeck/lib/models/options_model.dart b/packages/superdeck/lib/models/options_model.dart deleted file mode 100644 index a1a76fc9..00000000 --- a/packages/superdeck/lib/models/options_model.dart +++ /dev/null @@ -1,334 +0,0 @@ -import 'package:dart_mappable/dart_mappable.dart'; -import 'package:flutter/material.dart'; - -import '../helpers/extensions.dart'; -import '../helpers/mappers.dart'; -import '../schema/schema_model.dart'; -import '../schema/schema_values.dart'; -import '../schema/validators.dart'; - -part 'options_model.mapper.dart'; - -@MappableClass() -class ContentOptions with ContentOptionsMappable { - final ContentAlignment alignment; - final int? flex; - - const ContentOptions({ - this.flex, - this.alignment = ContentAlignment.centerLeft, - }); - - ContentOptions merge(ContentOptions? other) { - if (other == null) return this; - return copyWith.$merge(other); - } - - static final schema = SchemaShape( - { - "alignment": ContentAlignment.schema.optional(), - "flex": Schema.integer.optional(), - }, - ); -} - -@MappableClass() -abstract class SplitOptions with SplitOptionsMappable { - final int? _flex; - final LayoutPosition? _position; - - const SplitOptions({ - int? flex, - LayoutPosition? position, - }) : _flex = flex, - _position = position; - - int get flex => _flex ?? 1; - - LayoutPosition get position => _position ?? LayoutPosition.left; - - static final schema = SchemaShape( - { - "flex": Schema.integer, - "position": LayoutPosition.schema, - }, - ); -} - -@MappableClass() -class ImageOptions extends SplitOptions with ImageOptionsMappable { - final String src; - final ImageFit? fit; - - const ImageOptions({ - required this.src, - this.fit, - super.flex, - super.position, - }); - - static final schema = SplitOptions.schema.merge( - { - "fit": ImageFit.schema, - "src": Schema.string.required(), - }, - ); -} - -@MappableClass() -class WidgetOptions extends SplitOptions with WidgetOptionsMappable { - final String name; - final Map args; - - const WidgetOptions({ - required this.name, - this.args = const {}, - super.flex, - super.position, - }); - - static final schema = SplitOptions.schema.merge( - { - "name": Schema.string.required(), - "args": Schema.any.optional(), - }, - ); -} - -@MappableClass( - includeCustomMappers: [DurationMapper()], -) -class TransitionOptions with TransitionOptionsMappable { - final TransitionType type; - final Duration? duration; - final Duration? delay; - final CurveType? curve; - - const TransitionOptions({ - required this.type, - this.duration, - this.delay, - this.curve, - }); - - static const fromJson = TransitionOptionsMapper.fromJson; - - Duration get totalAnimationDuration => - (duration ?? Duration.zero) + (delay ?? Duration.zero); - - TransitionOptions merge(TransitionOptions? other) { - if (other == null) return this; - return copyWith.$merge(other); - } - - static final schema = SchemaShape( - { - "type": TransitionType.schema.required(), - "duration": Schema.integer.optional(), - "delay": Schema.integer.optional(), - "curve": CurveType.schema.optional(), - }, - ); -} - -typedef Decoder = T Function(Map); - -T mapDecoder(Map args) { - return args as T; -} - -class ArgsSchema { - final SchemaShape validator; - final Decoder decoder; - - const ArgsSchema({ - required this.validator, - required this.decoder, - }); -} - -typedef ExampleBuilder = Widget Function(BuildContext context); - -@MappableEnum() -enum ImageFit { - fill, - contain, - cover, - fitWidth, - fitHeight, - none, - scaleDown; - - static final schema = Schema.string.isEnum(values); - - BoxFit toBoxFit() { - return switch (this) { - ImageFit.fill => BoxFit.fill, - ImageFit.contain => BoxFit.contain, - ImageFit.cover => BoxFit.cover, - ImageFit.fitWidth => BoxFit.fitWidth, - ImageFit.fitHeight => BoxFit.fitHeight, - ImageFit.none => BoxFit.none, - ImageFit.scaleDown => BoxFit.scaleDown, - }; - } -} - -class LayoutType { - const LayoutType._(); - static const simple = 'simple'; - static const image = 'image'; - static const widget = 'widget'; - static const twoColumn = 'two_column'; - static const twoColumnHeader = 'two_column_header'; - static const invalid = 'invalid'; -} - -@MappableEnum() -enum TransitionType { - // FadeIn Animations - fadeIn, - fadeInDown, - fadeInDownBig, - fadeInUp, - fadeInUpBig, - fadeInLeft, - fadeInLeftBig, - fadeInRight, - fadeInRightBig, - - // FadeOut Animations - fadeOut, - fadeOutDown, - fadeOutDownBig, - fadeOutUp, - fadeOutUpBig, - fadeOutLeft, - fadeOutLeftBig, - fadeOutRight, - fadeOutRightBig, - - // BounceIn Animations - bounceInDown, - bounceInUp, - bounceInLeft, - bounceInRight, - - // ElasticIn Animations - elasticIn, - elasticInDown, - elasticInUp, - elasticInLeft, - elasticInRight, - - // SlideIns Animations - slideInDown, - slideInUp, - slideInLeft, - slideInRight, - - // FlipIn Animations - flipInX, - flipInY, - - // Zooms - zoomIn, - zoomOut, - - // SpecialIn Animations - jelloIn, - - // Attention Seeker - bounce, - dance, - flash, - pulse, - roulette, - shakeX, - shakeY, - spin, - spinPerfect, - swing; - - static final schema = Schema.string.isEnum(values); -} - -@MappableEnum() -enum CurveType { - ease, - bounceIn, - bounceOut, - easeIn, - easeInOut, - easeOut, - elasticIn, - elasticInOut, - elasticOut, - fastLinearToSlowEaseIn, - fastOutSlowIn, - linear, - decelerate, - slowMiddle, - linearToEaseOut; - - static final schema = Schema.string.isEnum(values); -} - -@MappableEnum() -enum LayoutPosition { - left, - right, - top, - bottom; - - static final schema = Schema.string.isEnum(values); - - bool isHorizontal() { - return switch (this) { - LayoutPosition.left => true, - LayoutPosition.right => true, - LayoutPosition.top => false, - LayoutPosition.bottom => false, - }; - } - - bool isVertical() => !isHorizontal(); -} - -@MappableEnum() -enum ContentAlignment { - topLeft, - topCenter, - topRight, - centerLeft, - center, - centerRight, - bottomLeft, - bottomCenter, - bottomRight; - - static final schema = Schema.string.isEnum(values); - - Alignment toAlignment() { - return switch (this) { - ContentAlignment.topLeft => Alignment.topLeft, - ContentAlignment.topCenter => Alignment.topCenter, - ContentAlignment.topRight => Alignment.topRight, - ContentAlignment.centerLeft => Alignment.centerLeft, - ContentAlignment.center => Alignment.center, - ContentAlignment.centerRight => Alignment.centerRight, - ContentAlignment.bottomLeft => Alignment.bottomLeft, - ContentAlignment.bottomCenter => Alignment.bottomCenter, - ContentAlignment.bottomRight => Alignment.bottomRight, - }; - } -} - -extension on SchemaValue { - SchemaValue isEnum(List values) { - return copyWith(validators: [ - ...validators, - ArrayValidator(values.map((e) => e.name.snakeCase()).toList()), - ]); - } -} diff --git a/packages/superdeck/lib/models/options_model.mapper.dart b/packages/superdeck/lib/models/options_model.mapper.dart deleted file mode 100644 index 391f57ef..00000000 --- a/packages/superdeck/lib/models/options_model.mapper.dart +++ /dev/null @@ -1,1129 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member -// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter - -part of 'options_model.dart'; - -class ImageFitMapper extends EnumMapper { - ImageFitMapper._(); - - static ImageFitMapper? _instance; - static ImageFitMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = ImageFitMapper._()); - } - return _instance!; - } - - static ImageFit fromValue(dynamic value) { - ensureInitialized(); - return MapperContainer.globals.fromValue(value); - } - - @override - ImageFit decode(dynamic value) { - switch (value) { - case 'fill': - return ImageFit.fill; - case 'contain': - return ImageFit.contain; - case 'cover': - return ImageFit.cover; - case 'fit_width': - return ImageFit.fitWidth; - case 'fit_height': - return ImageFit.fitHeight; - case 'none': - return ImageFit.none; - case 'scale_down': - return ImageFit.scaleDown; - default: - throw MapperException.unknownEnumValue(value); - } - } - - @override - dynamic encode(ImageFit self) { - switch (self) { - case ImageFit.fill: - return 'fill'; - case ImageFit.contain: - return 'contain'; - case ImageFit.cover: - return 'cover'; - case ImageFit.fitWidth: - return 'fit_width'; - case ImageFit.fitHeight: - return 'fit_height'; - case ImageFit.none: - return 'none'; - case ImageFit.scaleDown: - return 'scale_down'; - } - } -} - -extension ImageFitMapperExtension on ImageFit { - String toValue() { - ImageFitMapper.ensureInitialized(); - return MapperContainer.globals.toValue(this) as String; - } -} - -class TransitionTypeMapper extends EnumMapper { - TransitionTypeMapper._(); - - static TransitionTypeMapper? _instance; - static TransitionTypeMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = TransitionTypeMapper._()); - } - return _instance!; - } - - static TransitionType fromValue(dynamic value) { - ensureInitialized(); - return MapperContainer.globals.fromValue(value); - } - - @override - TransitionType decode(dynamic value) { - switch (value) { - case 'fade_in': - return TransitionType.fadeIn; - case 'fade_in_down': - return TransitionType.fadeInDown; - case 'fade_in_down_big': - return TransitionType.fadeInDownBig; - case 'fade_in_up': - return TransitionType.fadeInUp; - case 'fade_in_up_big': - return TransitionType.fadeInUpBig; - case 'fade_in_left': - return TransitionType.fadeInLeft; - case 'fade_in_left_big': - return TransitionType.fadeInLeftBig; - case 'fade_in_right': - return TransitionType.fadeInRight; - case 'fade_in_right_big': - return TransitionType.fadeInRightBig; - case 'fade_out': - return TransitionType.fadeOut; - case 'fade_out_down': - return TransitionType.fadeOutDown; - case 'fade_out_down_big': - return TransitionType.fadeOutDownBig; - case 'fade_out_up': - return TransitionType.fadeOutUp; - case 'fade_out_up_big': - return TransitionType.fadeOutUpBig; - case 'fade_out_left': - return TransitionType.fadeOutLeft; - case 'fade_out_left_big': - return TransitionType.fadeOutLeftBig; - case 'fade_out_right': - return TransitionType.fadeOutRight; - case 'fade_out_right_big': - return TransitionType.fadeOutRightBig; - case 'bounce_in_down': - return TransitionType.bounceInDown; - case 'bounce_in_up': - return TransitionType.bounceInUp; - case 'bounce_in_left': - return TransitionType.bounceInLeft; - case 'bounce_in_right': - return TransitionType.bounceInRight; - case 'elastic_in': - return TransitionType.elasticIn; - case 'elastic_in_down': - return TransitionType.elasticInDown; - case 'elastic_in_up': - return TransitionType.elasticInUp; - case 'elastic_in_left': - return TransitionType.elasticInLeft; - case 'elastic_in_right': - return TransitionType.elasticInRight; - case 'slide_in_down': - return TransitionType.slideInDown; - case 'slide_in_up': - return TransitionType.slideInUp; - case 'slide_in_left': - return TransitionType.slideInLeft; - case 'slide_in_right': - return TransitionType.slideInRight; - case 'flip_in_x': - return TransitionType.flipInX; - case 'flip_in_y': - return TransitionType.flipInY; - case 'zoom_in': - return TransitionType.zoomIn; - case 'zoom_out': - return TransitionType.zoomOut; - case 'jello_in': - return TransitionType.jelloIn; - case 'bounce': - return TransitionType.bounce; - case 'dance': - return TransitionType.dance; - case 'flash': - return TransitionType.flash; - case 'pulse': - return TransitionType.pulse; - case 'roulette': - return TransitionType.roulette; - case 'shake_x': - return TransitionType.shakeX; - case 'shake_y': - return TransitionType.shakeY; - case 'spin': - return TransitionType.spin; - case 'spin_perfect': - return TransitionType.spinPerfect; - case 'swing': - return TransitionType.swing; - default: - throw MapperException.unknownEnumValue(value); - } - } - - @override - dynamic encode(TransitionType self) { - switch (self) { - case TransitionType.fadeIn: - return 'fade_in'; - case TransitionType.fadeInDown: - return 'fade_in_down'; - case TransitionType.fadeInDownBig: - return 'fade_in_down_big'; - case TransitionType.fadeInUp: - return 'fade_in_up'; - case TransitionType.fadeInUpBig: - return 'fade_in_up_big'; - case TransitionType.fadeInLeft: - return 'fade_in_left'; - case TransitionType.fadeInLeftBig: - return 'fade_in_left_big'; - case TransitionType.fadeInRight: - return 'fade_in_right'; - case TransitionType.fadeInRightBig: - return 'fade_in_right_big'; - case TransitionType.fadeOut: - return 'fade_out'; - case TransitionType.fadeOutDown: - return 'fade_out_down'; - case TransitionType.fadeOutDownBig: - return 'fade_out_down_big'; - case TransitionType.fadeOutUp: - return 'fade_out_up'; - case TransitionType.fadeOutUpBig: - return 'fade_out_up_big'; - case TransitionType.fadeOutLeft: - return 'fade_out_left'; - case TransitionType.fadeOutLeftBig: - return 'fade_out_left_big'; - case TransitionType.fadeOutRight: - return 'fade_out_right'; - case TransitionType.fadeOutRightBig: - return 'fade_out_right_big'; - case TransitionType.bounceInDown: - return 'bounce_in_down'; - case TransitionType.bounceInUp: - return 'bounce_in_up'; - case TransitionType.bounceInLeft: - return 'bounce_in_left'; - case TransitionType.bounceInRight: - return 'bounce_in_right'; - case TransitionType.elasticIn: - return 'elastic_in'; - case TransitionType.elasticInDown: - return 'elastic_in_down'; - case TransitionType.elasticInUp: - return 'elastic_in_up'; - case TransitionType.elasticInLeft: - return 'elastic_in_left'; - case TransitionType.elasticInRight: - return 'elastic_in_right'; - case TransitionType.slideInDown: - return 'slide_in_down'; - case TransitionType.slideInUp: - return 'slide_in_up'; - case TransitionType.slideInLeft: - return 'slide_in_left'; - case TransitionType.slideInRight: - return 'slide_in_right'; - case TransitionType.flipInX: - return 'flip_in_x'; - case TransitionType.flipInY: - return 'flip_in_y'; - case TransitionType.zoomIn: - return 'zoom_in'; - case TransitionType.zoomOut: - return 'zoom_out'; - case TransitionType.jelloIn: - return 'jello_in'; - case TransitionType.bounce: - return 'bounce'; - case TransitionType.dance: - return 'dance'; - case TransitionType.flash: - return 'flash'; - case TransitionType.pulse: - return 'pulse'; - case TransitionType.roulette: - return 'roulette'; - case TransitionType.shakeX: - return 'shake_x'; - case TransitionType.shakeY: - return 'shake_y'; - case TransitionType.spin: - return 'spin'; - case TransitionType.spinPerfect: - return 'spin_perfect'; - case TransitionType.swing: - return 'swing'; - } - } -} - -extension TransitionTypeMapperExtension on TransitionType { - String toValue() { - TransitionTypeMapper.ensureInitialized(); - return MapperContainer.globals.toValue(this) as String; - } -} - -class CurveTypeMapper extends EnumMapper { - CurveTypeMapper._(); - - static CurveTypeMapper? _instance; - static CurveTypeMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = CurveTypeMapper._()); - } - return _instance!; - } - - static CurveType fromValue(dynamic value) { - ensureInitialized(); - return MapperContainer.globals.fromValue(value); - } - - @override - CurveType decode(dynamic value) { - switch (value) { - case 'ease': - return CurveType.ease; - case 'bounce_in': - return CurveType.bounceIn; - case 'bounce_out': - return CurveType.bounceOut; - case 'ease_in': - return CurveType.easeIn; - case 'ease_in_out': - return CurveType.easeInOut; - case 'ease_out': - return CurveType.easeOut; - case 'elastic_in': - return CurveType.elasticIn; - case 'elastic_in_out': - return CurveType.elasticInOut; - case 'elastic_out': - return CurveType.elasticOut; - case 'fast_linear_to_slow_ease_in': - return CurveType.fastLinearToSlowEaseIn; - case 'fast_out_slow_in': - return CurveType.fastOutSlowIn; - case 'linear': - return CurveType.linear; - case 'decelerate': - return CurveType.decelerate; - case 'slow_middle': - return CurveType.slowMiddle; - case 'linear_to_ease_out': - return CurveType.linearToEaseOut; - default: - throw MapperException.unknownEnumValue(value); - } - } - - @override - dynamic encode(CurveType self) { - switch (self) { - case CurveType.ease: - return 'ease'; - case CurveType.bounceIn: - return 'bounce_in'; - case CurveType.bounceOut: - return 'bounce_out'; - case CurveType.easeIn: - return 'ease_in'; - case CurveType.easeInOut: - return 'ease_in_out'; - case CurveType.easeOut: - return 'ease_out'; - case CurveType.elasticIn: - return 'elastic_in'; - case CurveType.elasticInOut: - return 'elastic_in_out'; - case CurveType.elasticOut: - return 'elastic_out'; - case CurveType.fastLinearToSlowEaseIn: - return 'fast_linear_to_slow_ease_in'; - case CurveType.fastOutSlowIn: - return 'fast_out_slow_in'; - case CurveType.linear: - return 'linear'; - case CurveType.decelerate: - return 'decelerate'; - case CurveType.slowMiddle: - return 'slow_middle'; - case CurveType.linearToEaseOut: - return 'linear_to_ease_out'; - } - } -} - -extension CurveTypeMapperExtension on CurveType { - String toValue() { - CurveTypeMapper.ensureInitialized(); - return MapperContainer.globals.toValue(this) as String; - } -} - -class LayoutPositionMapper extends EnumMapper { - LayoutPositionMapper._(); - - static LayoutPositionMapper? _instance; - static LayoutPositionMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = LayoutPositionMapper._()); - } - return _instance!; - } - - static LayoutPosition fromValue(dynamic value) { - ensureInitialized(); - return MapperContainer.globals.fromValue(value); - } - - @override - LayoutPosition decode(dynamic value) { - switch (value) { - case 'left': - return LayoutPosition.left; - case 'right': - return LayoutPosition.right; - case 'top': - return LayoutPosition.top; - case 'bottom': - return LayoutPosition.bottom; - default: - throw MapperException.unknownEnumValue(value); - } - } - - @override - dynamic encode(LayoutPosition self) { - switch (self) { - case LayoutPosition.left: - return 'left'; - case LayoutPosition.right: - return 'right'; - case LayoutPosition.top: - return 'top'; - case LayoutPosition.bottom: - return 'bottom'; - } - } -} - -extension LayoutPositionMapperExtension on LayoutPosition { - String toValue() { - LayoutPositionMapper.ensureInitialized(); - return MapperContainer.globals.toValue(this) as String; - } -} - -class ContentAlignmentMapper extends EnumMapper { - ContentAlignmentMapper._(); - - static ContentAlignmentMapper? _instance; - static ContentAlignmentMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = ContentAlignmentMapper._()); - } - return _instance!; - } - - static ContentAlignment fromValue(dynamic value) { - ensureInitialized(); - return MapperContainer.globals.fromValue(value); - } - - @override - ContentAlignment decode(dynamic value) { - switch (value) { - case 'top_left': - return ContentAlignment.topLeft; - case 'top_center': - return ContentAlignment.topCenter; - case 'top_right': - return ContentAlignment.topRight; - case 'center_left': - return ContentAlignment.centerLeft; - case 'center': - return ContentAlignment.center; - case 'center_right': - return ContentAlignment.centerRight; - case 'bottom_left': - return ContentAlignment.bottomLeft; - case 'bottom_center': - return ContentAlignment.bottomCenter; - case 'bottom_right': - return ContentAlignment.bottomRight; - default: - throw MapperException.unknownEnumValue(value); - } - } - - @override - dynamic encode(ContentAlignment self) { - switch (self) { - case ContentAlignment.topLeft: - return 'top_left'; - case ContentAlignment.topCenter: - return 'top_center'; - case ContentAlignment.topRight: - return 'top_right'; - case ContentAlignment.centerLeft: - return 'center_left'; - case ContentAlignment.center: - return 'center'; - case ContentAlignment.centerRight: - return 'center_right'; - case ContentAlignment.bottomLeft: - return 'bottom_left'; - case ContentAlignment.bottomCenter: - return 'bottom_center'; - case ContentAlignment.bottomRight: - return 'bottom_right'; - } - } -} - -extension ContentAlignmentMapperExtension on ContentAlignment { - String toValue() { - ContentAlignmentMapper.ensureInitialized(); - return MapperContainer.globals.toValue(this) as String; - } -} - -class ContentOptionsMapper extends ClassMapperBase { - ContentOptionsMapper._(); - - static ContentOptionsMapper? _instance; - static ContentOptionsMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = ContentOptionsMapper._()); - ContentAlignmentMapper.ensureInitialized(); - } - return _instance!; - } - - @override - final String id = 'ContentOptions'; - - static int? _$flex(ContentOptions v) => v.flex; - static const Field _f$flex = - Field('flex', _$flex, opt: true); - static ContentAlignment _$alignment(ContentOptions v) => v.alignment; - static const Field _f$alignment = Field( - 'alignment', _$alignment, - opt: true, def: ContentAlignment.centerLeft); - - @override - final MappableFields fields = const { - #flex: _f$flex, - #alignment: _f$alignment, - }; - @override - final bool ignoreNull = true; - - static ContentOptions _instantiate(DecodingData data) { - return ContentOptions( - flex: data.dec(_f$flex), alignment: data.dec(_f$alignment)); - } - - @override - final Function instantiate = _instantiate; - - static ContentOptions fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static ContentOptions fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin ContentOptionsMappable { - String toJson() { - return ContentOptionsMapper.ensureInitialized() - .encodeJson(this as ContentOptions); - } - - Map toMap() { - return ContentOptionsMapper.ensureInitialized() - .encodeMap(this as ContentOptions); - } - - ContentOptionsCopyWith - get copyWith => _ContentOptionsCopyWithImpl( - this as ContentOptions, $identity, $identity); - @override - String toString() { - return ContentOptionsMapper.ensureInitialized() - .stringifyValue(this as ContentOptions); - } - - @override - bool operator ==(Object other) { - return ContentOptionsMapper.ensureInitialized() - .equalsValue(this as ContentOptions, other); - } - - @override - int get hashCode { - return ContentOptionsMapper.ensureInitialized() - .hashValue(this as ContentOptions); - } -} - -extension ContentOptionsValueCopy<$R, $Out> - on ObjectCopyWith<$R, ContentOptions, $Out> { - ContentOptionsCopyWith<$R, ContentOptions, $Out> get $asContentOptions => - $base.as((v, t, t2) => _ContentOptionsCopyWithImpl(v, t, t2)); -} - -abstract class ContentOptionsCopyWith<$R, $In extends ContentOptions, $Out> - implements ClassCopyWith<$R, $In, $Out> { - $R call({int? flex, ContentAlignment? alignment}); - ContentOptionsCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t); -} - -class _ContentOptionsCopyWithImpl<$R, $Out> - extends ClassCopyWithBase<$R, ContentOptions, $Out> - implements ContentOptionsCopyWith<$R, ContentOptions, $Out> { - _ContentOptionsCopyWithImpl(super.value, super.then, super.then2); - - @override - late final ClassMapperBase $mapper = - ContentOptionsMapper.ensureInitialized(); - @override - $R call({Object? flex = $none, ContentAlignment? alignment}) => - $apply(FieldCopyWithData({ - if (flex != $none) #flex: flex, - if (alignment != null) #alignment: alignment - })); - @override - ContentOptions $make(CopyWithData data) => ContentOptions( - flex: data.get(#flex, or: $value.flex), - alignment: data.get(#alignment, or: $value.alignment)); - - @override - ContentOptionsCopyWith<$R2, ContentOptions, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t) => - _ContentOptionsCopyWithImpl($value, $cast, t); -} - -class SplitOptionsMapper extends ClassMapperBase { - SplitOptionsMapper._(); - - static SplitOptionsMapper? _instance; - static SplitOptionsMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = SplitOptionsMapper._()); - ImageOptionsMapper.ensureInitialized(); - WidgetOptionsMapper.ensureInitialized(); - LayoutPositionMapper.ensureInitialized(); - } - return _instance!; - } - - @override - final String id = 'SplitOptions'; - - static int? _$_flex(SplitOptions v) => v._flex; - static const Field _f$_flex = - Field('_flex', _$_flex, key: 'flex', opt: true); - static LayoutPosition? _$_position(SplitOptions v) => v._position; - static const Field _f$_position = - Field('_position', _$_position, key: 'position', opt: true); - - @override - final MappableFields fields = const { - #_flex: _f$_flex, - #_position: _f$_position, - }; - @override - final bool ignoreNull = true; - - static SplitOptions _instantiate(DecodingData data) { - throw MapperException.missingConstructor('SplitOptions'); - } - - @override - final Function instantiate = _instantiate; - - static SplitOptions fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static SplitOptions fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin SplitOptionsMappable { - String toJson(); - Map toMap(); - SplitOptionsCopyWith get copyWith; -} - -abstract class SplitOptionsCopyWith<$R, $In extends SplitOptions, $Out> - implements ClassCopyWith<$R, $In, $Out> { - $R call({int? flex, LayoutPosition? position}); - SplitOptionsCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); -} - -class ImageOptionsMapper extends ClassMapperBase { - ImageOptionsMapper._(); - - static ImageOptionsMapper? _instance; - static ImageOptionsMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = ImageOptionsMapper._()); - SplitOptionsMapper.ensureInitialized(); - ImageFitMapper.ensureInitialized(); - LayoutPositionMapper.ensureInitialized(); - } - return _instance!; - } - - @override - final String id = 'ImageOptions'; - - static String _$src(ImageOptions v) => v.src; - static const Field _f$src = Field('src', _$src); - static ImageFit? _$fit(ImageOptions v) => v.fit; - static const Field _f$fit = - Field('fit', _$fit, opt: true); - static int? _$_flex(ImageOptions v) => v._flex; - static const Field _f$_flex = - Field('_flex', _$_flex, key: 'flex', opt: true); - static LayoutPosition? _$_position(ImageOptions v) => v._position; - static const Field _f$_position = - Field('_position', _$_position, key: 'position', opt: true); - - @override - final MappableFields fields = const { - #src: _f$src, - #fit: _f$fit, - #_flex: _f$_flex, - #_position: _f$_position, - }; - @override - final bool ignoreNull = true; - - static ImageOptions _instantiate(DecodingData data) { - return ImageOptions( - src: data.dec(_f$src), - fit: data.dec(_f$fit), - flex: data.dec(_f$_flex), - position: data.dec(_f$_position)); - } - - @override - final Function instantiate = _instantiate; - - static ImageOptions fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static ImageOptions fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin ImageOptionsMappable { - String toJson() { - return ImageOptionsMapper.ensureInitialized() - .encodeJson(this as ImageOptions); - } - - Map toMap() { - return ImageOptionsMapper.ensureInitialized() - .encodeMap(this as ImageOptions); - } - - ImageOptionsCopyWith get copyWith => - _ImageOptionsCopyWithImpl(this as ImageOptions, $identity, $identity); - @override - String toString() { - return ImageOptionsMapper.ensureInitialized() - .stringifyValue(this as ImageOptions); - } - - @override - bool operator ==(Object other) { - return ImageOptionsMapper.ensureInitialized() - .equalsValue(this as ImageOptions, other); - } - - @override - int get hashCode { - return ImageOptionsMapper.ensureInitialized() - .hashValue(this as ImageOptions); - } -} - -extension ImageOptionsValueCopy<$R, $Out> - on ObjectCopyWith<$R, ImageOptions, $Out> { - ImageOptionsCopyWith<$R, ImageOptions, $Out> get $asImageOptions => - $base.as((v, t, t2) => _ImageOptionsCopyWithImpl(v, t, t2)); -} - -abstract class ImageOptionsCopyWith<$R, $In extends ImageOptions, $Out> - implements SplitOptionsCopyWith<$R, $In, $Out> { - @override - $R call({String? src, ImageFit? fit, int? flex, LayoutPosition? position}); - ImageOptionsCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); -} - -class _ImageOptionsCopyWithImpl<$R, $Out> - extends ClassCopyWithBase<$R, ImageOptions, $Out> - implements ImageOptionsCopyWith<$R, ImageOptions, $Out> { - _ImageOptionsCopyWithImpl(super.value, super.then, super.then2); - - @override - late final ClassMapperBase $mapper = - ImageOptionsMapper.ensureInitialized(); - @override - $R call( - {String? src, - Object? fit = $none, - Object? flex = $none, - Object? position = $none}) => - $apply(FieldCopyWithData({ - if (src != null) #src: src, - if (fit != $none) #fit: fit, - if (flex != $none) #flex: flex, - if (position != $none) #position: position - })); - @override - ImageOptions $make(CopyWithData data) => ImageOptions( - src: data.get(#src, or: $value.src), - fit: data.get(#fit, or: $value.fit), - flex: data.get(#flex, or: $value._flex), - position: data.get(#position, or: $value._position)); - - @override - ImageOptionsCopyWith<$R2, ImageOptions, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t) => - _ImageOptionsCopyWithImpl($value, $cast, t); -} - -class WidgetOptionsMapper extends ClassMapperBase { - WidgetOptionsMapper._(); - - static WidgetOptionsMapper? _instance; - static WidgetOptionsMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = WidgetOptionsMapper._()); - SplitOptionsMapper.ensureInitialized(); - LayoutPositionMapper.ensureInitialized(); - } - return _instance!; - } - - @override - final String id = 'WidgetOptions'; - - static String _$name(WidgetOptions v) => v.name; - static const Field _f$name = Field('name', _$name); - static Map _$args(WidgetOptions v) => v.args; - static const Field> _f$args = - Field('args', _$args, opt: true, def: const {}); - static int? _$_flex(WidgetOptions v) => v._flex; - static const Field _f$_flex = - Field('_flex', _$_flex, key: 'flex', opt: true); - static LayoutPosition? _$_position(WidgetOptions v) => v._position; - static const Field _f$_position = - Field('_position', _$_position, key: 'position', opt: true); - - @override - final MappableFields fields = const { - #name: _f$name, - #args: _f$args, - #_flex: _f$_flex, - #_position: _f$_position, - }; - @override - final bool ignoreNull = true; - - static WidgetOptions _instantiate(DecodingData data) { - return WidgetOptions( - name: data.dec(_f$name), - args: data.dec(_f$args), - flex: data.dec(_f$_flex), - position: data.dec(_f$_position)); - } - - @override - final Function instantiate = _instantiate; - - static WidgetOptions fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static WidgetOptions fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin WidgetOptionsMappable { - String toJson() { - return WidgetOptionsMapper.ensureInitialized() - .encodeJson(this as WidgetOptions); - } - - Map toMap() { - return WidgetOptionsMapper.ensureInitialized() - .encodeMap(this as WidgetOptions); - } - - WidgetOptionsCopyWith - get copyWith => _WidgetOptionsCopyWithImpl( - this as WidgetOptions, $identity, $identity); - @override - String toString() { - return WidgetOptionsMapper.ensureInitialized() - .stringifyValue(this as WidgetOptions); - } - - @override - bool operator ==(Object other) { - return WidgetOptionsMapper.ensureInitialized() - .equalsValue(this as WidgetOptions, other); - } - - @override - int get hashCode { - return WidgetOptionsMapper.ensureInitialized() - .hashValue(this as WidgetOptions); - } -} - -extension WidgetOptionsValueCopy<$R, $Out> - on ObjectCopyWith<$R, WidgetOptions, $Out> { - WidgetOptionsCopyWith<$R, WidgetOptions, $Out> get $asWidgetOptions => - $base.as((v, t, t2) => _WidgetOptionsCopyWithImpl(v, t, t2)); -} - -abstract class WidgetOptionsCopyWith<$R, $In extends WidgetOptions, $Out> - implements SplitOptionsCopyWith<$R, $In, $Out> { - MapCopyWith<$R, String, dynamic, ObjectCopyWith<$R, dynamic, dynamic>> - get args; - @override - $R call( - {String? name, - Map? args, - int? flex, - LayoutPosition? position}); - WidgetOptionsCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); -} - -class _WidgetOptionsCopyWithImpl<$R, $Out> - extends ClassCopyWithBase<$R, WidgetOptions, $Out> - implements WidgetOptionsCopyWith<$R, WidgetOptions, $Out> { - _WidgetOptionsCopyWithImpl(super.value, super.then, super.then2); - - @override - late final ClassMapperBase $mapper = - WidgetOptionsMapper.ensureInitialized(); - @override - MapCopyWith<$R, String, dynamic, ObjectCopyWith<$R, dynamic, dynamic>> - get args => MapCopyWith($value.args, - (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(args: v)); - @override - $R call( - {String? name, - Map? args, - Object? flex = $none, - Object? position = $none}) => - $apply(FieldCopyWithData({ - if (name != null) #name: name, - if (args != null) #args: args, - if (flex != $none) #flex: flex, - if (position != $none) #position: position - })); - @override - WidgetOptions $make(CopyWithData data) => WidgetOptions( - name: data.get(#name, or: $value.name), - args: data.get(#args, or: $value.args), - flex: data.get(#flex, or: $value._flex), - position: data.get(#position, or: $value._position)); - - @override - WidgetOptionsCopyWith<$R2, WidgetOptions, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t) => - _WidgetOptionsCopyWithImpl($value, $cast, t); -} - -class TransitionOptionsMapper extends ClassMapperBase { - TransitionOptionsMapper._(); - - static TransitionOptionsMapper? _instance; - static TransitionOptionsMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = TransitionOptionsMapper._()); - MapperContainer.globals.useAll([DurationMapper()]); - TransitionTypeMapper.ensureInitialized(); - CurveTypeMapper.ensureInitialized(); - } - return _instance!; - } - - @override - final String id = 'TransitionOptions'; - - static TransitionType _$type(TransitionOptions v) => v.type; - static const Field _f$type = - Field('type', _$type); - static Duration? _$duration(TransitionOptions v) => v.duration; - static const Field _f$duration = - Field('duration', _$duration, opt: true); - static Duration? _$delay(TransitionOptions v) => v.delay; - static const Field _f$delay = - Field('delay', _$delay, opt: true); - static CurveType? _$curve(TransitionOptions v) => v.curve; - static const Field _f$curve = - Field('curve', _$curve, opt: true); - - @override - final MappableFields fields = const { - #type: _f$type, - #duration: _f$duration, - #delay: _f$delay, - #curve: _f$curve, - }; - @override - final bool ignoreNull = true; - - static TransitionOptions _instantiate(DecodingData data) { - return TransitionOptions( - type: data.dec(_f$type), - duration: data.dec(_f$duration), - delay: data.dec(_f$delay), - curve: data.dec(_f$curve)); - } - - @override - final Function instantiate = _instantiate; - - static TransitionOptions fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static TransitionOptions fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin TransitionOptionsMappable { - String toJson() { - return TransitionOptionsMapper.ensureInitialized() - .encodeJson(this as TransitionOptions); - } - - Map toMap() { - return TransitionOptionsMapper.ensureInitialized() - .encodeMap(this as TransitionOptions); - } - - TransitionOptionsCopyWith - get copyWith => _TransitionOptionsCopyWithImpl( - this as TransitionOptions, $identity, $identity); - @override - String toString() { - return TransitionOptionsMapper.ensureInitialized() - .stringifyValue(this as TransitionOptions); - } - - @override - bool operator ==(Object other) { - return TransitionOptionsMapper.ensureInitialized() - .equalsValue(this as TransitionOptions, other); - } - - @override - int get hashCode { - return TransitionOptionsMapper.ensureInitialized() - .hashValue(this as TransitionOptions); - } -} - -extension TransitionOptionsValueCopy<$R, $Out> - on ObjectCopyWith<$R, TransitionOptions, $Out> { - TransitionOptionsCopyWith<$R, TransitionOptions, $Out> - get $asTransitionOptions => - $base.as((v, t, t2) => _TransitionOptionsCopyWithImpl(v, t, t2)); -} - -abstract class TransitionOptionsCopyWith<$R, $In extends TransitionOptions, - $Out> implements ClassCopyWith<$R, $In, $Out> { - $R call( - {TransitionType? type, - Duration? duration, - Duration? delay, - CurveType? curve}); - TransitionOptionsCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t); -} - -class _TransitionOptionsCopyWithImpl<$R, $Out> - extends ClassCopyWithBase<$R, TransitionOptions, $Out> - implements TransitionOptionsCopyWith<$R, TransitionOptions, $Out> { - _TransitionOptionsCopyWithImpl(super.value, super.then, super.then2); - - @override - late final ClassMapperBase $mapper = - TransitionOptionsMapper.ensureInitialized(); - @override - $R call( - {TransitionType? type, - Object? duration = $none, - Object? delay = $none, - Object? curve = $none}) => - $apply(FieldCopyWithData({ - if (type != null) #type: type, - if (duration != $none) #duration: duration, - if (delay != $none) #delay: delay, - if (curve != $none) #curve: curve - })); - @override - TransitionOptions $make(CopyWithData data) => TransitionOptions( - type: data.get(#type, or: $value.type), - duration: data.get(#duration, or: $value.duration), - delay: data.get(#delay, or: $value.delay), - curve: data.get(#curve, or: $value.curve)); - - @override - TransitionOptionsCopyWith<$R2, TransitionOptions, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t) => - _TransitionOptionsCopyWithImpl($value, $cast, t); -} diff --git a/packages/superdeck/lib/models/reference_model.dart b/packages/superdeck/lib/models/reference_model.dart deleted file mode 100644 index 47ae8be7..00000000 --- a/packages/superdeck/lib/models/reference_model.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:dart_mappable/dart_mappable.dart'; - -import 'asset_model.dart'; -import 'config_model.dart'; -import 'slide_model.dart'; - -part 'reference_model.mapper.dart'; - -@MappableClass() -class SuperDeckReference with SuperDeckReferenceMappable { - final Config config; - final List slides; - final List assets; - - SuperDeckReference({ - required this.config, - required this.slides, - required this.assets, - }); - - const SuperDeckReference.empty() - : config = const Config.empty(), - slides = const [], - assets = const []; - - static const fromMap = SuperDeckReferenceMapper.fromMap; - static const fromJson = SuperDeckReferenceMapper.fromJson; -} diff --git a/packages/superdeck/lib/models/reference_model.mapper.dart b/packages/superdeck/lib/models/reference_model.mapper.dart deleted file mode 100644 index 4983e382..00000000 --- a/packages/superdeck/lib/models/reference_model.mapper.dart +++ /dev/null @@ -1,152 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member -// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter - -part of 'reference_model.dart'; - -class SuperDeckReferenceMapper extends ClassMapperBase { - SuperDeckReferenceMapper._(); - - static SuperDeckReferenceMapper? _instance; - static SuperDeckReferenceMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = SuperDeckReferenceMapper._()); - ConfigMapper.ensureInitialized(); - SlideMapper.ensureInitialized(); - SlideAssetMapper.ensureInitialized(); - } - return _instance!; - } - - @override - final String id = 'SuperDeckReference'; - - static Config _$config(SuperDeckReference v) => v.config; - static const Field _f$config = - Field('config', _$config); - static List _$slides(SuperDeckReference v) => v.slides; - static const Field> _f$slides = - Field('slides', _$slides); - static List _$assets(SuperDeckReference v) => v.assets; - static const Field> _f$assets = - Field('assets', _$assets); - - @override - final MappableFields fields = const { - #config: _f$config, - #slides: _f$slides, - #assets: _f$assets, - }; - @override - final bool ignoreNull = true; - - static SuperDeckReference _instantiate(DecodingData data) { - return SuperDeckReference( - config: data.dec(_f$config), - slides: data.dec(_f$slides), - assets: data.dec(_f$assets)); - } - - @override - final Function instantiate = _instantiate; - - static SuperDeckReference fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static SuperDeckReference fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin SuperDeckReferenceMappable { - String toJson() { - return SuperDeckReferenceMapper.ensureInitialized() - .encodeJson(this as SuperDeckReference); - } - - Map toMap() { - return SuperDeckReferenceMapper.ensureInitialized() - .encodeMap(this as SuperDeckReference); - } - - SuperDeckReferenceCopyWith - get copyWith => _SuperDeckReferenceCopyWithImpl( - this as SuperDeckReference, $identity, $identity); - @override - String toString() { - return SuperDeckReferenceMapper.ensureInitialized() - .stringifyValue(this as SuperDeckReference); - } - - @override - bool operator ==(Object other) { - return SuperDeckReferenceMapper.ensureInitialized() - .equalsValue(this as SuperDeckReference, other); - } - - @override - int get hashCode { - return SuperDeckReferenceMapper.ensureInitialized() - .hashValue(this as SuperDeckReference); - } -} - -extension SuperDeckReferenceValueCopy<$R, $Out> - on ObjectCopyWith<$R, SuperDeckReference, $Out> { - SuperDeckReferenceCopyWith<$R, SuperDeckReference, $Out> - get $asSuperDeckReference => - $base.as((v, t, t2) => _SuperDeckReferenceCopyWithImpl(v, t, t2)); -} - -abstract class SuperDeckReferenceCopyWith<$R, $In extends SuperDeckReference, - $Out> implements ClassCopyWith<$R, $In, $Out> { - ConfigCopyWith<$R, Config, Config> get config; - ListCopyWith<$R, Slide, SlideCopyWith<$R, Slide, Slide>> get slides; - ListCopyWith<$R, SlideAsset, SlideAssetCopyWith<$R, SlideAsset, SlideAsset>> - get assets; - $R call({Config? config, List? slides, List? assets}); - SuperDeckReferenceCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t); -} - -class _SuperDeckReferenceCopyWithImpl<$R, $Out> - extends ClassCopyWithBase<$R, SuperDeckReference, $Out> - implements SuperDeckReferenceCopyWith<$R, SuperDeckReference, $Out> { - _SuperDeckReferenceCopyWithImpl(super.value, super.then, super.then2); - - @override - late final ClassMapperBase $mapper = - SuperDeckReferenceMapper.ensureInitialized(); - @override - ConfigCopyWith<$R, Config, Config> get config => - $value.config.copyWith.$chain((v) => call(config: v)); - @override - ListCopyWith<$R, Slide, SlideCopyWith<$R, Slide, Slide>> get slides => - ListCopyWith($value.slides, (v, t) => v.copyWith.$chain(t), - (v) => call(slides: v)); - @override - ListCopyWith<$R, SlideAsset, SlideAssetCopyWith<$R, SlideAsset, SlideAsset>> - get assets => ListCopyWith($value.assets, (v, t) => v.copyWith.$chain(t), - (v) => call(assets: v)); - @override - $R call({Config? config, List? slides, List? assets}) => - $apply(FieldCopyWithData({ - if (config != null) #config: config, - if (slides != null) #slides: slides, - if (assets != null) #assets: assets - })); - @override - SuperDeckReference $make(CopyWithData data) => SuperDeckReference( - config: data.get(#config, or: $value.config), - slides: data.get(#slides, or: $value.slides), - assets: data.get(#assets, or: $value.assets)); - - @override - SuperDeckReferenceCopyWith<$R2, SuperDeckReference, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t) => - _SuperDeckReferenceCopyWithImpl($value, $cast, t); -} diff --git a/packages/superdeck/lib/models/slide_model.dart b/packages/superdeck/lib/models/slide_model.dart deleted file mode 100644 index 178068a9..00000000 --- a/packages/superdeck/lib/models/slide_model.dart +++ /dev/null @@ -1,332 +0,0 @@ -import 'package:dart_mappable/dart_mappable.dart'; - -import '../helpers/section_tag.dart'; -import '../schema/schema_model.dart'; -import '../schema/schema_validation.dart'; -import '../superdeck.dart'; -import 'config_model.dart'; - -part 'slide_model.mapper.dart'; - -@MappableClass(discriminatorKey: 'layout') -abstract class Slide extends BaseConfig with SlideMappable { - final String? title; - final String layout; - final String content; - - final String key; - - final ContentOptions? contentOptions; - - Slide({ - required this.layout, - required this.content, - required this.key, - required this.title, - required this.contentOptions, - required super.background, - required super.style, - required super.transition, - }); - - static Slide parse(Map map) { - final layout = map['layout'] ??= LayoutType.simple; - - try { - switch (layout) { - case LayoutType.simple: - case null: - SimpleSlide.schema.validateOrThrow(map); - return SimpleSlide.fromMap(map); - case LayoutType.image: - ImageSlide.schema.validateOrThrow(map); - return ImageSlide.fromMap(map); - case LayoutType.widget: - WidgetSlide.schema.validateOrThrow(map); - return WidgetSlide.fromMap(map); - case LayoutType.twoColumn: - TwoColumnSlide.schema.validateOrThrow(map); - return TwoColumnSlide.fromMap(map); - case LayoutType.twoColumnHeader: - TwoColumnHeaderSlide.schema.validateOrThrow(map); - return TwoColumnHeaderSlide.fromMap(map); - default: - return InvalidSlide.invalidTemplate(layout); - } - } on SchemaValidationException catch (e) { - return InvalidSlide.schemaError(e.result); - } on Exception catch (e) { - return InvalidSlide.exception(e); - } catch (e) { - return InvalidSlide.message('# Unknown Error \n $e'); - } - } - - static const fromMap = SlideMapper.fromMap; - - static const fromJson = SlideMapper.fromJson; - - static final schema = BaseConfig.schema.merge( - { - "layout": Schema.string.required(), - "data": Schema.string.required(), - "content": ContentOptions.schema, - "title": Schema.string, - "raw": Schema.string, - }, - ); -} - -@MappableClass(discriminatorValue: MappableClass.useAsDefault) -class SimpleSlide extends Slide with SimpleSlideMappable { - SimpleSlide({ - super.title, - super.background, - super.contentOptions, - super.style, - super.transition, - required super.key, - required super.content, - }) : super(layout: LayoutType.simple); - - static const fromMap = SimpleSlideMapper.fromMap; - - static const fromJson = SimpleSlideMapper.fromJson; - - static final schema = Slide.schema; -} - -@MappableClass() -abstract class SplitSlide extends Slide - with SplitSlideMappable { - final T options; - - SplitSlide({ - required this.options, - super.title, - super.background, - required super.contentOptions, - super.style, - super.transition, - required super.content, - required super.layout, - required super.key, - }); -} - -@MappableClass(discriminatorValue: LayoutType.image) -class ImageSlide extends SplitSlide with ImageSlideMappable { - ImageSlide({ - super.title, - super.style, - super.background, - required super.contentOptions, - super.transition, - required super.content, - required super.options, - required super.key, - }) : super(layout: LayoutType.image); - - static const fromMap = ImageSlideMapper.fromMap; - - static const fromJson = ImageSlideMapper.fromJson; - - static final schema = Slide.schema.merge( - { - 'options': ImageOptions.schema.required(), - }, - ); -} - -@MappableClass(discriminatorValue: LayoutType.widget) -class WidgetSlide extends SplitSlide with WidgetSlideMappable { - WidgetSlide({ - super.title, - required super.options, - super.style, - super.background, - required super.contentOptions, - super.transition, - required super.content, - required super.key, - }) : super(layout: LayoutType.widget); - - static const fromMap = WidgetSlideMapper.fromMap; - - static const fromJson = WidgetSlideMapper.fromJson; - - static final schema = Slide.schema.merge( - { - 'options': WidgetOptions.schema.required(), - }, - ); -} - -@MappableRecord() -typedef SectionData = ({String content, ContentOptions? options}); - -@MappableClass() -abstract class SectionsSlide extends Slide with SectionsSlideMappable { - @MappableField() - final Map sections; - - SectionsSlide({ - super.title, - super.background, - required super.contentOptions, - super.style, - super.transition, - required super.content, - this.sections = const {}, - required super.layout, - required super.key, - }); - - Map? _sectionCache; - - Map get _contentSections { - if (_sectionCache != null) return _sectionCache!; - - return _sectionCache = parseContentSections(content); - } - - SectionData getSection(String section, [String? sectionFallback]) { - var content = _contentSections[section]; - - content ??= _contentSections[sectionFallback]; - - final payload = ( - content: content ?? '', - options: sections[section] ?? const ContentOptions(), - ); - - return payload; - } -} - -@MappableClass(discriminatorValue: LayoutType.twoColumn) -class TwoColumnSlide extends SectionsSlide with TwoColumnSlideMappable { - TwoColumnSlide({ - super.title, - super.background, - required super.contentOptions, - super.style, - super.transition, - required super.content, - super.sections, - required super.key, - }) : super(layout: LayoutType.twoColumn); - - SectionData get left => getSection(SectionTag.left, SectionTag.first); - - SectionData get right => getSection(SectionTag.right); - - static const fromMap = TwoColumnSlideMapper.fromMap; - - static const fromJson = TwoColumnSlideMapper.fromJson; - - static final schema = Slide.schema.merge({ - 'sections': SchemaMap({ - 'left': ContentOptions.schema.optional(), - 'right': ContentOptions.schema.optional(), - }), - }); -} - -@MappableClass(discriminatorValue: LayoutType.twoColumnHeader) -class TwoColumnHeaderSlide extends SectionsSlide - with TwoColumnHeaderSlideMappable { - TwoColumnHeaderSlide({ - super.title, - super.background, - required super.contentOptions, - super.style, - super.transition, - required super.content, - super.sections, - required super.key, - }) : super(layout: LayoutType.twoColumnHeader); - - SectionData get header => getSection(SectionTag.header, SectionTag.first); - - SectionData get left => getSection(SectionTag.left); - - SectionData get right => getSection(SectionTag.right); - - static const fromMap = TwoColumnHeaderSlideMapper.fromMap; - - static const fromJson = TwoColumnHeaderSlideMapper.fromJson; - - static final schema = TwoColumnSlide.schema.merge( - { - 'sections': SchemaMap({ - 'header': ContentOptions.schema.optional(), - }), - }, - ); -} - -@MappableClass(discriminatorValue: LayoutType.invalid) -class InvalidSlide extends Slide with InvalidSlideMappable { - InvalidSlide({ - required super.contentOptions, - super.title, - super.background, - super.style, - super.transition, - required super.content, - required super.key, - }) : super(layout: LayoutType.invalid); - - InvalidSlide.message(String message) - : super( - layout: LayoutType.invalid, - title: null, - content: message, - background: null, - contentOptions: null, - style: null, - key: 'invalid_key', - transition: null, - ); - - InvalidSlide.invalidTemplate(String template) - : this.message('# Invalid template \n ## $template'); - - factory InvalidSlide.exception(Exception exception) { - return InvalidSlide.message('# Exception \n ## ${exception.toString()}'); - } - - factory InvalidSlide.schemaError( - SchemaValidationResult result, [ - String? content, - ]) { - final path = result.key; - final errors = result.errors; - final errorMessage = errors.map((error) => error.message).join('\n\n'); - - // dont forget the tab or spacing since they are nested - String keysNested = ''; - - if (path.isNotEmpty) { - keysNested = path.join('.'); - } - - content ??= '# Schema Error'; - - final message = ''' -$content -## $keysNested -$errorMessage -'''; - - return InvalidSlide.message(message); - } - - factory InvalidSlide.projectSchemaError(SchemaValidationResult error) { - return InvalidSlide.schemaError(error, '# Project configuration error'); - } - - static const fromMap = InvalidSlideMapper.fromMap; - static const fromJson = InvalidSlideMapper.fromJson; -} diff --git a/packages/superdeck/lib/models/slide_model.mapper.dart b/packages/superdeck/lib/models/slide_model.mapper.dart deleted file mode 100644 index ee3c1c6e..00000000 --- a/packages/superdeck/lib/models/slide_model.mapper.dart +++ /dev/null @@ -1,1694 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member -// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter - -part of 'slide_model.dart'; - -class SlideMapper extends SubClassMapperBase { - SlideMapper._(); - - static SlideMapper? _instance; - static SlideMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = SlideMapper._()); - BaseConfigMapper.ensureInitialized().addSubMapper(_instance!); - SimpleSlideMapper.ensureInitialized(); - SplitSlideMapper.ensureInitialized(); - SectionsSlideMapper.ensureInitialized(); - InvalidSlideMapper.ensureInitialized(); - ContentOptionsMapper.ensureInitialized(); - TransitionOptionsMapper.ensureInitialized(); - } - return _instance!; - } - - @override - final String id = 'Slide'; - - static String _$layout(Slide v) => v.layout; - static const Field _f$layout = Field('layout', _$layout); - static String _$content(Slide v) => v.content; - static const Field _f$content = Field('content', _$content); - static String _$key(Slide v) => v.key; - static const Field _f$key = Field('key', _$key); - static String? _$title(Slide v) => v.title; - static const Field _f$title = Field('title', _$title); - static ContentOptions? _$contentOptions(Slide v) => v.contentOptions; - static const Field _f$contentOptions = - Field('contentOptions', _$contentOptions, key: 'content_options'); - static String? _$background(Slide v) => v.background; - static const Field _f$background = - Field('background', _$background); - static String? _$style(Slide v) => v.style; - static const Field _f$style = Field('style', _$style); - static TransitionOptions? _$transition(Slide v) => v.transition; - static const Field _f$transition = - Field('transition', _$transition); - - @override - final MappableFields fields = const { - #layout: _f$layout, - #content: _f$content, - #key: _f$key, - #title: _f$title, - #contentOptions: _f$contentOptions, - #background: _f$background, - #style: _f$style, - #transition: _f$transition, - }; - @override - final bool ignoreNull = true; - - @override - final String discriminatorKey = 'type'; - @override - final dynamic discriminatorValue = 'Slide'; - @override - late final ClassMapperBase superMapper = BaseConfigMapper.ensureInitialized(); - - static Slide _instantiate(DecodingData data) { - throw MapperException.missingSubclass( - 'Slide', 'layout', '${data.value['layout']}'); - } - - @override - final Function instantiate = _instantiate; - - static Slide fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static Slide fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin SlideMappable { - String toJson(); - Map toMap(); - SlideCopyWith get copyWith; -} - -abstract class SlideCopyWith<$R, $In extends Slide, $Out> - implements BaseConfigCopyWith<$R, $In, $Out> { - ContentOptionsCopyWith<$R, ContentOptions, ContentOptions>? - get contentOptions; - @override - TransitionOptionsCopyWith<$R, TransitionOptions, TransitionOptions>? - get transition; - @override - $R call( - {String? content, - String? key, - String? title, - ContentOptions? contentOptions, - String? background, - String? style, - TransitionOptions? transition}); - SlideCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); -} - -class SimpleSlideMapper extends SubClassMapperBase { - SimpleSlideMapper._(); - - static SimpleSlideMapper? _instance; - static SimpleSlideMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = SimpleSlideMapper._()); - SlideMapper.ensureInitialized().addSubMapper(_instance!); - ContentOptionsMapper.ensureInitialized(); - TransitionOptionsMapper.ensureInitialized(); - } - return _instance!; - } - - @override - final String id = 'SimpleSlide'; - - static String? _$title(SimpleSlide v) => v.title; - static const Field _f$title = - Field('title', _$title, opt: true); - static String? _$background(SimpleSlide v) => v.background; - static const Field _f$background = - Field('background', _$background, opt: true); - static ContentOptions? _$contentOptions(SimpleSlide v) => v.contentOptions; - static const Field _f$contentOptions = Field( - 'contentOptions', _$contentOptions, - key: 'content_options', opt: true); - static String? _$style(SimpleSlide v) => v.style; - static const Field _f$style = - Field('style', _$style, opt: true); - static TransitionOptions? _$transition(SimpleSlide v) => v.transition; - static const Field _f$transition = - Field('transition', _$transition, opt: true); - static String _$key(SimpleSlide v) => v.key; - static const Field _f$key = Field('key', _$key); - static String _$content(SimpleSlide v) => v.content; - static const Field _f$content = - Field('content', _$content); - static String _$layout(SimpleSlide v) => v.layout; - static const Field _f$layout = - Field('layout', _$layout, mode: FieldMode.member); - - @override - final MappableFields fields = const { - #title: _f$title, - #background: _f$background, - #contentOptions: _f$contentOptions, - #style: _f$style, - #transition: _f$transition, - #key: _f$key, - #content: _f$content, - #layout: _f$layout, - }; - @override - final bool ignoreNull = true; - - @override - final String discriminatorKey = 'layout'; - @override - final dynamic discriminatorValue = MappableClass.useAsDefault; - @override - late final ClassMapperBase superMapper = SlideMapper.ensureInitialized(); - - static SimpleSlide _instantiate(DecodingData data) { - return SimpleSlide( - title: data.dec(_f$title), - background: data.dec(_f$background), - contentOptions: data.dec(_f$contentOptions), - style: data.dec(_f$style), - transition: data.dec(_f$transition), - key: data.dec(_f$key), - content: data.dec(_f$content)); - } - - @override - final Function instantiate = _instantiate; - - static SimpleSlide fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static SimpleSlide fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin SimpleSlideMappable { - String toJson() { - return SimpleSlideMapper.ensureInitialized() - .encodeJson(this as SimpleSlide); - } - - Map toMap() { - return SimpleSlideMapper.ensureInitialized() - .encodeMap(this as SimpleSlide); - } - - SimpleSlideCopyWith get copyWith => - _SimpleSlideCopyWithImpl(this as SimpleSlide, $identity, $identity); - @override - String toString() { - return SimpleSlideMapper.ensureInitialized() - .stringifyValue(this as SimpleSlide); - } - - @override - bool operator ==(Object other) { - return SimpleSlideMapper.ensureInitialized() - .equalsValue(this as SimpleSlide, other); - } - - @override - int get hashCode { - return SimpleSlideMapper.ensureInitialized().hashValue(this as SimpleSlide); - } -} - -extension SimpleSlideValueCopy<$R, $Out> - on ObjectCopyWith<$R, SimpleSlide, $Out> { - SimpleSlideCopyWith<$R, SimpleSlide, $Out> get $asSimpleSlide => - $base.as((v, t, t2) => _SimpleSlideCopyWithImpl(v, t, t2)); -} - -abstract class SimpleSlideCopyWith<$R, $In extends SimpleSlide, $Out> - implements SlideCopyWith<$R, $In, $Out> { - @override - ContentOptionsCopyWith<$R, ContentOptions, ContentOptions>? - get contentOptions; - @override - TransitionOptionsCopyWith<$R, TransitionOptions, TransitionOptions>? - get transition; - @override - $R call( - {String? title, - String? background, - ContentOptions? contentOptions, - String? style, - TransitionOptions? transition, - String? key, - String? content}); - SimpleSlideCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); -} - -class _SimpleSlideCopyWithImpl<$R, $Out> - extends ClassCopyWithBase<$R, SimpleSlide, $Out> - implements SimpleSlideCopyWith<$R, SimpleSlide, $Out> { - _SimpleSlideCopyWithImpl(super.value, super.then, super.then2); - - @override - late final ClassMapperBase $mapper = - SimpleSlideMapper.ensureInitialized(); - @override - ContentOptionsCopyWith<$R, ContentOptions, ContentOptions>? - get contentOptions => $value.contentOptions?.copyWith - .$chain((v) => call(contentOptions: v)); - @override - TransitionOptionsCopyWith<$R, TransitionOptions, TransitionOptions>? - get transition => - $value.transition?.copyWith.$chain((v) => call(transition: v)); - @override - $R call( - {Object? title = $none, - Object? background = $none, - Object? contentOptions = $none, - Object? style = $none, - Object? transition = $none, - String? key, - String? content}) => - $apply(FieldCopyWithData({ - if (title != $none) #title: title, - if (background != $none) #background: background, - if (contentOptions != $none) #contentOptions: contentOptions, - if (style != $none) #style: style, - if (transition != $none) #transition: transition, - if (key != null) #key: key, - if (content != null) #content: content - })); - @override - SimpleSlide $make(CopyWithData data) => SimpleSlide( - title: data.get(#title, or: $value.title), - background: data.get(#background, or: $value.background), - contentOptions: data.get(#contentOptions, or: $value.contentOptions), - style: data.get(#style, or: $value.style), - transition: data.get(#transition, or: $value.transition), - key: data.get(#key, or: $value.key), - content: data.get(#content, or: $value.content)); - - @override - SimpleSlideCopyWith<$R2, SimpleSlide, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t) => - _SimpleSlideCopyWithImpl($value, $cast, t); -} - -class SplitSlideMapper extends SubClassMapperBase { - SplitSlideMapper._(); - - static SplitSlideMapper? _instance; - static SplitSlideMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = SplitSlideMapper._()); - SlideMapper.ensureInitialized().addSubMapper(_instance!); - ImageSlideMapper.ensureInitialized(); - WidgetSlideMapper.ensureInitialized(); - ContentOptionsMapper.ensureInitialized(); - TransitionOptionsMapper.ensureInitialized(); - SplitOptionsMapper.ensureInitialized(); - } - return _instance!; - } - - @override - final String id = 'SplitSlide'; - @override - Function get typeFactory => (f) => f>(); - - static SplitOptions _$options(SplitSlide v) => v.options; - static dynamic _arg$options(f) => f(); - static const Field _f$options = - Field('options', _$options, arg: _arg$options); - static String? _$title(SplitSlide v) => v.title; - static const Field _f$title = - Field('title', _$title, opt: true); - static String? _$background(SplitSlide v) => v.background; - static const Field _f$background = - Field('background', _$background, opt: true); - static ContentOptions? _$contentOptions(SplitSlide v) => v.contentOptions; - static const Field _f$contentOptions = - Field('contentOptions', _$contentOptions, key: 'content_options'); - static String? _$style(SplitSlide v) => v.style; - static const Field _f$style = - Field('style', _$style, opt: true); - static TransitionOptions? _$transition(SplitSlide v) => v.transition; - static const Field _f$transition = - Field('transition', _$transition, opt: true); - static String _$content(SplitSlide v) => v.content; - static const Field _f$content = - Field('content', _$content); - static String _$layout(SplitSlide v) => v.layout; - static const Field _f$layout = Field('layout', _$layout); - static String _$key(SplitSlide v) => v.key; - static const Field _f$key = Field('key', _$key); - - @override - final MappableFields fields = const { - #options: _f$options, - #title: _f$title, - #background: _f$background, - #contentOptions: _f$contentOptions, - #style: _f$style, - #transition: _f$transition, - #content: _f$content, - #layout: _f$layout, - #key: _f$key, - }; - @override - final bool ignoreNull = true; - - @override - final String discriminatorKey = 'layout'; - @override - final dynamic discriminatorValue = 'SplitSlide'; - @override - late final ClassMapperBase superMapper = SlideMapper.ensureInitialized(); - - @override - DecodingContext inherit(DecodingContext context) { - return context.inherit(args: () => [SplitOptions]); - } - - static SplitSlide _instantiate(DecodingData data) { - throw MapperException.missingSubclass( - 'SplitSlide', 'layout', '${data.value['layout']}'); - } - - @override - final Function instantiate = _instantiate; - - static SplitSlide fromMap( - Map map) { - return ensureInitialized().decodeMap>(map); - } - - static SplitSlide fromJson(String json) { - return ensureInitialized().decodeJson>(json); - } -} - -mixin SplitSlideMappable { - String toJson(); - Map toMap(); - SplitSlideCopyWith, SplitSlide, SplitSlide, T> - get copyWith; -} - -abstract class SplitSlideCopyWith<$R, $In extends SplitSlide, $Out, - T extends SplitOptions> implements SlideCopyWith<$R, $In, $Out> { - SplitOptionsCopyWith<$R, SplitOptions, T> get options; - @override - ContentOptionsCopyWith<$R, ContentOptions, ContentOptions>? - get contentOptions; - @override - TransitionOptionsCopyWith<$R, TransitionOptions, TransitionOptions>? - get transition; - @override - $R call( - {T? options, - String? title, - String? background, - ContentOptions? contentOptions, - String? style, - TransitionOptions? transition, - String? content, - String? key}); - SplitSlideCopyWith<$R2, $In, $Out2, T> $chain<$R2, $Out2>(Then<$Out2, $R2> t); -} - -class ImageSlideMapper extends SubClassMapperBase { - ImageSlideMapper._(); - - static ImageSlideMapper? _instance; - static ImageSlideMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = ImageSlideMapper._()); - SplitSlideMapper.ensureInitialized().addSubMapper(_instance!); - ContentOptionsMapper.ensureInitialized(); - TransitionOptionsMapper.ensureInitialized(); - ImageOptionsMapper.ensureInitialized(); - } - return _instance!; - } - - @override - final String id = 'ImageSlide'; - - static String? _$title(ImageSlide v) => v.title; - static const Field _f$title = - Field('title', _$title, opt: true); - static String? _$style(ImageSlide v) => v.style; - static const Field _f$style = - Field('style', _$style, opt: true); - static String? _$background(ImageSlide v) => v.background; - static const Field _f$background = - Field('background', _$background, opt: true); - static ContentOptions? _$contentOptions(ImageSlide v) => v.contentOptions; - static const Field _f$contentOptions = - Field('contentOptions', _$contentOptions, key: 'content_options'); - static TransitionOptions? _$transition(ImageSlide v) => v.transition; - static const Field _f$transition = - Field('transition', _$transition, opt: true); - static String _$content(ImageSlide v) => v.content; - static const Field _f$content = - Field('content', _$content); - static ImageOptions _$options(ImageSlide v) => v.options; - static const Field _f$options = - Field('options', _$options); - static String _$key(ImageSlide v) => v.key; - static const Field _f$key = Field('key', _$key); - static String _$layout(ImageSlide v) => v.layout; - static const Field _f$layout = - Field('layout', _$layout, mode: FieldMode.member); - - @override - final MappableFields fields = const { - #title: _f$title, - #style: _f$style, - #background: _f$background, - #contentOptions: _f$contentOptions, - #transition: _f$transition, - #content: _f$content, - #options: _f$options, - #key: _f$key, - #layout: _f$layout, - }; - @override - final bool ignoreNull = true; - - @override - final String discriminatorKey = 'layout'; - @override - final dynamic discriminatorValue = LayoutType.image; - @override - late final ClassMapperBase superMapper = SplitSlideMapper.ensureInitialized(); - - @override - DecodingContext inherit(DecodingContext context) { - return context.inherit(args: () => []); - } - - static ImageSlide _instantiate(DecodingData data) { - return ImageSlide( - title: data.dec(_f$title), - style: data.dec(_f$style), - background: data.dec(_f$background), - contentOptions: data.dec(_f$contentOptions), - transition: data.dec(_f$transition), - content: data.dec(_f$content), - options: data.dec(_f$options), - key: data.dec(_f$key)); - } - - @override - final Function instantiate = _instantiate; - - static ImageSlide fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static ImageSlide fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin ImageSlideMappable { - String toJson() { - return ImageSlideMapper.ensureInitialized() - .encodeJson(this as ImageSlide); - } - - Map toMap() { - return ImageSlideMapper.ensureInitialized() - .encodeMap(this as ImageSlide); - } - - ImageSlideCopyWith get copyWith => - _ImageSlideCopyWithImpl(this as ImageSlide, $identity, $identity); - @override - String toString() { - return ImageSlideMapper.ensureInitialized() - .stringifyValue(this as ImageSlide); - } - - @override - bool operator ==(Object other) { - return ImageSlideMapper.ensureInitialized() - .equalsValue(this as ImageSlide, other); - } - - @override - int get hashCode { - return ImageSlideMapper.ensureInitialized().hashValue(this as ImageSlide); - } -} - -extension ImageSlideValueCopy<$R, $Out> - on ObjectCopyWith<$R, ImageSlide, $Out> { - ImageSlideCopyWith<$R, ImageSlide, $Out> get $asImageSlide => - $base.as((v, t, t2) => _ImageSlideCopyWithImpl(v, t, t2)); -} - -abstract class ImageSlideCopyWith<$R, $In extends ImageSlide, $Out> - implements SplitSlideCopyWith<$R, $In, $Out, ImageOptions> { - @override - ContentOptionsCopyWith<$R, ContentOptions, ContentOptions>? - get contentOptions; - @override - TransitionOptionsCopyWith<$R, TransitionOptions, TransitionOptions>? - get transition; - @override - ImageOptionsCopyWith<$R, ImageOptions, ImageOptions> get options; - @override - $R call( - {String? title, - String? style, - String? background, - ContentOptions? contentOptions, - TransitionOptions? transition, - String? content, - ImageOptions? options, - String? key}); - ImageSlideCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); -} - -class _ImageSlideCopyWithImpl<$R, $Out> - extends ClassCopyWithBase<$R, ImageSlide, $Out> - implements ImageSlideCopyWith<$R, ImageSlide, $Out> { - _ImageSlideCopyWithImpl(super.value, super.then, super.then2); - - @override - late final ClassMapperBase $mapper = - ImageSlideMapper.ensureInitialized(); - @override - ContentOptionsCopyWith<$R, ContentOptions, ContentOptions>? - get contentOptions => $value.contentOptions?.copyWith - .$chain((v) => call(contentOptions: v)); - @override - TransitionOptionsCopyWith<$R, TransitionOptions, TransitionOptions>? - get transition => - $value.transition?.copyWith.$chain((v) => call(transition: v)); - @override - ImageOptionsCopyWith<$R, ImageOptions, ImageOptions> get options => - ($value.options as ImageOptions).copyWith.$chain((v) => call(options: v)); - @override - $R call( - {Object? title = $none, - Object? style = $none, - Object? background = $none, - Object? contentOptions = $none, - Object? transition = $none, - String? content, - ImageOptions? options, - String? key}) => - $apply(FieldCopyWithData({ - if (title != $none) #title: title, - if (style != $none) #style: style, - if (background != $none) #background: background, - if (contentOptions != $none) #contentOptions: contentOptions, - if (transition != $none) #transition: transition, - if (content != null) #content: content, - if (options != null) #options: options, - if (key != null) #key: key - })); - @override - ImageSlide $make(CopyWithData data) => ImageSlide( - title: data.get(#title, or: $value.title), - style: data.get(#style, or: $value.style), - background: data.get(#background, or: $value.background), - contentOptions: data.get(#contentOptions, or: $value.contentOptions), - transition: data.get(#transition, or: $value.transition), - content: data.get(#content, or: $value.content), - options: data.get(#options, or: $value.options), - key: data.get(#key, or: $value.key)); - - @override - ImageSlideCopyWith<$R2, ImageSlide, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t) => - _ImageSlideCopyWithImpl($value, $cast, t); -} - -class WidgetSlideMapper extends SubClassMapperBase { - WidgetSlideMapper._(); - - static WidgetSlideMapper? _instance; - static WidgetSlideMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = WidgetSlideMapper._()); - SplitSlideMapper.ensureInitialized().addSubMapper(_instance!); - WidgetOptionsMapper.ensureInitialized(); - ContentOptionsMapper.ensureInitialized(); - TransitionOptionsMapper.ensureInitialized(); - } - return _instance!; - } - - @override - final String id = 'WidgetSlide'; - - static String? _$title(WidgetSlide v) => v.title; - static const Field _f$title = - Field('title', _$title, opt: true); - static WidgetOptions _$options(WidgetSlide v) => v.options; - static const Field _f$options = - Field('options', _$options); - static String? _$style(WidgetSlide v) => v.style; - static const Field _f$style = - Field('style', _$style, opt: true); - static String? _$background(WidgetSlide v) => v.background; - static const Field _f$background = - Field('background', _$background, opt: true); - static ContentOptions? _$contentOptions(WidgetSlide v) => v.contentOptions; - static const Field _f$contentOptions = - Field('contentOptions', _$contentOptions, key: 'content_options'); - static TransitionOptions? _$transition(WidgetSlide v) => v.transition; - static const Field _f$transition = - Field('transition', _$transition, opt: true); - static String _$content(WidgetSlide v) => v.content; - static const Field _f$content = - Field('content', _$content); - static String _$key(WidgetSlide v) => v.key; - static const Field _f$key = Field('key', _$key); - static String _$layout(WidgetSlide v) => v.layout; - static const Field _f$layout = - Field('layout', _$layout, mode: FieldMode.member); - - @override - final MappableFields fields = const { - #title: _f$title, - #options: _f$options, - #style: _f$style, - #background: _f$background, - #contentOptions: _f$contentOptions, - #transition: _f$transition, - #content: _f$content, - #key: _f$key, - #layout: _f$layout, - }; - @override - final bool ignoreNull = true; - - @override - final String discriminatorKey = 'layout'; - @override - final dynamic discriminatorValue = LayoutType.widget; - @override - late final ClassMapperBase superMapper = SplitSlideMapper.ensureInitialized(); - - @override - DecodingContext inherit(DecodingContext context) { - return context.inherit(args: () => []); - } - - static WidgetSlide _instantiate(DecodingData data) { - return WidgetSlide( - title: data.dec(_f$title), - options: data.dec(_f$options), - style: data.dec(_f$style), - background: data.dec(_f$background), - contentOptions: data.dec(_f$contentOptions), - transition: data.dec(_f$transition), - content: data.dec(_f$content), - key: data.dec(_f$key)); - } - - @override - final Function instantiate = _instantiate; - - static WidgetSlide fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static WidgetSlide fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin WidgetSlideMappable { - String toJson() { - return WidgetSlideMapper.ensureInitialized() - .encodeJson(this as WidgetSlide); - } - - Map toMap() { - return WidgetSlideMapper.ensureInitialized() - .encodeMap(this as WidgetSlide); - } - - WidgetSlideCopyWith get copyWith => - _WidgetSlideCopyWithImpl(this as WidgetSlide, $identity, $identity); - @override - String toString() { - return WidgetSlideMapper.ensureInitialized() - .stringifyValue(this as WidgetSlide); - } - - @override - bool operator ==(Object other) { - return WidgetSlideMapper.ensureInitialized() - .equalsValue(this as WidgetSlide, other); - } - - @override - int get hashCode { - return WidgetSlideMapper.ensureInitialized().hashValue(this as WidgetSlide); - } -} - -extension WidgetSlideValueCopy<$R, $Out> - on ObjectCopyWith<$R, WidgetSlide, $Out> { - WidgetSlideCopyWith<$R, WidgetSlide, $Out> get $asWidgetSlide => - $base.as((v, t, t2) => _WidgetSlideCopyWithImpl(v, t, t2)); -} - -abstract class WidgetSlideCopyWith<$R, $In extends WidgetSlide, $Out> - implements SplitSlideCopyWith<$R, $In, $Out, WidgetOptions> { - @override - WidgetOptionsCopyWith<$R, WidgetOptions, WidgetOptions> get options; - @override - ContentOptionsCopyWith<$R, ContentOptions, ContentOptions>? - get contentOptions; - @override - TransitionOptionsCopyWith<$R, TransitionOptions, TransitionOptions>? - get transition; - @override - $R call( - {String? title, - WidgetOptions? options, - String? style, - String? background, - ContentOptions? contentOptions, - TransitionOptions? transition, - String? content, - String? key}); - WidgetSlideCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); -} - -class _WidgetSlideCopyWithImpl<$R, $Out> - extends ClassCopyWithBase<$R, WidgetSlide, $Out> - implements WidgetSlideCopyWith<$R, WidgetSlide, $Out> { - _WidgetSlideCopyWithImpl(super.value, super.then, super.then2); - - @override - late final ClassMapperBase $mapper = - WidgetSlideMapper.ensureInitialized(); - @override - WidgetOptionsCopyWith<$R, WidgetOptions, WidgetOptions> get options => - ($value.options as WidgetOptions) - .copyWith - .$chain((v) => call(options: v)); - @override - ContentOptionsCopyWith<$R, ContentOptions, ContentOptions>? - get contentOptions => $value.contentOptions?.copyWith - .$chain((v) => call(contentOptions: v)); - @override - TransitionOptionsCopyWith<$R, TransitionOptions, TransitionOptions>? - get transition => - $value.transition?.copyWith.$chain((v) => call(transition: v)); - @override - $R call( - {Object? title = $none, - WidgetOptions? options, - Object? style = $none, - Object? background = $none, - Object? contentOptions = $none, - Object? transition = $none, - String? content, - String? key}) => - $apply(FieldCopyWithData({ - if (title != $none) #title: title, - if (options != null) #options: options, - if (style != $none) #style: style, - if (background != $none) #background: background, - if (contentOptions != $none) #contentOptions: contentOptions, - if (transition != $none) #transition: transition, - if (content != null) #content: content, - if (key != null) #key: key - })); - @override - WidgetSlide $make(CopyWithData data) => WidgetSlide( - title: data.get(#title, or: $value.title), - options: data.get(#options, or: $value.options), - style: data.get(#style, or: $value.style), - background: data.get(#background, or: $value.background), - contentOptions: data.get(#contentOptions, or: $value.contentOptions), - transition: data.get(#transition, or: $value.transition), - content: data.get(#content, or: $value.content), - key: data.get(#key, or: $value.key)); - - @override - WidgetSlideCopyWith<$R2, WidgetSlide, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t) => - _WidgetSlideCopyWithImpl($value, $cast, t); -} - -class SectionsSlideMapper extends SubClassMapperBase { - SectionsSlideMapper._(); - - static SectionsSlideMapper? _instance; - static SectionsSlideMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = SectionsSlideMapper._()); - SlideMapper.ensureInitialized().addSubMapper(_instance!); - TwoColumnSlideMapper.ensureInitialized(); - TwoColumnHeaderSlideMapper.ensureInitialized(); - ContentOptionsMapper.ensureInitialized(); - TransitionOptionsMapper.ensureInitialized(); - } - return _instance!; - } - - @override - final String id = 'SectionsSlide'; - - static String? _$title(SectionsSlide v) => v.title; - static const Field _f$title = - Field('title', _$title, opt: true); - static String? _$background(SectionsSlide v) => v.background; - static const Field _f$background = - Field('background', _$background, opt: true); - static ContentOptions? _$contentOptions(SectionsSlide v) => v.contentOptions; - static const Field _f$contentOptions = - Field('contentOptions', _$contentOptions, key: 'content_options'); - static String? _$style(SectionsSlide v) => v.style; - static const Field _f$style = - Field('style', _$style, opt: true); - static TransitionOptions? _$transition(SectionsSlide v) => v.transition; - static const Field _f$transition = - Field('transition', _$transition, opt: true); - static String _$content(SectionsSlide v) => v.content; - static const Field _f$content = - Field('content', _$content); - static Map _$sections(SectionsSlide v) => v.sections; - static const Field> _f$sections = - Field('sections', _$sections, opt: true, def: const {}); - static String _$layout(SectionsSlide v) => v.layout; - static const Field _f$layout = - Field('layout', _$layout); - static String _$key(SectionsSlide v) => v.key; - static const Field _f$key = Field('key', _$key); - - @override - final MappableFields fields = const { - #title: _f$title, - #background: _f$background, - #contentOptions: _f$contentOptions, - #style: _f$style, - #transition: _f$transition, - #content: _f$content, - #sections: _f$sections, - #layout: _f$layout, - #key: _f$key, - }; - @override - final bool ignoreNull = true; - - @override - final String discriminatorKey = 'layout'; - @override - final dynamic discriminatorValue = 'SectionsSlide'; - @override - late final ClassMapperBase superMapper = SlideMapper.ensureInitialized(); - - static SectionsSlide _instantiate(DecodingData data) { - throw MapperException.missingSubclass( - 'SectionsSlide', 'layout', '${data.value['layout']}'); - } - - @override - final Function instantiate = _instantiate; - - static SectionsSlide fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static SectionsSlide fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin SectionsSlideMappable { - String toJson(); - Map toMap(); - SectionsSlideCopyWith - get copyWith; -} - -abstract class SectionsSlideCopyWith<$R, $In extends SectionsSlide, $Out> - implements SlideCopyWith<$R, $In, $Out> { - @override - ContentOptionsCopyWith<$R, ContentOptions, ContentOptions>? - get contentOptions; - @override - TransitionOptionsCopyWith<$R, TransitionOptions, TransitionOptions>? - get transition; - MapCopyWith<$R, String, ContentOptions?, - ContentOptionsCopyWith<$R, ContentOptions, ContentOptions>?> get sections; - @override - $R call( - {String? title, - String? background, - ContentOptions? contentOptions, - String? style, - TransitionOptions? transition, - String? content, - Map? sections, - String? key}); - SectionsSlideCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); -} - -class TwoColumnSlideMapper extends SubClassMapperBase { - TwoColumnSlideMapper._(); - - static TwoColumnSlideMapper? _instance; - static TwoColumnSlideMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = TwoColumnSlideMapper._()); - SectionsSlideMapper.ensureInitialized().addSubMapper(_instance!); - ContentOptionsMapper.ensureInitialized(); - TransitionOptionsMapper.ensureInitialized(); - } - return _instance!; - } - - @override - final String id = 'TwoColumnSlide'; - - static String? _$title(TwoColumnSlide v) => v.title; - static const Field _f$title = - Field('title', _$title, opt: true); - static String? _$background(TwoColumnSlide v) => v.background; - static const Field _f$background = - Field('background', _$background, opt: true); - static ContentOptions? _$contentOptions(TwoColumnSlide v) => v.contentOptions; - static const Field _f$contentOptions = - Field('contentOptions', _$contentOptions, key: 'content_options'); - static String? _$style(TwoColumnSlide v) => v.style; - static const Field _f$style = - Field('style', _$style, opt: true); - static TransitionOptions? _$transition(TwoColumnSlide v) => v.transition; - static const Field _f$transition = - Field('transition', _$transition, opt: true); - static String _$content(TwoColumnSlide v) => v.content; - static const Field _f$content = - Field('content', _$content); - static Map _$sections(TwoColumnSlide v) => - v.sections; - static const Field> _f$sections = - Field('sections', _$sections, opt: true, def: const {}); - static String _$key(TwoColumnSlide v) => v.key; - static const Field _f$key = Field('key', _$key); - static String _$layout(TwoColumnSlide v) => v.layout; - static const Field _f$layout = - Field('layout', _$layout, mode: FieldMode.member); - - @override - final MappableFields fields = const { - #title: _f$title, - #background: _f$background, - #contentOptions: _f$contentOptions, - #style: _f$style, - #transition: _f$transition, - #content: _f$content, - #sections: _f$sections, - #key: _f$key, - #layout: _f$layout, - }; - @override - final bool ignoreNull = true; - - @override - final String discriminatorKey = 'layout'; - @override - final dynamic discriminatorValue = LayoutType.twoColumn; - @override - late final ClassMapperBase superMapper = - SectionsSlideMapper.ensureInitialized(); - - static TwoColumnSlide _instantiate(DecodingData data) { - return TwoColumnSlide( - title: data.dec(_f$title), - background: data.dec(_f$background), - contentOptions: data.dec(_f$contentOptions), - style: data.dec(_f$style), - transition: data.dec(_f$transition), - content: data.dec(_f$content), - sections: data.dec(_f$sections), - key: data.dec(_f$key)); - } - - @override - final Function instantiate = _instantiate; - - static TwoColumnSlide fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static TwoColumnSlide fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin TwoColumnSlideMappable { - String toJson() { - return TwoColumnSlideMapper.ensureInitialized() - .encodeJson(this as TwoColumnSlide); - } - - Map toMap() { - return TwoColumnSlideMapper.ensureInitialized() - .encodeMap(this as TwoColumnSlide); - } - - TwoColumnSlideCopyWith - get copyWith => _TwoColumnSlideCopyWithImpl( - this as TwoColumnSlide, $identity, $identity); - @override - String toString() { - return TwoColumnSlideMapper.ensureInitialized() - .stringifyValue(this as TwoColumnSlide); - } - - @override - bool operator ==(Object other) { - return TwoColumnSlideMapper.ensureInitialized() - .equalsValue(this as TwoColumnSlide, other); - } - - @override - int get hashCode { - return TwoColumnSlideMapper.ensureInitialized() - .hashValue(this as TwoColumnSlide); - } -} - -extension TwoColumnSlideValueCopy<$R, $Out> - on ObjectCopyWith<$R, TwoColumnSlide, $Out> { - TwoColumnSlideCopyWith<$R, TwoColumnSlide, $Out> get $asTwoColumnSlide => - $base.as((v, t, t2) => _TwoColumnSlideCopyWithImpl(v, t, t2)); -} - -abstract class TwoColumnSlideCopyWith<$R, $In extends TwoColumnSlide, $Out> - implements SectionsSlideCopyWith<$R, $In, $Out> { - @override - ContentOptionsCopyWith<$R, ContentOptions, ContentOptions>? - get contentOptions; - @override - TransitionOptionsCopyWith<$R, TransitionOptions, TransitionOptions>? - get transition; - @override - MapCopyWith<$R, String, ContentOptions?, - ContentOptionsCopyWith<$R, ContentOptions, ContentOptions>?> get sections; - @override - $R call( - {String? title, - String? background, - ContentOptions? contentOptions, - String? style, - TransitionOptions? transition, - String? content, - Map? sections, - String? key}); - TwoColumnSlideCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t); -} - -class _TwoColumnSlideCopyWithImpl<$R, $Out> - extends ClassCopyWithBase<$R, TwoColumnSlide, $Out> - implements TwoColumnSlideCopyWith<$R, TwoColumnSlide, $Out> { - _TwoColumnSlideCopyWithImpl(super.value, super.then, super.then2); - - @override - late final ClassMapperBase $mapper = - TwoColumnSlideMapper.ensureInitialized(); - @override - ContentOptionsCopyWith<$R, ContentOptions, ContentOptions>? - get contentOptions => $value.contentOptions?.copyWith - .$chain((v) => call(contentOptions: v)); - @override - TransitionOptionsCopyWith<$R, TransitionOptions, TransitionOptions>? - get transition => - $value.transition?.copyWith.$chain((v) => call(transition: v)); - @override - MapCopyWith<$R, String, ContentOptions?, - ContentOptionsCopyWith<$R, ContentOptions, ContentOptions>?> - get sections => MapCopyWith($value.sections, - (v, t) => v?.copyWith.$chain(t), (v) => call(sections: v)); - @override - $R call( - {Object? title = $none, - Object? background = $none, - Object? contentOptions = $none, - Object? style = $none, - Object? transition = $none, - String? content, - Map? sections, - String? key}) => - $apply(FieldCopyWithData({ - if (title != $none) #title: title, - if (background != $none) #background: background, - if (contentOptions != $none) #contentOptions: contentOptions, - if (style != $none) #style: style, - if (transition != $none) #transition: transition, - if (content != null) #content: content, - if (sections != null) #sections: sections, - if (key != null) #key: key - })); - @override - TwoColumnSlide $make(CopyWithData data) => TwoColumnSlide( - title: data.get(#title, or: $value.title), - background: data.get(#background, or: $value.background), - contentOptions: data.get(#contentOptions, or: $value.contentOptions), - style: data.get(#style, or: $value.style), - transition: data.get(#transition, or: $value.transition), - content: data.get(#content, or: $value.content), - sections: data.get(#sections, or: $value.sections), - key: data.get(#key, or: $value.key)); - - @override - TwoColumnSlideCopyWith<$R2, TwoColumnSlide, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t) => - _TwoColumnSlideCopyWithImpl($value, $cast, t); -} - -class TwoColumnHeaderSlideMapper - extends SubClassMapperBase { - TwoColumnHeaderSlideMapper._(); - - static TwoColumnHeaderSlideMapper? _instance; - static TwoColumnHeaderSlideMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = TwoColumnHeaderSlideMapper._()); - SectionsSlideMapper.ensureInitialized().addSubMapper(_instance!); - ContentOptionsMapper.ensureInitialized(); - TransitionOptionsMapper.ensureInitialized(); - } - return _instance!; - } - - @override - final String id = 'TwoColumnHeaderSlide'; - - static String? _$title(TwoColumnHeaderSlide v) => v.title; - static const Field _f$title = - Field('title', _$title, opt: true); - static String? _$background(TwoColumnHeaderSlide v) => v.background; - static const Field _f$background = - Field('background', _$background, opt: true); - static ContentOptions? _$contentOptions(TwoColumnHeaderSlide v) => - v.contentOptions; - static const Field _f$contentOptions = - Field('contentOptions', _$contentOptions, key: 'content_options'); - static String? _$style(TwoColumnHeaderSlide v) => v.style; - static const Field _f$style = - Field('style', _$style, opt: true); - static TransitionOptions? _$transition(TwoColumnHeaderSlide v) => - v.transition; - static const Field _f$transition = - Field('transition', _$transition, opt: true); - static String _$content(TwoColumnHeaderSlide v) => v.content; - static const Field _f$content = - Field('content', _$content); - static Map _$sections(TwoColumnHeaderSlide v) => - v.sections; - static const Field> - _f$sections = Field('sections', _$sections, opt: true, def: const {}); - static String _$key(TwoColumnHeaderSlide v) => v.key; - static const Field _f$key = Field('key', _$key); - static String _$layout(TwoColumnHeaderSlide v) => v.layout; - static const Field _f$layout = - Field('layout', _$layout, mode: FieldMode.member); - - @override - final MappableFields fields = const { - #title: _f$title, - #background: _f$background, - #contentOptions: _f$contentOptions, - #style: _f$style, - #transition: _f$transition, - #content: _f$content, - #sections: _f$sections, - #key: _f$key, - #layout: _f$layout, - }; - @override - final bool ignoreNull = true; - - @override - final String discriminatorKey = 'layout'; - @override - final dynamic discriminatorValue = LayoutType.twoColumnHeader; - @override - late final ClassMapperBase superMapper = - SectionsSlideMapper.ensureInitialized(); - - static TwoColumnHeaderSlide _instantiate(DecodingData data) { - return TwoColumnHeaderSlide( - title: data.dec(_f$title), - background: data.dec(_f$background), - contentOptions: data.dec(_f$contentOptions), - style: data.dec(_f$style), - transition: data.dec(_f$transition), - content: data.dec(_f$content), - sections: data.dec(_f$sections), - key: data.dec(_f$key)); - } - - @override - final Function instantiate = _instantiate; - - static TwoColumnHeaderSlide fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static TwoColumnHeaderSlide fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin TwoColumnHeaderSlideMappable { - String toJson() { - return TwoColumnHeaderSlideMapper.ensureInitialized() - .encodeJson(this as TwoColumnHeaderSlide); - } - - Map toMap() { - return TwoColumnHeaderSlideMapper.ensureInitialized() - .encodeMap(this as TwoColumnHeaderSlide); - } - - TwoColumnHeaderSlideCopyWith - get copyWith => _TwoColumnHeaderSlideCopyWithImpl( - this as TwoColumnHeaderSlide, $identity, $identity); - @override - String toString() { - return TwoColumnHeaderSlideMapper.ensureInitialized() - .stringifyValue(this as TwoColumnHeaderSlide); - } - - @override - bool operator ==(Object other) { - return TwoColumnHeaderSlideMapper.ensureInitialized() - .equalsValue(this as TwoColumnHeaderSlide, other); - } - - @override - int get hashCode { - return TwoColumnHeaderSlideMapper.ensureInitialized() - .hashValue(this as TwoColumnHeaderSlide); - } -} - -extension TwoColumnHeaderSlideValueCopy<$R, $Out> - on ObjectCopyWith<$R, TwoColumnHeaderSlide, $Out> { - TwoColumnHeaderSlideCopyWith<$R, TwoColumnHeaderSlide, $Out> - get $asTwoColumnHeaderSlide => - $base.as((v, t, t2) => _TwoColumnHeaderSlideCopyWithImpl(v, t, t2)); -} - -abstract class TwoColumnHeaderSlideCopyWith< - $R, - $In extends TwoColumnHeaderSlide, - $Out> implements SectionsSlideCopyWith<$R, $In, $Out> { - @override - ContentOptionsCopyWith<$R, ContentOptions, ContentOptions>? - get contentOptions; - @override - TransitionOptionsCopyWith<$R, TransitionOptions, TransitionOptions>? - get transition; - @override - MapCopyWith<$R, String, ContentOptions?, - ContentOptionsCopyWith<$R, ContentOptions, ContentOptions>?> get sections; - @override - $R call( - {String? title, - String? background, - ContentOptions? contentOptions, - String? style, - TransitionOptions? transition, - String? content, - Map? sections, - String? key}); - TwoColumnHeaderSlideCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t); -} - -class _TwoColumnHeaderSlideCopyWithImpl<$R, $Out> - extends ClassCopyWithBase<$R, TwoColumnHeaderSlide, $Out> - implements TwoColumnHeaderSlideCopyWith<$R, TwoColumnHeaderSlide, $Out> { - _TwoColumnHeaderSlideCopyWithImpl(super.value, super.then, super.then2); - - @override - late final ClassMapperBase $mapper = - TwoColumnHeaderSlideMapper.ensureInitialized(); - @override - ContentOptionsCopyWith<$R, ContentOptions, ContentOptions>? - get contentOptions => $value.contentOptions?.copyWith - .$chain((v) => call(contentOptions: v)); - @override - TransitionOptionsCopyWith<$R, TransitionOptions, TransitionOptions>? - get transition => - $value.transition?.copyWith.$chain((v) => call(transition: v)); - @override - MapCopyWith<$R, String, ContentOptions?, - ContentOptionsCopyWith<$R, ContentOptions, ContentOptions>?> - get sections => MapCopyWith($value.sections, - (v, t) => v?.copyWith.$chain(t), (v) => call(sections: v)); - @override - $R call( - {Object? title = $none, - Object? background = $none, - Object? contentOptions = $none, - Object? style = $none, - Object? transition = $none, - String? content, - Map? sections, - String? key}) => - $apply(FieldCopyWithData({ - if (title != $none) #title: title, - if (background != $none) #background: background, - if (contentOptions != $none) #contentOptions: contentOptions, - if (style != $none) #style: style, - if (transition != $none) #transition: transition, - if (content != null) #content: content, - if (sections != null) #sections: sections, - if (key != null) #key: key - })); - @override - TwoColumnHeaderSlide $make(CopyWithData data) => TwoColumnHeaderSlide( - title: data.get(#title, or: $value.title), - background: data.get(#background, or: $value.background), - contentOptions: data.get(#contentOptions, or: $value.contentOptions), - style: data.get(#style, or: $value.style), - transition: data.get(#transition, or: $value.transition), - content: data.get(#content, or: $value.content), - sections: data.get(#sections, or: $value.sections), - key: data.get(#key, or: $value.key)); - - @override - TwoColumnHeaderSlideCopyWith<$R2, TwoColumnHeaderSlide, $Out2> - $chain<$R2, $Out2>(Then<$Out2, $R2> t) => - _TwoColumnHeaderSlideCopyWithImpl($value, $cast, t); -} - -class InvalidSlideMapper extends SubClassMapperBase { - InvalidSlideMapper._(); - - static InvalidSlideMapper? _instance; - static InvalidSlideMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = InvalidSlideMapper._()); - SlideMapper.ensureInitialized().addSubMapper(_instance!); - ContentOptionsMapper.ensureInitialized(); - TransitionOptionsMapper.ensureInitialized(); - } - return _instance!; - } - - @override - final String id = 'InvalidSlide'; - - static ContentOptions? _$contentOptions(InvalidSlide v) => v.contentOptions; - static const Field _f$contentOptions = - Field('contentOptions', _$contentOptions, key: 'content_options'); - static String? _$title(InvalidSlide v) => v.title; - static const Field _f$title = - Field('title', _$title, opt: true); - static String? _$background(InvalidSlide v) => v.background; - static const Field _f$background = - Field('background', _$background, opt: true); - static String? _$style(InvalidSlide v) => v.style; - static const Field _f$style = - Field('style', _$style, opt: true); - static TransitionOptions? _$transition(InvalidSlide v) => v.transition; - static const Field _f$transition = - Field('transition', _$transition, opt: true); - static String _$content(InvalidSlide v) => v.content; - static const Field _f$content = - Field('content', _$content); - static String _$key(InvalidSlide v) => v.key; - static const Field _f$key = Field('key', _$key); - static String _$layout(InvalidSlide v) => v.layout; - static const Field _f$layout = - Field('layout', _$layout, mode: FieldMode.member); - - @override - final MappableFields fields = const { - #contentOptions: _f$contentOptions, - #title: _f$title, - #background: _f$background, - #style: _f$style, - #transition: _f$transition, - #content: _f$content, - #key: _f$key, - #layout: _f$layout, - }; - @override - final bool ignoreNull = true; - - @override - final String discriminatorKey = 'layout'; - @override - final dynamic discriminatorValue = LayoutType.invalid; - @override - late final ClassMapperBase superMapper = SlideMapper.ensureInitialized(); - - static InvalidSlide _instantiate(DecodingData data) { - return InvalidSlide( - contentOptions: data.dec(_f$contentOptions), - title: data.dec(_f$title), - background: data.dec(_f$background), - style: data.dec(_f$style), - transition: data.dec(_f$transition), - content: data.dec(_f$content), - key: data.dec(_f$key)); - } - - @override - final Function instantiate = _instantiate; - - static InvalidSlide fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static InvalidSlide fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin InvalidSlideMappable { - String toJson() { - return InvalidSlideMapper.ensureInitialized() - .encodeJson(this as InvalidSlide); - } - - Map toMap() { - return InvalidSlideMapper.ensureInitialized() - .encodeMap(this as InvalidSlide); - } - - InvalidSlideCopyWith get copyWith => - _InvalidSlideCopyWithImpl(this as InvalidSlide, $identity, $identity); - @override - String toString() { - return InvalidSlideMapper.ensureInitialized() - .stringifyValue(this as InvalidSlide); - } - - @override - bool operator ==(Object other) { - return InvalidSlideMapper.ensureInitialized() - .equalsValue(this as InvalidSlide, other); - } - - @override - int get hashCode { - return InvalidSlideMapper.ensureInitialized() - .hashValue(this as InvalidSlide); - } -} - -extension InvalidSlideValueCopy<$R, $Out> - on ObjectCopyWith<$R, InvalidSlide, $Out> { - InvalidSlideCopyWith<$R, InvalidSlide, $Out> get $asInvalidSlide => - $base.as((v, t, t2) => _InvalidSlideCopyWithImpl(v, t, t2)); -} - -abstract class InvalidSlideCopyWith<$R, $In extends InvalidSlide, $Out> - implements SlideCopyWith<$R, $In, $Out> { - @override - ContentOptionsCopyWith<$R, ContentOptions, ContentOptions>? - get contentOptions; - @override - TransitionOptionsCopyWith<$R, TransitionOptions, TransitionOptions>? - get transition; - @override - $R call( - {ContentOptions? contentOptions, - String? title, - String? background, - String? style, - TransitionOptions? transition, - String? content, - String? key}); - InvalidSlideCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); -} - -class _InvalidSlideCopyWithImpl<$R, $Out> - extends ClassCopyWithBase<$R, InvalidSlide, $Out> - implements InvalidSlideCopyWith<$R, InvalidSlide, $Out> { - _InvalidSlideCopyWithImpl(super.value, super.then, super.then2); - - @override - late final ClassMapperBase $mapper = - InvalidSlideMapper.ensureInitialized(); - @override - ContentOptionsCopyWith<$R, ContentOptions, ContentOptions>? - get contentOptions => $value.contentOptions?.copyWith - .$chain((v) => call(contentOptions: v)); - @override - TransitionOptionsCopyWith<$R, TransitionOptions, TransitionOptions>? - get transition => - $value.transition?.copyWith.$chain((v) => call(transition: v)); - @override - $R call( - {Object? contentOptions = $none, - Object? title = $none, - Object? background = $none, - Object? style = $none, - Object? transition = $none, - String? content, - String? key}) => - $apply(FieldCopyWithData({ - if (contentOptions != $none) #contentOptions: contentOptions, - if (title != $none) #title: title, - if (background != $none) #background: background, - if (style != $none) #style: style, - if (transition != $none) #transition: transition, - if (content != null) #content: content, - if (key != null) #key: key - })); - @override - InvalidSlide $make(CopyWithData data) => InvalidSlide( - contentOptions: data.get(#contentOptions, or: $value.contentOptions), - title: data.get(#title, or: $value.title), - background: data.get(#background, or: $value.background), - style: data.get(#style, or: $value.style), - transition: data.get(#transition, or: $value.transition), - content: data.get(#content, or: $value.content), - key: data.get(#key, or: $value.key)); - - @override - InvalidSlideCopyWith<$R2, InvalidSlide, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t) => - _InvalidSlideCopyWithImpl($value, $cast, t); -} - -class SectionDataMapper extends RecordMapperBase { - static SectionDataMapper? _instance; - SectionDataMapper._(); - - static SectionDataMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = SectionDataMapper._()); - MapperBase.addType((f) => f<({A content, B options})>()); - } - return _instance!; - } - - static String _$content(SectionData v) => v.content; - static const Field _f$content = - Field('content', _$content); - static ContentOptions? _$options(SectionData v) => v.options; - static const Field _f$options = - Field('options', _$options); - - @override - final MappableFields fields = const { - #content: _f$content, - #options: _f$options, - }; - - @override - Function get typeFactory => (f) => f(); - - @override - List apply(MappingContext context) { - return []; - } - - static SectionData _instantiate(DecodingData data) { - return (content: data.dec(_f$content), options: data.dec(_f$options)); - } - - @override - final Function instantiate = _instantiate; - - static SectionData fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static SectionData fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -extension SectionDataMappable on SectionData { - Map toMap() { - return SectionDataMapper.ensureInitialized().encodeMap(this); - } - - String toJson() { - return SectionDataMapper.ensureInitialized().encodeJson(this); - } - - SectionDataCopyWith get copyWith => - _SectionDataCopyWithImpl(this, $identity, $identity); -} - -extension SectionDataValueCopy<$R> - on ObjectCopyWith<$R, SectionData, SectionData> { - SectionDataCopyWith<$R> get $asSectionData => - $base.as((v, t, t2) => _SectionDataCopyWithImpl(v, t, t2)); -} - -abstract class SectionDataCopyWith<$R> - implements RecordCopyWith<$R, SectionData> { - $R call({String? content, ContentOptions? options}); - SectionDataCopyWith<$R2> $chain<$R2>(Then t); -} - -class _SectionDataCopyWithImpl<$R> extends RecordCopyWithBase<$R, SectionData> - implements SectionDataCopyWith<$R> { - _SectionDataCopyWithImpl(super.value, super.then, super.then2); - - @override - late final RecordMapperBase $mapper = - SectionDataMapper.ensureInitialized(); - @override - $R call({String? content, Object? options = $none}) => - $apply(FieldCopyWithData({ - if (content != null) #content: content, - if (options != $none) #options: options - })); - @override - SectionData $make(CopyWithData data) => ( - content: data.get(#content, or: $value.content), - options: data.get(#options, or: $value.options) - ); - - @override - SectionDataCopyWith<$R2> $chain<$R2>(Then t) => - _SectionDataCopyWithImpl($value, $cast, t); -} diff --git a/packages/superdeck/lib/providers/assets_provider.dart b/packages/superdeck/lib/providers/assets_provider.dart deleted file mode 100644 index e2d0f31c..00000000 --- a/packages/superdeck/lib/providers/assets_provider.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter/widgets.dart'; - -import '../superdeck.dart'; - -class AssetsProvider extends InheritedWidget { - const AssetsProvider({ - super.key, - required this.assets, - required super.child, - }); - - final List assets; - - static List of(BuildContext context) { - return context - .dependOnInheritedWidgetOfExactType() - ?.assets ?? - []; - } - - static AssetsProvider inherit({ - required BuildContext context, - required Widget child, - }) { - return AssetsProvider(assets: of(context), child: child); - } - - @override - bool updateShouldNotify(covariant AssetsProvider oldWidget) { - return assets != oldWidget.assets; - } -} diff --git a/packages/superdeck/lib/providers/controller.dart b/packages/superdeck/lib/providers/controller.dart deleted file mode 100644 index 57ccadc2..00000000 --- a/packages/superdeck/lib/providers/controller.dart +++ /dev/null @@ -1,77 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; - -import '../models/asset_model.dart'; -import '../models/slide_model.dart'; -import '../services/reference_service.dart'; - -final $superdeck = SuperDeckController.instance; - -class SuperDeckController extends ChangeNotifier { - SuperDeckController._(); - - static final instance = SuperDeckController._(); - - bool _initialized = false; - - static Future initialize() async { - if (instance._initialized) return; - instance._initialized = true; - await instance._loadData(); - ReferenceService.instance.listen(instance._loadData); - } - - bool _loading = false; - Object? _error; - List _slides = []; - List _assets = []; - bool _completed = false; - bool _isRefreshing = false; - - bool get loading => _loading; - Object? get error => _error; - List get slides => _slides; - List get assets => _assets; - bool get completed => _completed; - bool get isRefreshing => _isRefreshing; - - Future _loadData() async { - _loading = true; - _error = null; - _completed = false; - notifyListeners(); - - try { - final data = await ReferenceService.instance.loadReference(); - - _slides = data.slides; - _assets = data.assets; - } catch (e) { - _error = e; - } finally { - _completed = true; - _loading = false; - _isRefreshing = false; - notifyListeners(); - } - } - - Future refresh() async { - _isRefreshing = true; - await _loadData(); - } -} - -T useSuperdeckSelector(T Function(SuperDeckController) selector) { - return useListenableSelector( - $superdeck, - () => selector($superdeck), - ); -} - -List useSlides() { - return useListenableSelector( - $superdeck, - () => $superdeck.slides, - ); -} diff --git a/packages/superdeck/lib/providers/examples_provider.dart b/packages/superdeck/lib/providers/examples_provider.dart deleted file mode 100644 index a067cbea..00000000 --- a/packages/superdeck/lib/providers/examples_provider.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../superdeck.dart'; - -class ExamplesProvider extends InheritedWidget { - const ExamplesProvider({ - super.key, - required this.examples, - required super.child, - }); - - final Map examples; - - static Map of(BuildContext context) { - return context - .dependOnInheritedWidgetOfExactType()! - .examples; - } - - static ExamplesProvider inherit({ - required BuildContext context, - required Widget child, - }) { - return ExamplesProvider(examples: of(context), child: child); - } - - @override - bool updateShouldNotify(covariant ExamplesProvider oldWidget) { - return examples != oldWidget.examples; - } -} diff --git a/packages/superdeck/lib/providers/slide_provider.dart b/packages/superdeck/lib/providers/slide_provider.dart deleted file mode 100644 index 97fd8965..00000000 --- a/packages/superdeck/lib/providers/slide_provider.dart +++ /dev/null @@ -1,51 +0,0 @@ -// Create a SlideProvider that extends an Inherited widget -import 'package:flutter/material.dart'; - -import '../models/slide_model.dart'; -import '../templates/templates.dart'; - -enum SlideProviderAspect { - slide, - constraints, -} - -class SlideProvider extends InheritedWidget { - const SlideProvider({ - super.key, - required this.slide, - required super.child, - }); - - final T slide; - - static T of(BuildContext context) { - final slideProvider = - context.dependOnInheritedWidgetOfExactType>(); - if (slideProvider == null) { - throw Exception('SlideProvider not found in context'); - } - return slideProvider.slide; - } - - @override - bool updateShouldNotify(covariant SlideProvider oldWidget) { - return oldWidget.slide != slide; - } -} - -class SlideBuilder extends StatelessWidget { - const SlideBuilder( - this.config, { - super.key, - }); - - final Slide config; - - @override - Widget build(BuildContext context) { - return SlideProvider( - slide: config, - child: TemplateBuilder.buildTemplate(config), - ); - } -} diff --git a/packages/superdeck/lib/providers/snapshot_provider.dart b/packages/superdeck/lib/providers/snapshot_provider.dart deleted file mode 100644 index bd7d66d3..00000000 --- a/packages/superdeck/lib/providers/snapshot_provider.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flutter/widgets.dart'; - -class SnapshotRef { - BuildContext? _context; - - SnapshotRef._(); - - static final SnapshotRef instance = SnapshotRef._(); - - void setContext(BuildContext context) { - Scrollable.ensureVisible(context); - _context = context; - } - - BuildContext get context { - assert(_context != null, '$runtimeType has not updated its context'); - return _context!; - } -} - -class SnapshotProvider extends InheritedWidget { - final bool isCapturing; - - const SnapshotProvider({ - required this.isCapturing, - required super.child, - super.key, - }); - - static SnapshotProvider? of(BuildContext context) { - return context.dependOnInheritedWidgetOfExactType(); - } - - @override - bool updateShouldNotify(covariant SnapshotProvider oldWidget) { - return oldWidget.isCapturing != isCapturing; - } - - static bool isCapturingOf(BuildContext context) { - return of(context)?.isCapturing ?? false; - } -} diff --git a/packages/superdeck/lib/providers/style_provider.dart b/packages/superdeck/lib/providers/style_provider.dart deleted file mode 100644 index 34fcceb5..00000000 --- a/packages/superdeck/lib/providers/style_provider.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:flutter/widgets.dart'; - -import '../styles/style_util.dart'; -import '../superdeck.dart'; - -class StyleProvider extends InheritedWidget { - StyleProvider({ - super.key, - Style? baseStyle, - this.styles = const {}, - required super.child, - }) : style = defaultStyle.merge(baseStyle); - - final Style style; - final Map styles; - - static StyleProvider _of(BuildContext context) { - return context.dependOnInheritedWidgetOfExactType()!; - } - - static Style of(BuildContext context, String? styleName) { - final provider = _of(context); - if (styleName != null) { - final style = provider.styles[styleName]; - assert(style != null, 'No Style for $styleName found.'); - return provider.style.merge(style); - } - return _of(context).style; - } - - static StyleProvider inherit({ - required BuildContext context, - required Widget child, - }) { - return StyleProvider( - baseStyle: _of(context).style, - styles: _of(context).styles, - child: child, - ); - } - - @override - bool updateShouldNotify(covariant StyleProvider oldWidget) { - return style != oldWidget.style; - } -} diff --git a/packages/superdeck/lib/schema/schema_model.dart b/packages/superdeck/lib/schema/schema_model.dart deleted file mode 100644 index ff327a5c..00000000 --- a/packages/superdeck/lib/schema/schema_model.dart +++ /dev/null @@ -1,162 +0,0 @@ -import 'schema_validation.dart'; -import 'schema_values.dart'; -import 'validators.dart'; - -typedef JSON = Map; - -class SchemaMap extends SchemaValue> { - final Map properties; - final bool additionalProperties; - const SchemaMap( - this.properties, { - super.optional = true, - this.additionalProperties = false, - super.validators = const [], - }); - - @override - SchemaMap copyWith({ - bool? optional, - bool? additionalProperties, - Map? properties, - List>>? validators, - }) { - return SchemaMap( - properties ?? this.properties, - additionalProperties: additionalProperties ?? this.additionalProperties, - optional: optional ?? optionalValue, - validators: validators ?? this.validators, - ); - } - - @override - Map? tryParse(Object? value) { - return value is Map ? value : null; - } - - SchemaMap mergeSchema(SchemaMap schema) { - return merge( - schema.properties, - additionalProperties: schema.additionalProperties, - ); - } - - T? getSchemaValue(String key) { - return properties[key] as T?; - } - - SchemaMap merge( - Map properties, { - bool? additionalProperties, - bool? optional, - }) { - // if property SchemaValue is of SchemaMap, we need to merge them - final mergedProperties = {...this.properties}; - - for (final entry in properties.entries) { - final key = entry.key; - final prop = entry.value; - - final existingProp = mergedProperties[key]; - - if (existingProp is SchemaMap && prop is SchemaMap) { - mergedProperties[key] = existingProp.merge(prop.properties); - } else { - mergedProperties[key] = prop; - } - } - - return copyWith( - properties: mergedProperties, - optional: optional, - additionalProperties: additionalProperties, - ); - } - - @override - SchemaValidationResult validate(List path, Object? value) { - if (value == null) { - return optionalValue - ? SchemaValidationResult.valid(path) - : SchemaValidationResult.requiredPropMissing(path); - } - - final parsedValue = tryParse(value); - - if (parsedValue == null) { - return SchemaValidationResult.invalidType( - path, - value, - {}.toString(), - ); - } - - final keys = parsedValue.keys.toSet(); - - final required = properties.entries - .where((entry) => !entry.value.optionalValue) - .map((entry) => entry.key); - - final requiredKeys = required.toSet(); - - if (!keys.containsAll(requiredKeys)) { - return SchemaValidationResult( - key: path, - errors: requiredKeys - .difference(keys) - .map(SchemaError.requiredPropMissing) - .toList()); - } - - if (additionalProperties == false) { - final extraKeys = keys.difference(properties.keys.toSet()); - if (extraKeys.isNotEmpty) { - return SchemaValidationResult( - key: path, - errors: - extraKeys.map(SchemaError.unallowedAdditionalProperty).toList(), - ); - } - } - - for (final entry in parsedValue.entries) { - final key = entry.key; - final prop = properties[key]; - - if (prop == null) { - return additionalProperties == false - ? SchemaValidationResult( - key: path, - errors: [SchemaError.unallowedAdditionalProperty(key)]) - : SchemaValidationResult.valid(path); - } - - final result = prop.validate([...path, key], entry.value); - if (!result.isValid) { - return result; - } - } - - return SchemaValidationResult.valid(path); - } -} - -class SchemaShape extends SchemaMap { - const SchemaShape( - super.properties, { - super.additionalProperties = false, - }); -} - -typedef _DoubleType = double; - -class Schema { - const Schema._(); - - static const string = SchemaValue(); - static const map = SchemaShape.new; - static const double = SchemaValue<_DoubleType>(); - static const integer = SchemaValue(); - static const boolean = SchemaValue(); - static const any = SchemaShape({}, additionalProperties: true); -} diff --git a/packages/superdeck/lib/schema/schema_validation.dart b/packages/superdeck/lib/schema/schema_validation.dart deleted file mode 100644 index b37f1428..00000000 --- a/packages/superdeck/lib/schema/schema_validation.dart +++ /dev/null @@ -1,124 +0,0 @@ -import 'package:dart_mappable/dart_mappable.dart'; - -part 'schema_validation.mapper.dart'; - -typedef JSON = Map; - -class SchemaValidationException implements Exception { - final SchemaValidationResult result; - - const SchemaValidationException(this.result); -} - -enum SchemaErrorType { - unallowedAdditionalProperty, - enumViolated, - requiredPropMissing, - invalidType, - constraints, - unknown; -} - -@MappableClass() -class SchemaError with SchemaErrorMappable { - final SchemaErrorType type; - final String message; - - const SchemaError.unknown() - : type = SchemaErrorType.unknown, - message = 'Unknown error'; - - const SchemaError.constraints(this.message) - : type = SchemaErrorType.constraints; - - const SchemaError.unallowedAdditionalProperty(String property) - : type = SchemaErrorType.unallowedAdditionalProperty, - message = 'Unallowed property: [$property]'; - - const SchemaError.enumViolated(String value, List possibleValues) - : type = SchemaErrorType.enumViolated, - message = 'Wrong value: [$value] \n\n Possible values: $possibleValues'; - - const SchemaError.requiredPropMissing(String property) - : type = SchemaErrorType.requiredPropMissing, - message = 'Missing prop: [$property]'; - - const SchemaError.invalidType(Type value, String expectedType) - : type = SchemaErrorType.invalidType, - message = 'Invalid type: [$expectedType] got [$value]'; - - @override - String toString() { - return 'SchemaValidationError{type: $type, message: $message}'; - } -} - -@MappableClass() -class SchemaValidationResult with SchemaValidationResultMappable { - final List key; - final List errors; - - const SchemaValidationResult({ - required this.key, - required this.errors, - }); - - const SchemaValidationResult.valid(this.key) : errors = const []; - - factory SchemaValidationResult.invalidType( - List path, - Object value, - String expectedType, - ) { - return SchemaValidationResult( - key: path, - errors: [ - SchemaError.invalidType( - value.runtimeType, - expectedType, - ) - ], - ); - } - - factory SchemaValidationResult.unallowedAdditionalProperty( - List path, String property) { - return SchemaValidationResult( - key: path, - errors: [SchemaError.unallowedAdditionalProperty(property)], - ); - } - - factory SchemaValidationResult.enumViolated( - List path, String value, List possibleValues) { - return SchemaValidationResult( - key: path, - errors: [SchemaError.enumViolated(value, possibleValues)], - ); - } - - factory SchemaValidationResult.requiredPropMissing(List path) { - return SchemaValidationResult( - key: path, - errors: [SchemaError.requiredPropMissing(path.last)], - ); - } - - factory SchemaValidationResult.constraints( - List path, String message) { - return SchemaValidationResult( - key: path, - errors: [SchemaError.constraints(message)], - ); - } - - @override - String toString() { - return '${errors.isEmpty ? 'VALID' : 'INVALID'}${errors.isEmpty ? ', Errors: $errors' : ''}'; - } - - bool get isValid => errors.isEmpty; - - static const fromMap = SchemaValidationResultMapper.fromMap; - static const fromJson = SchemaValidationResultMapper.fromJson; -} diff --git a/packages/superdeck/lib/schema/schema_validation.mapper.dart b/packages/superdeck/lib/schema/schema_validation.mapper.dart deleted file mode 100644 index 1ea4d17a..00000000 --- a/packages/superdeck/lib/schema/schema_validation.mapper.dart +++ /dev/null @@ -1,248 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member -// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter - -part of 'schema_validation.dart'; - -class SchemaErrorMapper extends ClassMapperBase { - SchemaErrorMapper._(); - - static SchemaErrorMapper? _instance; - static SchemaErrorMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = SchemaErrorMapper._()); - } - return _instance!; - } - - @override - final String id = 'SchemaError'; - - static SchemaErrorType _$type(SchemaError v) => v.type; - static const Field _f$type = - Field('type', _$type, mode: FieldMode.member); - static String _$message(SchemaError v) => v.message; - static const Field _f$message = - Field('message', _$message, mode: FieldMode.member); - - @override - final MappableFields fields = const { - #type: _f$type, - #message: _f$message, - }; - @override - final bool ignoreNull = true; - - static SchemaError _instantiate(DecodingData data) { - return SchemaError.unknown(); - } - - @override - final Function instantiate = _instantiate; - - static SchemaError fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static SchemaError fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin SchemaErrorMappable { - String toJson() { - return SchemaErrorMapper.ensureInitialized() - .encodeJson(this as SchemaError); - } - - Map toMap() { - return SchemaErrorMapper.ensureInitialized() - .encodeMap(this as SchemaError); - } - - SchemaErrorCopyWith get copyWith => - _SchemaErrorCopyWithImpl(this as SchemaError, $identity, $identity); - @override - String toString() { - return SchemaErrorMapper.ensureInitialized() - .stringifyValue(this as SchemaError); - } - - @override - bool operator ==(Object other) { - return SchemaErrorMapper.ensureInitialized() - .equalsValue(this as SchemaError, other); - } - - @override - int get hashCode { - return SchemaErrorMapper.ensureInitialized().hashValue(this as SchemaError); - } -} - -extension SchemaErrorValueCopy<$R, $Out> - on ObjectCopyWith<$R, SchemaError, $Out> { - SchemaErrorCopyWith<$R, SchemaError, $Out> get $asSchemaError => - $base.as((v, t, t2) => _SchemaErrorCopyWithImpl(v, t, t2)); -} - -abstract class SchemaErrorCopyWith<$R, $In extends SchemaError, $Out> - implements ClassCopyWith<$R, $In, $Out> { - $R call(); - SchemaErrorCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); -} - -class _SchemaErrorCopyWithImpl<$R, $Out> - extends ClassCopyWithBase<$R, SchemaError, $Out> - implements SchemaErrorCopyWith<$R, SchemaError, $Out> { - _SchemaErrorCopyWithImpl(super.value, super.then, super.then2); - - @override - late final ClassMapperBase $mapper = - SchemaErrorMapper.ensureInitialized(); - @override - $R call() => $apply(FieldCopyWithData({})); - @override - SchemaError $make(CopyWithData data) => SchemaError.unknown(); - - @override - SchemaErrorCopyWith<$R2, SchemaError, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t) => - _SchemaErrorCopyWithImpl($value, $cast, t); -} - -class SchemaValidationResultMapper - extends ClassMapperBase { - SchemaValidationResultMapper._(); - - static SchemaValidationResultMapper? _instance; - static SchemaValidationResultMapper ensureInitialized() { - if (_instance == null) { - MapperContainer.globals.use(_instance = SchemaValidationResultMapper._()); - SchemaErrorMapper.ensureInitialized(); - } - return _instance!; - } - - @override - final String id = 'SchemaValidationResult'; - - static List _$key(SchemaValidationResult v) => v.key; - static const Field> _f$key = - Field('key', _$key); - static List _$errors(SchemaValidationResult v) => v.errors; - static const Field> _f$errors = - Field('errors', _$errors); - - @override - final MappableFields fields = const { - #key: _f$key, - #errors: _f$errors, - }; - @override - final bool ignoreNull = true; - - static SchemaValidationResult _instantiate(DecodingData data) { - return SchemaValidationResult( - key: data.dec(_f$key), errors: data.dec(_f$errors)); - } - - @override - final Function instantiate = _instantiate; - - static SchemaValidationResult fromMap(Map map) { - return ensureInitialized().decodeMap(map); - } - - static SchemaValidationResult fromJson(String json) { - return ensureInitialized().decodeJson(json); - } -} - -mixin SchemaValidationResultMappable { - String toJson() { - return SchemaValidationResultMapper.ensureInitialized() - .encodeJson(this as SchemaValidationResult); - } - - Map toMap() { - return SchemaValidationResultMapper.ensureInitialized() - .encodeMap(this as SchemaValidationResult); - } - - SchemaValidationResultCopyWith - get copyWith => _SchemaValidationResultCopyWithImpl( - this as SchemaValidationResult, $identity, $identity); - @override - String toString() { - return SchemaValidationResultMapper.ensureInitialized() - .stringifyValue(this as SchemaValidationResult); - } - - @override - bool operator ==(Object other) { - return SchemaValidationResultMapper.ensureInitialized() - .equalsValue(this as SchemaValidationResult, other); - } - - @override - int get hashCode { - return SchemaValidationResultMapper.ensureInitialized() - .hashValue(this as SchemaValidationResult); - } -} - -extension SchemaValidationResultValueCopy<$R, $Out> - on ObjectCopyWith<$R, SchemaValidationResult, $Out> { - SchemaValidationResultCopyWith<$R, SchemaValidationResult, $Out> - get $asSchemaValidationResult => - $base.as((v, t, t2) => _SchemaValidationResultCopyWithImpl(v, t, t2)); -} - -abstract class SchemaValidationResultCopyWith< - $R, - $In extends SchemaValidationResult, - $Out> implements ClassCopyWith<$R, $In, $Out> { - ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get key; - ListCopyWith<$R, SchemaError, - SchemaErrorCopyWith<$R, SchemaError, SchemaError>> get errors; - $R call({List? key, List? errors}); - SchemaValidationResultCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( - Then<$Out2, $R2> t); -} - -class _SchemaValidationResultCopyWithImpl<$R, $Out> - extends ClassCopyWithBase<$R, SchemaValidationResult, $Out> - implements - SchemaValidationResultCopyWith<$R, SchemaValidationResult, $Out> { - _SchemaValidationResultCopyWithImpl(super.value, super.then, super.then2); - - @override - late final ClassMapperBase $mapper = - SchemaValidationResultMapper.ensureInitialized(); - @override - ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get key => - ListCopyWith($value.key, (v, t) => ObjectCopyWith(v, $identity, t), - (v) => call(key: v)); - @override - ListCopyWith<$R, SchemaError, - SchemaErrorCopyWith<$R, SchemaError, SchemaError>> - get errors => ListCopyWith($value.errors, (v, t) => v.copyWith.$chain(t), - (v) => call(errors: v)); - @override - $R call({List? key, List? errors}) => - $apply(FieldCopyWithData( - {if (key != null) #key: key, if (errors != null) #errors: errors})); - @override - SchemaValidationResult $make(CopyWithData data) => SchemaValidationResult( - key: data.get(#key, or: $value.key), - errors: data.get(#errors, or: $value.errors)); - - @override - SchemaValidationResultCopyWith<$R2, SchemaValidationResult, $Out2> - $chain<$R2, $Out2>(Then<$Out2, $R2> t) => - _SchemaValidationResultCopyWithImpl($value, $cast, t); -} diff --git a/packages/superdeck/lib/schema/schema_values.dart b/packages/superdeck/lib/schema/schema_values.dart deleted file mode 100644 index bf05c956..00000000 --- a/packages/superdeck/lib/schema/schema_values.dart +++ /dev/null @@ -1,214 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'schema_validation.dart'; -import 'validators.dart'; - -class SchemaValue { - const SchemaValue({ - bool optional = true, - this.validators = const [], - }) : optionalValue = optional; - - SchemaValue copyWith({ - bool? optional, - List>? validators, - }) { - return SchemaValue( - optional: optional ?? this.optionalValue, - validators: validators ?? this.validators, - ); - } - - SchemaValue required() { - return copyWith(optional: false); - } - - SchemaValue optional() { - return copyWith(optional: true); - } - - @protected - final bool optionalValue; - - final List> validators; - - bool get requiredValue => !optionalValue; - - V? tryParse(Object? value) { - if (value is V) { - return value; - } - if (V is int) { - return _tryParseInt(value) as V?; - } - - if (V is double) { - return _tryParseDouble(value) as V?; - } - - if (V is bool) { - return _tryParseBool(value) as V?; - } - - return null; - } - - void validateOrThrow(Object value) { - final result = validate([], value); - if (!result.isValid) { - throw SchemaValidationException(result); - } - } - - SchemaValidationResult validate(List path, Object? value) { - if (value == null) { - return optionalValue - ? SchemaValidationResult.valid(path) - : SchemaValidationResult.requiredPropMissing(path); - } - - final valueType = tryParse(value); - - if (valueType == null) { - return SchemaValidationResult.invalidType(path, value, V.toString()); - } - - for (final validator in validators) { - final error = validator.validate(valueType); - if (error != null) { - return SchemaValidationResult.constraints(path, error.message); - } - } - - return SchemaValidationResult.valid(path); - } -} - -/// Used to remove value from SchemaMap - -double? _tryParseDouble(Object? value) { - if (value is double) { - return value; - } - - if (value is int) { - return value.toDouble(); - } - - if (value is String) { - return double.tryParse(value); - } - - return null; -} - -bool? _tryParseBool(Object? value) { - if (value is bool) { - return value; - } - - if (value is String) { - if (value.toLowerCase() == 'true') { - return true; - } else if (value.toLowerCase() == 'false') { - return false; - } - } - - return null; -} - -int? _tryParseInt(Object? value) { - if (value is int) { - return value; - } - - if (value is String) { - return int.tryParse(value); - } - - return null; -} - -class BooleanSchema extends SchemaValue { - const BooleanSchema({super.optional = false, super.validators = const []}); - - @override - BooleanSchema copyWith({ - bool? optional, - List>? validators, - }) { - return BooleanSchema( - optional: optional ?? optionalValue, - validators: validators ?? this.validators, - ); - } - - @override - bool? tryParse(Object? value) { - if (value is bool) { - return value; - } - - if (value is String) { - if (value.toLowerCase() == 'true') { - return true; - } else if (value.toLowerCase() == 'false') { - return false; - } - } - - return null; - } -} - -extension StringSchemaExt on SchemaValue { - SchemaValue isPosixPath() { - return copyWith(validators: [ - ...validators, - const PosixPathValidator(), - ]); - } - - SchemaValue isEmail() { - return copyWith(validators: [ - ...validators, - const EmailValidator(), - ]); - } - - SchemaValue isHexColor() { - return copyWith(validators: [ - ...validators, - const HexColorValidator(), - ]); - } - - SchemaValue isArray(List values) { - return copyWith(validators: [ - ...validators, - ArrayValidator(values), - ]); - } - - SchemaValue isEmpty() { - return copyWith(validators: [ - ...validators, - const IsEmptyValidator(), - ]); - } - - SchemaValue minLength(int min) { - return copyWith(validators: [ - ...validators, - MinLengthValidator(min), - ]); - } - - SchemaValue maxLength(int max) { - return copyWith(validators: [ - ...validators, - MaxLengthValidator(max), - ]); - } -} diff --git a/packages/superdeck/lib/schema/validators.dart b/packages/superdeck/lib/schema/validators.dart deleted file mode 100644 index 230e60c6..00000000 --- a/packages/superdeck/lib/schema/validators.dart +++ /dev/null @@ -1,218 +0,0 @@ -import 'schema_validation.dart'; - -abstract class Validator { - const Validator(); - - SchemaError? validate(T value); -} - -class ArrayValidator extends Validator { - final List values; - const ArrayValidator(this.values); - - @override - SchemaError? validate(Object? value) { - if (value is String) { - if (values.contains(value)) { - return null; - } - } - return SchemaError.enumViolated( - '$value', - values, - ); - } -} - -class EmailValidator extends RegexValidator { - const EmailValidator() - : super( - name: 'email', - pattern: r'^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$', - example: 'example@domain.com', - ); -} - -class UrlValidator extends RegexValidator { - const UrlValidator() - : super( - name: 'url', - pattern: - r'^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$', - example: 'https://example.com', - ); -} - -class PosixPathValidator extends RegexValidator { - const PosixPathValidator() - : super( - name: 'posix path', - example: '/path/to/file', - pattern: r'^(/[^/ ]*)+/?$', - ); -} - -class HexColorValidator extends RegexValidator { - const HexColorValidator() - : super( - name: 'hex color', - example: '#ff0000', - pattern: r'^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$', - ); -} - -class RegexValidator extends Validator { - final String name; - final String pattern; - final String example; - const RegexValidator({ - required this.name, - required this.pattern, - required this.example, - }); - - @override - SchemaError? validate(String value) { - if (!RegExp(pattern).hasMatch(value)) { - return SchemaError.constraints( - 'String does is not $name. Example: $example', - ); - } - - return null; - } -} - -class IsEmptyValidator extends Validator { - const IsEmptyValidator(); - - @override - SchemaError? validate(String value) { - return value.isEmpty - ? null - : const SchemaError.constraints('String is not empty'); - } -} - -class MinLengthValidator extends Validator { - final int min; - const MinLengthValidator(this.min); - - @override - SchemaError? validate(String value) { - return value.length >= min - ? null - : SchemaError.constraints( - 'String length is less than the minimum required length: $min', - ); - } -} - -class MaxLengthValidator extends Validator { - final int max; - const MaxLengthValidator(this.max); - - @override - SchemaError? validate(String value) { - return value.length <= max - ? null - : SchemaError.constraints( - 'String length is greater than the maximum required length: $max', - ); - } -} - -class MinValueValidator extends Validator { - final num min; - const MinValueValidator(this.min); - - @override - SchemaError? validate(num value) { - return value >= min - ? null - : SchemaError.constraints( - 'Value is less than the minimum required value: $min', - ); - } -} - -class MaxValueValidator extends Validator { - final num max; - const MaxValueValidator(this.max); - - @override - SchemaError? validate(num value) { - return value <= max - ? null - : SchemaError.constraints( - 'Value is greater than the maximum required value: $max', - ); - } -} - -class RangeValidator extends Validator { - final num min; - final num max; - const RangeValidator(this.min, this.max); - - @override - SchemaError? validate(num value) { - return value >= min && value <= max - ? null - : SchemaError.constraints( - 'Value is not within the required range: $min - $max', - ); - } -} - -class RequiredValidator extends Validator { - const RequiredValidator(); - - @override - SchemaError? validate(value) { - return value != null ? null : const SchemaError.constraints('is required'); - } -} - -// unique item list validator -class UniqueItemsValidator extends Validator> { - const UniqueItemsValidator(); - - @override - SchemaError? validate(List value) { - final unique = value.toSet().toList(); - return unique.length == value.length - ? null - : const SchemaError.constraints('List items are not unique'); - } -} - -// min length of list validator -class MinItemsValidator extends Validator> { - final int min; - const MinItemsValidator(this.min); - - @override - SchemaError? validate(List value) { - return value.length >= min - ? null - : SchemaError.constraints( - 'List length is less than the minimum required length: $min', - ); - } -} - -// max length of list validator -class MaxItemsValidator extends Validator> { - final int max; - const MaxItemsValidator(this.max); - - @override - SchemaError? validate(List value) { - return value.length <= max - ? null - : SchemaError.constraints( - 'List length is greater than the maximum required length: $max', - ); - } -} diff --git a/packages/superdeck/lib/screens/export_screen.dart b/packages/superdeck/lib/screens/export_screen.dart deleted file mode 100644 index 21089c68..00000000 --- a/packages/superdeck/lib/screens/export_screen.dart +++ /dev/null @@ -1,134 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; - -import '../components/remix/button.dart'; -import '../helpers/extensions.dart'; -import '../helpers/routes.dart'; -import '../services/export_service.dart'; -import '../services/snapshot_service.dart'; -import '../superdeck.dart'; - -class ExportScreen extends HookWidget { - const ExportScreen({super.key}); - - @override - Widget build(BuildContext context) { - final slides = useSlides(); - final currentSlideIndex = useValueNotifier( - context.currentSlideIndex, [context.currentSlideIndex]); - - final export = usePdfExportController( - slides: slides, - slideIndex: currentSlideIndex.value, - ); - - final inProgress = export.inProgress; - - return Stack( - children: [ - Center( - child: export.render(), - ), - Positioned.fill( - child: Container( - color: const Color.fromARGB(255, 14, 14, 14).withOpacity(0.9), - )), - inProgress ? _ProgressDialog(export) : ExportDialog(export), - ], - ); - } -} - -class _ProgressDialog extends StatelessWidget { - const _ProgressDialog(this.controller); - - final PdfExportController controller; - - @override - Widget build(BuildContext context) { - return Dialog( - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 32.0, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - controller.isComplete - ? Icon( - Icons.check_circle, - color: context.colorScheme.primary, - size: 42, - ) - : SizedBox( - height: 42, - width: 42, - child: CircularProgressIndicator( - color: context.colorScheme.primary, - value: controller.isBuilding ? null : controller.progress, - ), - ), - const SizedBox(height: 16.0), - Text(controller.progressText), - ], - ), - ), - ); - } -} - -class ExportDialog extends HookWidget { - const ExportDialog( - this.controller, { - super.key, - }); - final PdfExportController controller; - @override - Widget build(BuildContext context) { - final dropdownItems = SnapshotQuality.values.map( - (quality) => DropdownMenuItem( - value: quality, - child: Text( - quality.name.capitalize(), - ), - ), - ); - - return Dialog( - backgroundColor: Colors.black, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text('Select export quality:'), - const SizedBox(height: 16.0), - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.0), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - DropdownButton( - isDense: true, - value: controller.quality, - focusColor: Colors.transparent, - underline: const SizedBox.shrink(), - onChanged: (value) => controller.quality = value!, - items: dropdownItems.toList(), - ), - SizedBox(width: 10), - SDButtonSolid( - label: 'Export', - onPressed: () => controller.start(), - ), - ], - )), - ], - ), - ), - ); - } -} diff --git a/packages/superdeck/lib/screens/presentation_screen.dart b/packages/superdeck/lib/screens/presentation_screen.dart deleted file mode 100644 index 66b462ec..00000000 --- a/packages/superdeck/lib/screens/presentation_screen.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; - -import '../components/molecules/slide_preview.dart'; -import '../helpers/hooks.dart'; -import '../helpers/routes.dart'; -import '../superdeck.dart'; - -class PresentationScreen extends HookWidget { - const PresentationScreen({super.key}); - - final _duration = const Duration(milliseconds: 300); - final _curve = Curves.easeInOutCubic; - - @override - Widget build(BuildContext context) { - final slideIndex = context.currentSlideIndex; - final pageController = usePageController(initialPage: slideIndex); - - final slides = useSlides(); - - usePostFrameEffect(() { - if (slideIndex >= slides.length) { - return; - } - - if (slideIndex < 0) { - return; - } - pageController.animateToPage( - slideIndex, - duration: _duration, - curve: _curve, - ); - }, [slideIndex, slides.length]); - - return Center( - child: PageView.builder( - controller: pageController, - itemCount: slides.length, - itemBuilder: (_, index) { - return SlidePreview(slides[index]); - }, - ), - ); - } -} diff --git a/packages/superdeck/lib/services/export_service.dart b/packages/superdeck/lib/services/export_service.dart deleted file mode 100644 index 30e0b03e..00000000 --- a/packages/superdeck/lib/services/export_service.dart +++ /dev/null @@ -1,256 +0,0 @@ -import 'dart:async'; - -import 'package:file_saver/file_saver.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:pdf/pdf.dart'; -import 'package:pdf/widgets.dart' as pw; - -import '../components/molecules/slide_preview.dart'; -import '../helpers/constants.dart'; -import '../superdeck.dart'; -import 'snapshot_service.dart'; - -enum PdfExportStatus { - idle, - capturing, - building, - complete, -} - -class PdfExportController extends ChangeNotifier { - SnapshotQuality _quality = SnapshotQuality.good; - PdfExportStatus _status = PdfExportStatus.idle; - List _images = []; - late final PageController _pageController; - - /// Create a map for keys and GlobalKey for each slide - Map _slideKeys = {}; - - late final List _slides; - - bool get isComplete => _status == PdfExportStatus.complete; - - PdfExportController({ - required List slides, - required int initialIndex, - }) : _slides = slides { - _slideKeys = {for (var slide in _slides) slide.key: GlobalKey()}; - _pageController = PageController(initialPage: initialIndex); - } - - bool get inProgress => _status != PdfExportStatus.idle; - - bool get isBuilding => _status == PdfExportStatus.building; - - double get progress => _images.length / _slides.length; - - String get progressText { - if (isBuilding) { - return 'Building PDF...'; - } - return isComplete - ? 'Done' - : 'Exporting ${_images.length} / ${_slides.length}'; - } - - SnapshotQuality get quality => _quality; - - set quality(SnapshotQuality quality) { - if (_quality == quality) return; - _quality = quality; - notifyListeners(); - } - - PdfExportStatus get status => _status; - - Future _wait() async { - await Future.delayed(Durations.short1); - } - - Future start() async { - final currentPage = _pageController.page?.toInt() ?? 0; - _status = PdfExportStatus.capturing; - _pageController.jumpToPage(0); - notifyListeners(); - - for (var i = 0; i < _slides.length; i++) { - await _convertSlide(i); - } - await _wait(); - _status = PdfExportStatus.building; - notifyListeners(); - await _wait(); - final pdf = await _buildPdf(_images); - await _wait(); - _pageController.jumpToPage(currentPage); - - _status = PdfExportStatus.complete; - _images = []; - notifyListeners(); - await _savePdf(pdf); - await _wait(); - _status = PdfExportStatus.idle; - notifyListeners(); - } - - Future _convertSlide(int index) async { - final slide = _slides[index]; - final key = _slideKeys[slide.key]!; - - _pageController.jumpToPage(index); - - // Keep checking until key is attached to the widget - while (!(key.currentContext?.findRenderObject()?.attached ?? false)) { - await _wait(); - } - - final image = await SnapshotService.instance.generateWithKey( - quality: _quality, - key: _slideKeys[slide.key]!, - ); - _images = [..._images, image]; - notifyListeners(); - } - - Future _savePdf(Uint8List pdf) async { - try { - final result = await FileSaver.instance.saveFile( - name: 'superdeck', - bytes: pdf, - ext: 'pdf', - mimeType: MimeType.pdf, - ); - print('Save as result: $result'); - } catch (e) { - print('Error saving pdf: $e'); - } - } - - Future _buildPdf(List images) async { - final pdf = pw.Document(); - - for (final imageData in images) { - // see how logn this takes per page - final image = pw.MemoryImage(imageData); - - final pdfImage = pw.Image( - image, - width: kResolution.width, - height: kResolution.height, - ); - - pdf.addPage( - pw.Page( - pageFormat: PdfPageFormat( - kResolution.width, - kResolution.height, - ), - build: (pw.Context context) { - return pw.Center( - child: pdfImage, - ); - }, - ), - ); - } - - return await pdf.save(); - } - - Widget render() { - return PageView.builder( - controller: _pageController, - itemCount: _slides.length, - itemBuilder: (_, index) { - final slide = _slides[index]; - - return RepaintBoundary( - key: _slideKeys[slide.key], - child: SlidePreview(slide), - ); - }, - ); - } - - @override - void dispose() { - super.dispose(); - _pageController.dispose(); - } -} - -PdfExportController usePdfExportController({ - required List slides, - required int slideIndex, -}) { - return use(_PdfExportControllerHook( - slides: slides, - initialSlideIndex: slideIndex, - )); -} - -class _PdfExportControllerHook extends Hook { - const _PdfExportControllerHook({ - required this.slides, - required this.initialSlideIndex, - }); - - final List slides; - final int initialSlideIndex; - - @override - _PdfExportControllerHookState createState() => - _PdfExportControllerHookState(); -} - -class _PdfExportControllerHookState - extends HookState { - late PdfExportController controller; - - void _attachController({bool update = false}) { - if (update) { - controller.dispose(); - } - controller = PdfExportController( - slides: hook.slides, - initialIndex: hook.initialSlideIndex, - ); - - controller.addListener(() { - setState(() {}); - }); - - if (update) { - setState(() {}); - } - } - - @override - void initHook() { - super.initHook(); - - _attachController(); - } - - @override - void didUpdateHook(_PdfExportControllerHook oldHook) { - super.didUpdateHook(oldHook); - if (oldHook.slides != hook.slides || - oldHook.initialSlideIndex != hook.initialSlideIndex) { - print('update'); - _attachController(update: true); - } - } - - @override - PdfExportController build(BuildContext context) { - return controller; - } - - @override - void dispose() { - controller.dispose(); - } -} diff --git a/packages/superdeck/lib/services/reference_service.dart b/packages/superdeck/lib/services/reference_service.dart deleted file mode 100644 index 80d9e1c5..00000000 --- a/packages/superdeck/lib/services/reference_service.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'dart:async'; -import 'dart:developer'; -import 'dart:io'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; -import 'package:path/path.dart' as p; - -import '../helpers/constants.dart'; -import '../helpers/watcher.dart'; -import '../models/reference_model.dart'; - -final _assetDir = Directory(p.join('.superdeck')); -final _slideRef = File(p.join(_assetDir.path, 'slides.json')); -final _generatedDir = Directory(p.join(_assetDir.path, 'generated')); -final _markdown = File('slides.md'); - -class ReferenceService { - final _watcher = FileWatcher(_slideRef); - ReferenceService._(); - - static final instance = ReferenceService._(); - - Future loadString(String path) async { - if (kCanRunProcess) { - return File(path).readAsString(); - } else { - return rootBundle.loadString(path); - } - } - - Future loadMarkdown() async { - return _markdown.readAsString(); - } - - Future loadBytes(String path) async { - if (kCanRunProcess) { - final bytes = await File(path).readAsBytes(); - return ByteData.view(Uint8List.fromList(bytes).buffer); - } else { - return await rootBundle.load(path); - } - } - - Future saveMarkdown(String data) async { - await _markdown.writeAsString(data); - } - - File getAssetFile(String fileName) { - return File(p.join(_generatedDir.path, fileName)); - } - - void listen(VoidCallback callback) { - if (kCanRunProcess) { - if (!_watcher.isWatching) { - _watcher.start(callback); - } - } - } - - Future loadReference() async { - final slidesJson = await loadString(_slideRef.path); - try { - if (kCanRunProcess) { - return compute(SuperDeckReference.fromJson, slidesJson); - } else { - return SuperDeckReference.fromJson(slidesJson); - } - } catch (e) { - log('Error loading deck: $e'); - return const SuperDeckReference.empty(); - } - } -} diff --git a/packages/superdeck/lib/src/components/atoms/async_snapshot_widget.dart b/packages/superdeck/lib/src/components/atoms/async_snapshot_widget.dart new file mode 100644 index 00000000..7a33e1de --- /dev/null +++ b/packages/superdeck/lib/src/components/atoms/async_snapshot_widget.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +class AsyncStreamWidget extends StatelessWidget { + final Stream stream; + final Widget Function(T data) builder; + + const AsyncStreamWidget({ + super.key, + required this.stream, + required this.builder, + }); + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: stream, + builder: (context, snapshot) { + return _buildSnapshot(snapshot, builder); + }, + ); + } +} + +Widget _buildSnapshot( + AsyncSnapshot snapshot, + Widget Function(T data) builder, +) { + if (snapshot.hasData) { + return builder(snapshot.requireData); + } else if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } + + return const Center(child: CircularProgressIndicator()); +} diff --git a/packages/superdeck/lib/src/components/atoms/cache_image_widget.dart b/packages/superdeck/lib/src/components/atoms/cache_image_widget.dart new file mode 100644 index 00000000..fcd3b44d --- /dev/null +++ b/packages/superdeck/lib/src/components/atoms/cache_image_widget.dart @@ -0,0 +1,68 @@ +import 'dart:io'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:mix/mix.dart'; + +import '../../modules/common/helpers/constants.dart'; + +ImageProvider getImageProvider(Uri uri) { + switch (uri.scheme) { + case 'http': + case 'https': + return CachedNetworkImageProvider(uri.toString()); + default: + if (kCanRunProcess) { + return FileImage(File.fromUri(uri)); + } else { + return AssetImage(uri.path); + } + } +} + +class CachedImage extends StatelessWidget { + final Uri uri; + + final Size? targetSize; + + final ImageSpec spec; + + const CachedImage({ + super.key, + this.targetSize, + required this.uri, + this.spec = const ImageSpec(), + }); + + @override + Widget build(BuildContext context) { + final imageProvider = getImageProvider(uri); + + return AnimatedImageSpecWidget( + image: imageProvider, + spec: spec, + // frameBuilder: (context, child, frame, wasSynchronouslyLoaded) { + // return Container( + // padding: const EdgeInsets.all(0), + // constraints: BoxConstraints.loose(imageSize ?? size), + // decoration: BoxDecoration( + // color: Colors.green, + // image: DecorationImage( + // image: imageProvider, + // fit: widget.spec.fit, + // alignment: widget.spec.alignment ?? Alignment.center, + // ), + // ), + // ); + // }, + errorBuilder: (context, error, stackTrace) { + return Container( + color: Colors.red, + child: Center( + child: Text('Error loading image: $uri '), + ), + ); + }, + ); + } +} diff --git a/packages/superdeck/lib/src/components/atoms/linear_progresss_indicator_widget.dart b/packages/superdeck/lib/src/components/atoms/linear_progresss_indicator_widget.dart new file mode 100644 index 00000000..b21e0a53 --- /dev/null +++ b/packages/superdeck/lib/src/components/atoms/linear_progresss_indicator_widget.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; + +class AnimatedLinearProgressIndicator extends StatefulWidget { + final double progress; + + const AnimatedLinearProgressIndicator({ + super.key, + required this.progress, + }); + + @override + State createState() => + _AnimatedLinearProgressIndicatorState(); +} + +class _AnimatedLinearProgressIndicatorState + extends State + with SingleTickerProviderStateMixin { + late AnimationController _animationController; + late Animation _animation; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 100), + ); + _animation = Tween(begin: 0.0, end: widget.progress) + .animate(_animationController); + _animationController.forward(); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _animation, + builder: (context, child) { + return LinearProgressIndicator( + minHeight: 10, + borderRadius: BorderRadius.circular(10), + value: _animation.value, + ); + }, + ); + } +} diff --git a/packages/superdeck/lib/components/atoms/loading_indicator.dart b/packages/superdeck/lib/src/components/atoms/loading_indicator.dart similarity index 81% rename from packages/superdeck/lib/components/atoms/loading_indicator.dart rename to packages/superdeck/lib/src/components/atoms/loading_indicator.dart index 2a92cfb1..5494c94a 100644 --- a/packages/superdeck/lib/components/atoms/loading_indicator.dart +++ b/packages/superdeck/lib/src/components/atoms/loading_indicator.dart @@ -1,17 +1,16 @@ import 'package:flutter/material.dart'; +import 'package:superdeck/src/modules/common/helpers/extensions.dart'; class LoadingOverlay extends StatefulWidget { final bool isLoading; - final Widget child; const LoadingOverlay({ super.key, required this.isLoading, - required this.child, }); @override - _LoadingOverlayState createState() => _LoadingOverlayState(); + State createState() => _LoadingOverlayState(); } class _LoadingOverlayState extends State @@ -26,19 +25,33 @@ class _LoadingOverlayState extends State vsync: this, duration: const Duration(milliseconds: 500), ); - _opacityAnimation = - Tween(begin: 1.0, end: 0.0).animate(_animationController); + _opacityAnimation = CurvedAnimation( + parent: _animationController, + curve: Curves.easeIn, + reverseCurve: Curves.easeIn); } @override void didUpdateWidget(LoadingOverlay oldWidget) { super.didUpdateWidget(oldWidget); - if (!widget.isLoading && oldWidget.isLoading) { - _animationController.forward().then((_) { - setState(() {}); + if (oldWidget.isLoading != widget.isLoading) { + _triggerChange(widget.isLoading); + } + } + + Future _triggerChange(bool isLoading) async { + if (isLoading) { + _animationController.reverse().then((value) { + if (mounted) { + setState(() {}); + } + }); + } else { + _animationController.forward().then((value) { + if (mounted) { + setState(() {}); + } }); - } else if (widget.isLoading && !oldWidget.isLoading) { - _animationController.reverse().then((_) => setState(() {})); } } @@ -50,21 +63,19 @@ class _LoadingOverlayState extends State @override Widget build(BuildContext context) { - return Stack( - children: [ - widget.child, - if (widget.isLoading || _animationController.isAnimating) - FadeTransition( - opacity: _opacityAnimation, - child: Container( - color: Colors.black87, - child: const Center( - child: IsometricLoading(), - ), - ), + if (widget.isLoading || _animationController.isAnimating) { + return FadeTransition( + opacity: _opacityAnimation, + child: Container( + color: Colors.black87, + child: const Center( + child: IsometricLoading(), ), - ], - ); + ), + ); + } + + return const SizedBox.shrink(); } } @@ -77,7 +88,7 @@ class IsometricLoading extends StatefulWidget { final Color color; @override - _IsometricLoadingState createState() => _IsometricLoadingState(); + State createState() => _IsometricLoadingState(); } class _IsometricLoadingState extends State @@ -86,9 +97,9 @@ class _IsometricLoadingState extends State late Animation _animation; late final List _colors = [ widget.color, - widget.color.withOpacity(0.7), - widget.color.withOpacity(0.4), - widget.color.withOpacity(0.2), + widget.color.useOpacity(0.7), + widget.color.useOpacity(0.4), + widget.color.useOpacity(0.2), ]; @override @@ -158,12 +169,12 @@ class IsometricLoadingPainter extends CustomPainter { // Calculate the minimum and maximum Y-coordinates from the path data // Assuming the minimum Y-coordinate is 0 - final minY = 0.0; + const minY = 0.0; // Based on the maximum Y-coordinate value in the path data - final maxY = 226.45; + const maxY = 226.45; // Calculate the height of the paths - final pathHeight = maxY - minY; + const pathHeight = maxY - minY; // Calculate the scale factor based on the canvas size and path height final scaleFactor = canvasHeight / pathHeight; diff --git a/packages/superdeck/lib/src/components/atoms/markdown_viewer.dart b/packages/superdeck/lib/src/components/atoms/markdown_viewer.dart new file mode 100644 index 00000000..e93a7cab --- /dev/null +++ b/packages/superdeck/lib/src/components/atoms/markdown_viewer.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:markdown/markdown.dart' as md; + +import '../../../superdeck.dart'; +import '../../modules/common/markdown/markdown_element_builders.dart'; + +class MarkdownViewer extends ImplicitlyAnimatedWidget { + final String content; + final SlideSpec spec; + + const MarkdownViewer({ + super.key, + required this.content, + required this.spec, + super.duration = Durations.medium1, + super.curve = Curves.linear, + }); + + @override + ImplicitlyAnimatedWidgetState createState() => + _MarkdownViewerState(); +} + +class _MarkdownViewerState extends AnimatedWidgetBaseState { + SlideSpecTween? _styleTween; + + @override + void forEachTween(TweenVisitor visitor) { + _styleTween = visitor( + _styleTween, + widget.spec, + (dynamic value) => SlideSpecTween(begin: value), + ) as SlideSpecTween?; + } + + @override + Widget build(BuildContext context) { + final spec = _styleTween!.evaluate(animation) ?? const SlideSpec(); + return _MarkdownBuilder( + content: widget.content, + spec: spec, + ); + } +} + +class _MarkdownBuilder extends StatelessWidget { + final String content; + final SlideSpec spec; + + const _MarkdownBuilder({ + required this.content, + required this.spec, + }); + + @override + Widget build(BuildContext context) { + final builders = SpecMarkdownBuilders(spec); + return MarkdownBody( + data: content, + extensionSet: md.ExtensionSet.gitHubWeb, + builders: builders.builders, + paddingBuilders: builders.paddingBuilders, + checkboxBuilder: builders.checkboxBuilder, + bulletBuilder: builders.bulletBuilder, + blockSyntaxes: builders.blockSyntaxes, + inlineSyntaxes: builders.inlineSyntaxes, + styleSheet: spec.toStyle(), + ); + } +} diff --git a/packages/superdeck/lib/src/components/atoms/slide_thumbnail.dart b/packages/superdeck/lib/src/components/atoms/slide_thumbnail.dart new file mode 100644 index 00000000..b361dd1e --- /dev/null +++ b/packages/superdeck/lib/src/components/atoms/slide_thumbnail.dart @@ -0,0 +1,112 @@ +import 'package:flutter/material.dart'; +import 'package:mix/mix.dart'; + +import '../../modules/common/helpers/constants.dart'; +import '../../modules/deck/slide_configuration.dart'; +import '../../modules/slide_capture/thumbnail_controller.dart'; + +enum _PopMenuAction { + refreshThumbnail( + 'Refresh Thumbnail', + Icons.refresh, + ); + + const _PopMenuAction(this.label, this.icon); + + final String label; + final IconData icon; +} + +class SlideThumbnail extends StatelessWidget { + final bool selected; + final SlideConfiguration slide; + + const SlideThumbnail({ + super.key, + required this.selected, + required this.slide, + }); + + @override + Widget build(BuildContext context) { + final thumbnailController = ThumbnailController.of(context); + final asyncThumbnail = thumbnailController.get(slide, context); + return _PreviewContainer( + selected: selected, + child: Stack( + children: [ + AspectRatio( + aspectRatio: kAspectRatio, + child: asyncThumbnail.build(context), + ), + ], + ), + ); + } +} + +class _PreviewContainer extends StatelessWidget { + final Widget child; + final bool selected; + + const _PreviewContainer({ + super.key, + required this.selected, + required this.child, + }); + + @override + Widget build(BuildContext context) { + final style = Style( + $box.color.grey(), + $box.margin.all(8), + $box.border.width(2), + $box.shadow( + blurRadius: 4, + spreadRadius: 1, + ), + selected ? $box.wrap.scale(1.05) : $box.wrap.scale(1), + selected ? $box.wrap.opacity(1) : $box.wrap.opacity(0.5), + selected ? $box.border.color.cyan() : $box.border.color.transparent(), + ).animate(); + + return Box( + style: style, + child: child, + ); + } +} + +void _showOverlayMenu( + BuildContext context, + TapDownDetails details, + void Function(_PopMenuAction) handleAction, +) { + final overlay = Overlay.of(context).context.findRenderObject() as RenderBox; + + menuItem(_PopMenuAction action) => PopupMenuItem( + value: action, + onTap: () => handleAction(action), + mouseCursor: SystemMouseCursors.click, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(action.icon), + const SizedBox(width: 8), + Text(action.label), + ], + ), + ); + + showMenu( + context: context, + menuPadding: EdgeInsets.zero, + items: [ + menuItem(_PopMenuAction.refreshThumbnail), + ], + position: RelativeRect.fromSize( + details.globalPosition & Size.zero, + overlay.size, + ), + ); +} diff --git a/packages/superdeck/lib/src/components/atoms/slide_view.dart b/packages/superdeck/lib/src/components/atoms/slide_view.dart new file mode 100644 index 00000000..8648c739 --- /dev/null +++ b/packages/superdeck/lib/src/components/atoms/slide_view.dart @@ -0,0 +1,145 @@ +import 'package:flutter/material.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +import '../../modules/common/helpers/constants.dart'; +import '../../modules/deck/slide_configuration.dart'; +import '../molecules/block_widget.dart'; + +class SlideView extends StatelessWidget { + final SlideConfiguration slide; + const SlideView( + this.slide, { + super.key, + }); + + Widget _renderPreferredSize(PreferredSizeWidget? widget) { + return widget != null + ? SizedBox.fromSize( + size: widget.preferredSize, + child: widget, + ) + : const SizedBox.shrink(); + } + + Positioned _renderDebugInfo(SectionBlock section, Size slideSize) { + final label = ''' +@section | blocks: ${section.blocks.length} | ${slideSize.width.toStringAsFixed(2)} x ${slideSize.height.toStringAsFixed(2)} | align: ${section.align} | flex: ${section.flex}'''; + + const textStyle = TextStyle( + color: Colors.black, + fontSize: 12, + ); + return Positioned( + bottom: 0, + left: 0, + child: Container( + color: Colors.cyan, + padding: const EdgeInsets.all(8), + child: Text(label, style: textStyle), + ), + ); + } + + Widget _renderSections(SlideConfiguration configuration, Size slideSize) { + final sections = configuration.sections; + if (sections.isEmpty) { + return const SizedBox.shrink(); + } + final totalSectionsFlex = + sections.fold(0, (previous, section) => previous + section.flex); + + final sectionSizes = {}; + + for (var section in sections) { + final heightPercentage = section.flex / totalSectionsFlex; + final sectionSize = Size( + slideSize.width, + slideSize.height * heightPercentage, + ); + sectionSizes[section] = sectionSize; + } + + Offset currentOffset = Offset.zero; + + Map sectionOffsets = {}; + + for (var section in sectionSizes.entries) { + final sectionOffset = Offset(0, currentOffset.dy); + sectionOffsets[section.key] = sectionOffset; + currentOffset = Offset( + currentOffset.dx, + currentOffset.dy + section.value.height, + ); + } + + return Stack( + children: sections.map((section) { + final sectionOffset = sectionOffsets[section]!; + final sectionSize = sectionSizes[section]!; + return Positioned( + left: sectionOffset.dx, + top: sectionOffset.dy, + width: sectionSize.width, + height: sectionSize.height, + child: Stack( + children: [ + SectionBlockWidget( + section: section, + size: sectionSize, + ), + if (configuration.debug) _renderDebugInfo(section, sectionSize), + ], + ), + ); + }).toList(), + ); + } + + @override + Widget build(BuildContext context) { + final headerHeight = slide.parts?.header?.preferredSize.height ?? 0; + final footerHeight = slide.parts?.footer?.preferredSize.height ?? 0; + + final footerWidget = _renderPreferredSize(slide.parts?.footer); + final headerWidget = _renderPreferredSize(slide.parts?.header); + final backgroundWidget = slide.parts?.background ?? const SizedBox.shrink(); + + final slideSize = Size( + kResolution.width, + kResolution.height - headerHeight - footerHeight, + ); + + final sectionsWidget = _renderSections(slide, slideSize); + return SizedBox.fromSize( + size: kResolution, + child: Stack( + children: [ + Positioned.fill( + child: backgroundWidget, + ), + Positioned( + top: 0, + left: 0, + right: 0, + height: headerHeight, + child: headerWidget, + ), + Positioned( + top: headerHeight, + left: 0, + right: 0, + height: slideSize.height, + child: sectionsWidget, + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + height: footerHeight, + child: footerWidget, + ), + ], + ), + ); + } +} diff --git a/packages/superdeck/lib/src/components/molecules/block_provider.dart b/packages/superdeck/lib/src/components/molecules/block_provider.dart new file mode 100644 index 00000000..265937c0 --- /dev/null +++ b/packages/superdeck/lib/src/components/molecules/block_provider.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +import '../../modules/common/helpers/provider.dart'; +import '../../modules/common/styles/style_spec.dart'; + +class BlockData { + const BlockData({ + required this.spec, + required this.size, + required this.block, + }); + + final SlideSpec spec; + final Size size; + final T block; + + @override + bool operator ==(Object other) { + return other is BlockData && + other.spec == spec && + other.size == size && + other.block == block; + } + + @override + int get hashCode => spec.hashCode ^ size.hashCode ^ block.hashCode; + + static BlockData of(BuildContext context) { + final data = _tryAnyBlockData(context); + if (data == null) { + throw FlutterError('BlockData not found'); + } + return data; + } + + static BlockData? inheritedData(BuildContext context) { + return InheritedData.maybeOf>(context); + } + + static BlockData? _tryAnyBlockData(BuildContext context) { + return inheritedData(context) ?? + inheritedData(context) ?? + inheritedData(context) ?? + inheritedData(context); + } +} + +class SectionData { + const SectionData({ + required this.section, + required this.size, + }); + + final SectionBlock section; + final Size size; + + @override + bool operator ==(Object other) { + return other is SectionData && + other.section == section && + other.size == size; + } + + @override + int get hashCode => section.hashCode ^ size.hashCode; +} diff --git a/packages/superdeck/lib/src/components/molecules/block_widget.dart b/packages/superdeck/lib/src/components/molecules/block_widget.dart new file mode 100644 index 00000000..cb7c2bab --- /dev/null +++ b/packages/superdeck/lib/src/components/molecules/block_widget.dart @@ -0,0 +1,320 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:mix/mix.dart'; +import 'package:superdeck/src/components/molecules/block_provider.dart'; +import 'package:superdeck/src/modules/common/helpers/utils.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +import '../../modules/common/helpers/converters.dart'; +import '../../modules/common/helpers/provider.dart'; +import '../../modules/common/styles/style_spec.dart'; +import '../../modules/deck/slide_configuration.dart'; +import '../atoms/cache_image_widget.dart'; +import '../atoms/markdown_viewer.dart'; +import '../organisms/webview_wrapper.dart'; + +sealed class BlockWidget extends StatefulWidget { + const BlockWidget({ + super.key, + required this.block, + required this.size, + required this.configuration, + }); + + Widget build(BuildContext context, BlockData data); + + final T block; + final Size size; + final SlideConfiguration configuration; + @override + State> createState() => _BlockWidgetState(); +} + +class _BlockWidgetState extends State> { + @override + Widget build(context) { + final style = widget.configuration.style.applyVariant( + Variant(widget.block.type), + ); + + return SpecBuilder( + style: style, + builder: (context) { + final spec = SlideSpec.of(context); + + final blockOffset = calculateBlockOffset(spec.blockContainer); + + final blockData = BlockData( + block: widget.block, + spec: spec, + size: Size( + widget.size.width - blockOffset.dx, + widget.size.height - blockOffset.dy, + ), + ); + + Widget current = InheritedData( + data: blockData, + child: spec.blockContainer( + child: widget.build(context, blockData), + ), + ); + + if (widget.block.scrollable && !widget.configuration.isExporting) { + current = SingleChildScrollView( + child: current, + ); + } else { + current = Wrap( + clipBehavior: Clip.hardEdge, + children: [current], + ); + } + + final decoration = widget.configuration.debug + ? BoxDecoration( + border: Border.all( + color: Colors.cyan, + width: 2, + ), + ) + : null; + + return Container( + decoration: decoration, + child: ConstrainedBox( + constraints: BoxConstraints.loose(widget.size), + child: Stack( + children: [ + Align( + alignment: ConverterHelper.toAlignment( + blockData.block.align, + ), + child: current, + ), + ], + ), + ), + ); + }); + } +} + +class ColumnBlockWidget extends BlockWidget { + const ColumnBlockWidget({ + super.key, + required super.block, + required super.size, + required super.configuration, + }); + + @override + Widget build(context, data) { + return MarkdownViewer( + content: data.block.content, + spec: data.spec, + ); + } +} + +class ImageBlockWidget extends BlockWidget { + const ImageBlockWidget({ + super.key, + required super.block, + required super.size, + required super.configuration, + }); + + @override + Widget build(context, data) { + final alignment = data.block.align ?? ContentAlignment.center; + final imageFit = data.block.fit ?? ImageFit.cover; + final spec = data.spec; + + return CachedImage( + uri: Uri.parse(data.block.asset.fileName), + spec: spec.image.copyWith( + fit: ConverterHelper.toBoxFit(imageFit), + alignment: ConverterHelper.toAlignment(alignment), + ), + ); + } +} + +class WidgetBlockWidget extends BlockWidget { + const WidgetBlockWidget({ + super.key, + required super.block, + required super.size, + required super.configuration, + }); + + @override + Widget build(context, data) { + final slide = SlideConfiguration.of(context); + + final widgetBuilder = slide.getWidget(data.block.name); + + if (widgetBuilder == null) { + return Container( + color: Colors.red, + child: Center( + child: Text('Widget not found: ${data.block.name}'), + ), + ); + } + + return Builder( + builder: (context) { + try { + return SizedBox( + height: data.size.height, + child: widgetBuilder(data.block.args), + ); + } catch (e) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.red, + border: Border.all( + color: Colors.red, + width: 2, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Center( + child: Text(''' +Error building widget: ${data.block.name} + +${e.toString()}'''), + ), + ); + } + }, + ); + } +} + +class DartPadBlockWidget extends BlockWidget { + const DartPadBlockWidget({ + super.key, + required super.block, + required super.size, + required super.configuration, + }); + + @override + Widget build(context, data) { + if (kDebugMode) { + return SizedBox( + height: data.size.height, + width: data.size.width, + child: Container( + color: Colors.blue, + child: const Center( + child: Text('DartPad not available in debug mode'), + ), + ), + ); + } + + return WebViewWrapper( + size: data.size, + url: data.block.getDartPadUrl(), + ); + } +} + +class SectionBlockWidget extends StatelessWidget { + const SectionBlockWidget({ + super.key, + required this.section, + required this.size, + }); + + final SectionBlock section; + final Size size; + + Positioned _renderDebugInfo(Block block, Size size) { + const textStyle = TextStyle( + color: Colors.black, + fontSize: 12, + ); + final label = ''' +@${block.type} +${size.width.toStringAsFixed(2)} x ${size.height.toStringAsFixed(2)}'''; + + return Positioned( + top: 0, + right: 0, + child: Container( + color: Colors.cyan, + padding: const EdgeInsets.all(8), + child: Text(label, style: textStyle), + ), + ); + } + + @override + Widget build(context) { + final blockLeftOffset = List.filled(section.blocks.length, 0.0); + double cumulativeLeftOffset = 0; + final widthPerFlex = size.width / section.totalBlockFlex; + // get index + for (var index = 0; index < section.blocks.length; index++) { + final block = section.blocks[index]; + final blockWidth = widthPerFlex * block.flex; + blockLeftOffset[index] = cumulativeLeftOffset; + cumulativeLeftOffset = cumulativeLeftOffset + blockWidth; + } + + final configuration = SlideConfiguration.of(context); + + return Stack( + children: section.blocks.mapIndexed((index, block) { + final widthPercentage = block.flex / section.totalBlockFlex; + + final blockSize = Size( + size.width * widthPercentage, + size.height, + ); + + return Positioned( + left: blockLeftOffset[index], + top: 0, + width: blockSize.width, + height: blockSize.height, + child: Stack( + children: [ + switch (block) { + ImageBlock b => ImageBlockWidget( + block: b, + size: blockSize, + configuration: configuration, + ), + WidgetBlock b => WidgetBlockWidget( + block: b, + size: blockSize, + configuration: configuration, + ), + DartPadBlock b => DartPadBlockWidget( + block: b, + size: blockSize, + configuration: configuration, + ), + ColumnBlock b => ColumnBlockWidget( + block: b, + size: blockSize, + configuration: configuration, + ), + _ => const SizedBox.shrink(), + }, + if (configuration.debug) _renderDebugInfo(block, blockSize), + ], + ), + ); + }).toList(), + ); + } +} diff --git a/packages/superdeck/lib/src/components/molecules/bottom_bar.dart b/packages/superdeck/lib/src/components/molecules/bottom_bar.dart new file mode 100644 index 00000000..163cd937 --- /dev/null +++ b/packages/superdeck/lib/src/components/molecules/bottom_bar.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:mix/mix.dart'; +import 'package:superdeck/src/modules/slide_capture/pdf_export_screen.dart'; + +import '../../modules/deck/deck_controller.dart'; +import '../../modules/navigation/navigation_controller.dart'; +import '../../modules/slide_capture/thumbnail_controller.dart'; + +class DeckBottomBar extends StatelessWidget { + const DeckBottomBar({ + super.key, + }); + + @override + Widget build(BuildContext context) { + final navigation = NavigationController.of(context); + final thumbnail = ThumbnailController.of(context); + final deck = DeckController.of(context); + final currentPage = navigation.currentSlide.slideIndex + 1; + final totalPages = navigation.totalSlides; + + return _BottomBarContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // view notes + IconButton( + onPressed: navigation.toggleNotes, + icon: navigation.isNotesOpen + ? const Icon(Icons.comment) + : const Icon(Icons.comments_disabled), + ), + const SizedBox(width: 16), + IconButton( + onPressed: () => PdfExportDialogScreen.show(context), + icon: const Icon(Icons.save), + ), + const SizedBox(width: 16), + IconButton( + onPressed: () => thumbnail.generateThumbnails( + deck.slides, + context, + force: true, + ), + icon: const Icon(Icons.replay_circle_filled_rounded), + ), + const Spacer(), + IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: navigation.previousSlide, + ), + + IconButton( + icon: const Icon(Icons.arrow_forward), + onPressed: navigation.nextSlide, + ), + const Spacer(), + Text( + '$currentPage of $totalPages', + style: const TextStyle(color: Colors.white), + ), + const SizedBox(width: 16), + IconButton( + onPressed: navigation.closeMenu, + icon: const Icon(Icons.close), + ), + ], + ), + ); + } +} + +class _BottomBarContainer extends StatelessWidget { + const _BottomBarContainer({ + required this.child, + }); + + final Widget child; + + @override + Widget build(BuildContext context) { + final style = Style( + $box.height(60), + $box.borderRadius(16), + $box.color(const Color.fromARGB(255, 17, 17, 17)), + $box.padding(10, 20), + $box.margin(12), + ); + + return Box( + style: style, + child: child, + ); + } +} diff --git a/packages/superdeck/lib/components/molecules/scaled_app.dart b/packages/superdeck/lib/src/components/molecules/scaled_app.dart similarity index 73% rename from packages/superdeck/lib/components/molecules/scaled_app.dart rename to packages/superdeck/lib/src/components/molecules/scaled_app.dart index a4d4242b..bb280e0a 100644 --- a/packages/superdeck/lib/components/molecules/scaled_app.dart +++ b/packages/superdeck/lib/src/components/molecules/scaled_app.dart @@ -1,18 +1,18 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/widgets.dart'; -import '../../helpers/constants.dart'; +import '../../modules/common/helpers/constants.dart'; class ScaledWidget extends StatelessWidget { final Widget child; + final Size targetSize; const ScaledWidget({ super.key, required this.child, + required this.targetSize, }); - static double of(BuildContext context) => ScaledWidgetProvider.of(context); - @override Widget build(BuildContext context) { return LayoutBuilder( @@ -26,8 +26,8 @@ class ScaledWidget extends StatelessWidget { width = height * kAspectRatio; } - final scaleWidth = width / kResolution.width; - final scaleHeight = height / kResolution.height; + final scaleWidth = width / targetSize.width; + final scaleHeight = height / targetSize.height; return SizedBox( width: width, @@ -38,15 +38,15 @@ class ScaledWidget extends StatelessWidget { children: [ const Center(), Positioned( - top: (kResolution.height - height) / -2, - left: (kResolution.width - width) / -2, - width: kResolution.width.toDouble(), - height: kResolution.height.toDouble(), + top: (targetSize.height - height) / -2, + left: (targetSize.width - width) / -2, + width: targetSize.width.toDouble(), + height: targetSize.height.toDouble(), child: Transform.scale( scaleY: scaleHeight, scaleX: scaleWidth, child: MediaQuery( - data: MediaQuery.of(context).copyWith(size: kResolution), + data: MediaQuery.of(context).copyWith(size: targetSize), child: ScaledWidgetProvider( scale: scaleWidth, child: child, @@ -73,12 +73,6 @@ class ScaledWidgetProvider extends InheritedWidget { final double scale; - static double of(BuildContext context) { - return context - .dependOnInheritedWidgetOfExactType()! - .scale; - } - @override bool updateShouldNotify(ScaledWidgetProvider oldWidget) { return oldWidget.scale != scale; diff --git a/packages/superdeck/lib/src/components/molecules/slide_screen.dart b/packages/superdeck/lib/src/components/molecules/slide_screen.dart new file mode 100644 index 00000000..e0f5a212 --- /dev/null +++ b/packages/superdeck/lib/src/components/molecules/slide_screen.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:superdeck/src/modules/common/helpers/extensions.dart'; + +import '../../modules/common/helpers/provider.dart'; +import '../../modules/deck/slide_configuration.dart'; +import '../atoms/slide_view.dart'; + +class SlideScreen extends StatelessWidget { + const SlideScreen( + this.configuration, { + super.key, + }); + + final SlideConfiguration configuration; + + @override + Widget build(BuildContext context) { + return Center( + child: Container( + decoration: BoxDecoration( + color: const Color.fromARGB(255, 29, 29, 29), + boxShadow: [ + BoxShadow( + color: Colors.black.useOpacity(0.3), + blurRadius: 6, + spreadRadius: 3, + ), + ], + ), + child: InheritedData( + data: configuration, + child: SlideView(configuration), + ), + ), + ); + } +} diff --git a/packages/superdeck/lib/src/components/organisms/app_shell.dart b/packages/superdeck/lib/src/components/organisms/app_shell.dart new file mode 100644 index 00000000..35e75841 --- /dev/null +++ b/packages/superdeck/lib/src/components/organisms/app_shell.dart @@ -0,0 +1,234 @@ +import 'package:flutter/material.dart'; +import 'package:superdeck/src/components/atoms/slide_thumbnail.dart'; +import 'package:superdeck/src/components/molecules/scaled_app.dart'; +import 'package:superdeck/src/components/organisms/comments_panel.dart'; +import 'package:superdeck/src/components/organisms/thumbnail_panel.dart'; +import 'package:superdeck/src/modules/common/helpers/constants.dart'; +import 'package:superdeck/src/modules/common/helpers/utils.dart'; +import 'package:superdeck/src/modules/slide_capture/thumbnail_controller.dart'; + +import '../../modules/deck/deck_controller.dart'; +import '../../modules/navigation/navigation_controller.dart'; +import '../molecules/bottom_bar.dart'; +import 'keyboard_shortcuts.dart'; + +/// High-level app shell that toggles between +/// small layout (bottom panel) or regular layout (side panel). +class AppShell extends StatelessWidget { + const AppShell({ + super.key, + required this.child, + }); + + final Widget child; + + @override + Widget build(BuildContext context) { + final navigation = NavigationController.of(context); + + return KeyboardShortcuts( + child: SplitView( + isOpen: navigation.isMenuOpen, + isSmallLayout: context.isSmall, + child: child, + ), + ); + } +} + +/// A widget that can lay out the "panel" (thumbnails and possibly notes) +/// either at the bottom (vertical layout) or on the side (horizontal layout). +class SplitView extends StatefulWidget { + const SplitView({ + super.key, + required this.isOpen, + required this.child, + this.isSmallLayout = false, + }); + + final Widget child; + final bool isOpen; + final bool isSmallLayout; + + @override + State createState() => _SplitViewState(); +} + +class _SplitViewState extends State + with SingleTickerProviderStateMixin { + static const _animationDuration = Duration(milliseconds: 200); + late final AnimationController _animationController; + late final Animation _curvedAnimation; + + @override + void initState() { + super.initState(); + + _animationController = AnimationController( + duration: _animationDuration, + vsync: this, + value: widget.isOpen ? 1.0 : 0.0, + ); + _curvedAnimation = CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + ); + + WidgetsBinding.instance.addPostFrameCallback((_) { + final deckController = DeckController.of(context); + ThumbnailController.of(context).generateThumbnails( + deckController.slides, + context, + ); + }); + } + + @override + void didUpdateWidget(covariant SplitView oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.isOpen != widget.isOpen) { + if (widget.isOpen) { + _animationController.forward(); + } else { + _animationController.reverse(); + } + } + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + // Build the panel content (thumbnails + optional comments). + Widget _buildPanel(BuildContext context) { + final navigation = NavigationController.of(context); + final deckController = DeckController.of(context); + + final currentSlide = navigation.currentSlide; + final isNotesOpen = navigation.isNotesOpen; + + /// Common content for thumbnails + final thumbnailPanel = ThumbnailPanel( + scrollDirection: widget.isSmallLayout ? Axis.horizontal : Axis.vertical, + onItemTap: navigation.goToSlide, + activeIndex: currentSlide.slideIndex, + itemBuilder: (index, selected) { + return SlideThumbnail( + selected: selected, + slide: deckController.slides[index], + ); + }, + itemCount: deckController.slides.length, + ); + + /// Comments panel (shown only if notes are open) + final commentsPanel = isNotesOpen + ? CommentsPanel(comments: currentSlide.comments) + : const SizedBox(); + + // For small layout, show the panel horizontally (i.e., row) if it's at the BOTTOM, + // or for a big layout, we might do a column if it's on the SIDE. + // This is somewhat reversed based on your preference, so adjust as needed. + if (widget.isSmallLayout) { + // Panel at bottom => put them side-by-side in a Row + return Row( + children: [ + !isNotesOpen + ? Expanded(child: thumbnailPanel) + : Expanded(child: commentsPanel), + ], + ); + } else { + // Panel on the side => put them in a Column + return Column( + children: [ + Expanded( + flex: 3, + child: thumbnailPanel, + ), + if (isNotesOpen) + Expanded( + flex: 1, + child: commentsPanel, + ), + ], + ); + } + } + + @override + Widget build(BuildContext context) { + final navigation = NavigationController.of(context); + final isMenuOpen = navigation.isMenuOpen; + + // For small layout, the panel is typically at the bottom (vertical), + // so we place it in a Column below the main content. + // For regular layout, place it on the left in a Row. + return Scaffold( + backgroundColor: const Color.fromARGB(255, 9, 9, 9), + floatingActionButtonLocation: FloatingActionButtonLocation.miniEndFloat, + floatingActionButton: !isMenuOpen + ? IconButton( + icon: const Icon(Icons.menu), + onPressed: navigation.openMenu, + ) + : null, + + // Only show bottom bar on small layout (uncomment if needed): + bottomNavigationBar: SizeTransition( + axis: Axis.vertical, + sizeFactor: _curvedAnimation, + child: const DeckBottomBar(), + ), + + // Body changes layout based on [isSmallLayout]. + body: widget.isSmallLayout + ? Column( + children: [ + // Main slide content + Expanded( + child: Center( + child: ScaledWidget( + targetSize: kResolution, + child: widget.child, + ), + ), + ), + // Animated bottom panel + SizeTransition( + axis: Axis.vertical, + sizeFactor: _curvedAnimation, + child: SizedBox( + height: 200, + child: _buildPanel(context), + ), + ), + ], + ) + : Row( + children: [ + // Animated side panel + SizeTransition( + axis: Axis.horizontal, + sizeFactor: _curvedAnimation, + child: SizedBox( + width: 300, + child: _buildPanel(context), + ), + ), + // Main slide content + Expanded( + child: Center( + child: ScaledWidget( + targetSize: kResolution, + child: widget.child, + ), + ), + ), + ], + ), + ); + } +} diff --git a/packages/superdeck/lib/src/components/organisms/comments_panel.dart b/packages/superdeck/lib/src/components/organisms/comments_panel.dart new file mode 100644 index 00000000..f6f9b533 --- /dev/null +++ b/packages/superdeck/lib/src/components/organisms/comments_panel.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:mix/mix.dart'; + +class CommentsPanel extends StatelessWidget { + const CommentsPanel({ + super.key, + required this.comments, + }); + + final List comments; + + @override + Widget build(BuildContext context) { + final boxStyle = Style( + $box.color(const Color.fromARGB(255, 35, 35, 35)), + $box.margin.only(top: 10, left: 10, right: 10, bottom: 0), + $box.borderRadius(10), + ); + + final flexStyle = Style( + $flex.crossAxisAlignment.stretch(), + $flex.gap(10), + $box.padding(10), + ); + return Box( + style: boxStyle, + child: SingleChildScrollView( + child: VBox( + style: flexStyle, + children: comments.map(Text.new).toList(), + ), + ), + ); + } +} diff --git a/packages/superdeck/lib/src/components/organisms/keyboard_shortcuts.dart b/packages/superdeck/lib/src/components/organisms/keyboard_shortcuts.dart new file mode 100644 index 00000000..00006027 --- /dev/null +++ b/packages/superdeck/lib/src/components/organisms/keyboard_shortcuts.dart @@ -0,0 +1,67 @@ +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:superdeck/src/modules/navigation/navigation_controller.dart'; + +class NextSlideIntent extends Intent {} + +class PreviousSlideIntent extends Intent {} + +// Define your actions: +class NextSlideAction extends Action { + final NavigationController navigation; + NextSlideAction({required this.navigation}); + + @override + Object? invoke(covariant NextSlideIntent intent) { + navigation.nextSlide(); + return null; + } +} + +class PreviousSlideAction extends Action { + final NavigationController navigation; + PreviousSlideAction({required this.navigation}); + + @override + Object? invoke(covariant PreviousSlideIntent intent) { + navigation.previousSlide(); + return null; + } +} + +class KeyboardShortcuts extends StatelessWidget { + const KeyboardShortcuts({ + super.key, + required this.child, + }); + + final Widget child; + + @override + Widget build(BuildContext context) { + final navigation = NavigationController.of(context); + return Shortcuts( + shortcuts: { + // Using a LogicalKeySet allows you to define multiple keys. + LogicalKeySet(LogicalKeyboardKey.arrowRight, LogicalKeyboardKey.meta): + NextSlideIntent(), + LogicalKeySet(LogicalKeyboardKey.arrowDown, LogicalKeyboardKey.meta): + NextSlideIntent(), + LogicalKeySet(LogicalKeyboardKey.space, LogicalKeyboardKey.meta): + NextSlideIntent(), + LogicalKeySet(LogicalKeyboardKey.arrowLeft, LogicalKeyboardKey.meta): + PreviousSlideIntent(), + LogicalKeySet(LogicalKeyboardKey.arrowUp, LogicalKeyboardKey.meta): + PreviousSlideIntent(), + }, + child: Actions(actions: >{ + NextSlideIntent: NextSlideAction( + navigation: navigation, + ), + PreviousSlideIntent: PreviousSlideAction( + navigation: navigation, + ), + }, child: child), + ); + } +} diff --git a/packages/superdeck/lib/src/components/organisms/thumbnail_panel.dart b/packages/superdeck/lib/src/components/organisms/thumbnail_panel.dart new file mode 100644 index 00000000..21fc452a --- /dev/null +++ b/packages/superdeck/lib/src/components/organisms/thumbnail_panel.dart @@ -0,0 +1,128 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; + +class ThumbnailPanel extends StatefulWidget { + const ThumbnailPanel({ + super.key, + required this.itemBuilder, + required this.itemCount, + required this.activeIndex, + required this.onItemTap, + required this.scrollDirection, + }); + + final int activeIndex; + final Axis scrollDirection; + + final Widget Function(int index, bool selected) itemBuilder; + final void Function(int index) onItemTap; + final int itemCount; + + @override + State createState() => _ThumbnailPanelState(); +} + +class _ThumbnailPanelState extends State { + final _duration = const Duration(milliseconds: 300); + late final ItemScrollController _itemScrollController; + late final ItemPositionsListener _itemPositionsListener; + late List _visibleItems; + + @override + void initState() { + super.initState(); + _itemScrollController = ItemScrollController(); + _itemPositionsListener = ItemPositionsListener.create(); + _visibleItems = []; + _itemPositionsListener.itemPositions.addListener(_listener); + } + + void _listener() { + final newVisibleItems = _itemPositionsListener.itemPositions.value.toList(); + + if (listEquals(newVisibleItems, _visibleItems)) return; + + setState(() { + _visibleItems = newVisibleItems; + }); + } + + @override + void didUpdateWidget(ThumbnailPanel oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.activeIndex != widget.activeIndex) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _scrollToActiveSlide(widget.activeIndex); + }); + } + } + + void _scrollToActiveSlide(int index) { + final visibleItems = _visibleItems; + double alignment; + + if (visibleItems.isEmpty) return; + + final visibleItem = visibleItems.firstWhereOrNull( + (e) => e.index == index, + ); + + if (visibleItem == null) { + final isBeginning = visibleItems.first.index > index; + + alignment = isBeginning ? 0 : 0.7; + } else { + if (visibleItem.itemTrailingEdge > 1) { + final totalSpace = + visibleItem.itemTrailingEdge - visibleItem.itemLeadingEdge; + alignment = 1 - totalSpace; + } else if (visibleItem.itemLeadingEdge < 0) { + alignment = 0; + } else { + alignment = visibleItem.itemLeadingEdge; + } + } + + _itemScrollController.scrollTo( + index: index, + alignment: alignment, + duration: _duration, + curve: _curve, + ); + } + + @override + void dispose() { + _itemPositionsListener.itemPositions.removeListener(_listener); + super.dispose(); + } + + final _curve = Curves.easeInOutCubic; + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.black, + child: ScrollablePositionedList.builder( + scrollDirection: widget.scrollDirection, + itemCount: widget.itemCount, + itemPositionsListener: _itemPositionsListener, + itemScrollController: _itemScrollController, + padding: const EdgeInsets.all(20), + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 10, + ), + child: GestureDetector( + onTap: () => widget.onItemTap(index), + child: widget.itemBuilder(index, index == widget.activeIndex), + ), + ); + }), + ); + } +} diff --git a/packages/superdeck/lib/src/components/organisms/webview_wrapper.dart b/packages/superdeck/lib/src/components/organisms/webview_wrapper.dart new file mode 100644 index 00000000..ab241407 --- /dev/null +++ b/packages/superdeck/lib/src/components/organisms/webview_wrapper.dart @@ -0,0 +1,132 @@ +import 'package:flutter/material.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +class WebViewWrapper extends StatefulWidget { + final String url; + final Size size; + + WebViewWrapper({ + super.key, + required this.url, + required this.size, + }); + + final _uniqueKey = GlobalKey(); + + @override + State createState() => _WebViewWrapperState(); +} + +class _WebViewWrapperState extends State + with AutomaticKeepAliveClientMixin { + late WebViewController _controller; + bool _hide = true; + + @override + bool get wantKeepAlive => true; + + @override + void initState() { + super.initState(); + // (widget._uniqueKey).currentState?.dispose(); + _controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate( + onProgress: (int progress) {}, + onPageStarted: (String url) {}, + onPageFinished: (String url) { + _showDartPad(); + }, + onHttpError: (HttpResponseError error) {}, + onWebResourceError: (WebResourceError error) {}, + onNavigationRequest: (NavigationRequest request) { + return NavigationDecision.navigate; + }, + ), + ); + + _loadDartPad(); + } + + Future _loadDartPad() async { + await _controller.loadRequest(Uri.parse(widget.url)); + } + + Future _showDartPad() async { + await Future.delayed(const Duration(milliseconds: 500)); + setState(() { + _hide = false; + }); + } + + Future _reloadDartPad() async { + setState(() { + _hide = true; + }); + await Future.delayed(const Duration(milliseconds: 150)); + await _controller.reload(); + } + + Future executeInIframe(String code) { + return _controller.runJavaScript(code); + } + + Future clearDartPadEditor() { + _controller.reload(); + return executeInIframe(''' + var editor = document.querySelector('.CodeMirror')?.CodeMirror; + if (editor) { + editor.setValue(''); + editor.setCursor({line: 0, ch: 0}); + editor.focus(); + console.log('DartPad editor cleared!'); + } + '''); + } + + // Function to set content in the DartPad editor + Future setDartPadEditorContent(String content) { + return executeInIframe(''' + var editor = document.querySelector('.CodeMirror')?.CodeMirror; + if(editor){ + editor.setValue($content); + editor.setCursor(editor.lineCount(), 0); + editor.focus(); + console.log('DartPad editor content set!'); + } + '''); + } + + @override + Widget build(BuildContext context) { + super.build(context); + + return SizedBox( + width: widget.size.width, + height: widget.size.height, + child: Stack( + children: [ + AnimatedOpacity( + opacity: _hide ? 0 : 1, + duration: const Duration(milliseconds: 150), + child: WebViewWidget(controller: _controller), + ), + Row( + children: [ + IconButton( + onPressed: _reloadDartPad, + icon: const Icon(Icons.refresh), + ), + // add button that clears the webview by running javascript + IconButton( + onPressed: clearDartPadEditor, + icon: const Icon(Icons.clear), + ), + ], + ) + ], + ), + ); + } +} diff --git a/packages/superdeck/lib/src/components/parts/background.dart b/packages/superdeck/lib/src/components/parts/background.dart new file mode 100644 index 00000000..f8cf84d2 --- /dev/null +++ b/packages/superdeck/lib/src/components/parts/background.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:superdeck/superdeck.dart'; + +// Color _colorFromHex(String hexString) { +// hexString = hexString.trim(); +// if (hexString.isEmpty) { +// return Colors.black; // Default color if null or empty +// } +// hexString = hexString.replaceAll(RegExp(r'[^a-fA-F0-9]'), ''); +// hexString = hexString.replaceAll('#', ''); +// if (hexString.length == 6) { +// hexString = 'FF$hexString'; // Add opacity if not provided +// } +// return Color(int.parse(hexString, radix: 16)); +// } + +class BackgroundPart extends StatelessWidget { + const BackgroundPart({ + super.key, + }); + + @override + Widget build(BuildContext context) { + final configuration = SlideConfiguration.of(context); + + return const SizedBox.shrink(); + } +} diff --git a/packages/superdeck/lib/src/components/parts/footer.dart b/packages/superdeck/lib/src/components/parts/footer.dart new file mode 100644 index 00000000..6686fb18 --- /dev/null +++ b/packages/superdeck/lib/src/components/parts/footer.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +class FooterPart extends StatelessWidget implements PreferredSizeWidget { + const FooterPart({ + super.key, + }); + + @override + Size get preferredSize => const Size.fromHeight(50); + + @override + Widget build(context) { + return const Padding( + padding: EdgeInsets.symmetric(horizontal: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text('SUPERDECK'), + ], + ), + ); + } +} diff --git a/packages/superdeck/lib/src/components/parts/header.dart b/packages/superdeck/lib/src/components/parts/header.dart new file mode 100644 index 00000000..04c75b08 --- /dev/null +++ b/packages/superdeck/lib/src/components/parts/header.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:superdeck/superdeck.dart'; + +class HeaderPart extends StatelessWidget implements PreferredSizeWidget { + const HeaderPart({ + super.key, + }); + + @override + Size get preferredSize => const Size.fromHeight(50); + + @override + Widget build(context) { + final slide = SlideConfiguration.of(context); + + final index = slide.slideIndex; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text(slide.options.title ?? 'Generative UI with Flutter'), + const SizedBox(width: 20), + Text('${index + 1}'), + ], + ), + ); + } +} diff --git a/packages/superdeck/lib/src/components/parts/slide_parts.dart b/packages/superdeck/lib/src/components/parts/slide_parts.dart new file mode 100644 index 00000000..cb2595fc --- /dev/null +++ b/packages/superdeck/lib/src/components/parts/slide_parts.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:superdeck/src/components/parts/background.dart'; +import 'package:superdeck/src/components/parts/footer.dart'; +import 'package:superdeck/src/components/parts/header.dart'; + +class SlideParts { + const SlideParts({ + this.header, + this.footer, + this.background, + }); + + final PreferredSizeWidget? header; + final PreferredSizeWidget? footer; + final Widget? background; + + static const defaultParts = SlideParts( + header: HeaderPart(), + footer: FooterPart(), + background: BackgroundPart(), + ); +} diff --git a/packages/superdeck/lib/src/components/superdeck_app.dart b/packages/superdeck/lib/src/components/superdeck_app.dart new file mode 100644 index 00000000..b0adeabf --- /dev/null +++ b/packages/superdeck/lib/src/components/superdeck_app.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; + +import '../modules/common/helpers/theme.dart'; +import '../modules/common/initializer_provider.dart'; +import '../modules/deck/deck_options.dart'; +import '../modules/deck/deck_provider.dart'; +import '../modules/navigation/navigation_provider.dart'; +import 'organisms/app_shell.dart'; + +class SuperDeckApp extends StatelessWidget { + const SuperDeckApp({ + super.key, + required this.options, + }); + + final DeckOptions options; + + static Future initialize() async { + await initializeDependencies(); + } + + @override + Widget build(BuildContext context) { + return DeckControllerBuilder( + options: options, + builder: (controller) { + return NavigationProviderBuilder( + deckController: controller, + builder: (router) { + return MaterialApp.router( + debugShowCheckedModeBanner: false, + title: 'Superdeck', + routerConfig: router, + builder: (context, child) { + return AppShell(child: child!); + }, + theme: theme, + ); + }, + ); + }, + ); + } +} diff --git a/packages/superdeck/lib/helpers/constants.dart b/packages/superdeck/lib/src/modules/common/helpers/constants.dart similarity index 100% rename from packages/superdeck/lib/helpers/constants.dart rename to packages/superdeck/lib/src/modules/common/helpers/constants.dart diff --git a/packages/superdeck/lib/src/modules/common/helpers/converters.dart b/packages/superdeck/lib/src/modules/common/helpers/converters.dart new file mode 100644 index 00000000..98b43a5d --- /dev/null +++ b/packages/superdeck/lib/src/modules/common/helpers/converters.dart @@ -0,0 +1,77 @@ +import 'package:flutter/widgets.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +class ConverterHelper { + static BoxFit toBoxFit(ImageFit fit) { + return switch (fit) { + ImageFit.fill => BoxFit.fill, + ImageFit.contain => BoxFit.contain, + ImageFit.cover => BoxFit.cover, + ImageFit.fitWidth => BoxFit.fitWidth, + ImageFit.fitHeight => BoxFit.fitHeight, + ImageFit.none => BoxFit.none, + ImageFit.scaleDown => BoxFit.scaleDown, + }; + } + + static Alignment toAlignment(ContentAlignment? alignment) { + if (alignment == null) { + return Alignment.center; + } + return switch (alignment) { + ContentAlignment.topLeft => Alignment.topLeft, + ContentAlignment.topCenter => Alignment.topCenter, + ContentAlignment.topRight => Alignment.topRight, + ContentAlignment.centerLeft => Alignment.centerLeft, + ContentAlignment.center => Alignment.center, + ContentAlignment.centerRight => Alignment.centerRight, + ContentAlignment.bottomLeft => Alignment.bottomLeft, + ContentAlignment.bottomCenter => Alignment.bottomCenter, + ContentAlignment.bottomRight => Alignment.bottomRight, + }; + } + + static (MainAxisAlignment mainAxis, CrossAxisAlignment crossAxis) + toFlexAlignment(Axis axis, ContentAlignment alignment) { + final isHorizontal = axis == Axis.horizontal; + final (mainStart, mainCenter, mainEnd) = isHorizontal + ? ( + MainAxisAlignment.start, + MainAxisAlignment.center, + MainAxisAlignment.end + ) + : ( + MainAxisAlignment.end, + MainAxisAlignment.center, + MainAxisAlignment.start + ); + + final (crossStart, crossCenter, crossEnd) = ( + CrossAxisAlignment.start, + CrossAxisAlignment.center, + CrossAxisAlignment.end + ); + + return switch (alignment) { + ContentAlignment.topLeft => (mainStart, crossStart), + ContentAlignment.topCenter => (mainStart, crossCenter), + ContentAlignment.topRight => (mainStart, crossEnd), + ContentAlignment.centerLeft => (mainCenter, crossStart), + ContentAlignment.center => (mainCenter, crossCenter), + ContentAlignment.centerRight => (mainCenter, crossEnd), + ContentAlignment.bottomLeft => (mainEnd, crossStart), + ContentAlignment.bottomCenter => (mainEnd, crossCenter), + ContentAlignment.bottomRight => (mainEnd, crossEnd), + }; + } + + static (MainAxisAlignment mainAxis, CrossAxisAlignment crossAxis) + toRowAlignment(ContentAlignment alignment) { + return toFlexAlignment(Axis.horizontal, alignment); + } + + static (MainAxisAlignment mainAxis, CrossAxisAlignment crossAxis) + toColumnAlignment(ContentAlignment alignment) { + return toFlexAlignment(Axis.vertical, alignment); + } +} diff --git a/packages/superdeck/lib/helpers/deep_merge.dart b/packages/superdeck/lib/src/modules/common/helpers/deep_merge.dart similarity index 100% rename from packages/superdeck/lib/helpers/deep_merge.dart rename to packages/superdeck/lib/src/modules/common/helpers/deep_merge.dart diff --git a/packages/superdeck/lib/src/modules/common/helpers/extensions.dart b/packages/superdeck/lib/src/modules/common/helpers/extensions.dart new file mode 100644 index 00000000..55f11081 --- /dev/null +++ b/packages/superdeck/lib/src/modules/common/helpers/extensions.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; + +extension BuildContextX on BuildContext { + ThemeData get theme => Theme.of(this); + TextTheme get textTheme => theme.textTheme; + ColorScheme get colorScheme => theme.colorScheme; +} + +extension ColorX on Color { + Color useOpacity(double opacity) => withAlpha((255.0 * opacity).round()); +} diff --git a/packages/superdeck/lib/src/modules/common/helpers/measure_size.dart b/packages/superdeck/lib/src/modules/common/helpers/measure_size.dart new file mode 100644 index 00000000..ed9ffb1a --- /dev/null +++ b/packages/superdeck/lib/src/modules/common/helpers/measure_size.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; + +typedef OnWidgetSizeChange = void Function(Size size); + +class MeasureSize extends StatefulWidget { + final Widget child; + final OnWidgetSizeChange onChange; + + const MeasureSize({ + super.key, + required this.onChange, + required this.child, + }); + + @override + State createState() => _MeasureSizeState(); +} + +class _MeasureSizeState extends State { + late final GlobalKey _widgetKey; + Size? _size; + + @override + void initState() { + super.initState(); + _widgetKey = GlobalKey(debugLabel: 'MeasureSize'); + } + + @override + Widget build(BuildContext context) { + WidgetsBinding.instance.addPostFrameCallback(_afterLayout); + return Container( + key: _widgetKey, + child: widget.child, + ); + } + + void _afterLayout(Duration timeStamp) { + final context = _widgetKey.currentContext; + if (context == null) return; + final renderBox = context.findRenderObject() as RenderBox?; + if (_size == renderBox?.size) return; + if (renderBox != null && renderBox.hasSize) { + _size = renderBox.size; + widget.onChange(renderBox.size); + } + } +} diff --git a/packages/superdeck/lib/src/modules/common/helpers/provider.dart b/packages/superdeck/lib/src/modules/common/helpers/provider.dart new file mode 100644 index 00000000..4d7a6569 --- /dev/null +++ b/packages/superdeck/lib/src/modules/common/helpers/provider.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; + +class InheritedData extends InheritedWidget { + const InheritedData({ + super.key, + required super.child, + required this.data, + }); + + final T data; + + @override + bool updateShouldNotify(covariant InheritedData oldWidget) { + return oldWidget.data != data; + } + + static T? maybeOf(BuildContext context) { + final provider = + context.dependOnInheritedWidgetOfExactType>(); + return provider?.data; + } + + static T of(BuildContext context) { + final provider = + context.dependOnInheritedWidgetOfExactType>(); + if (provider == null) { + throw FlutterError('Provider of type $T not found in the widget tree'); + } + return provider.data; + } +} + +class InheritedNotifierData extends InheritedNotifier { + const InheritedNotifierData({ + super.key, + required super.child, + required T data, + }) : super(notifier: data); + + static T? maybeOf(BuildContext context) { + final provider = + context.dependOnInheritedWidgetOfExactType>(); + return provider?.notifier; + } + + static T of(BuildContext context) { + final notifier = maybeOf(context); + if (notifier == null) { + throw FlutterError('Provider of type $T not found in the widget tree'); + } + return notifier; + } +} diff --git a/packages/superdeck/lib/src/modules/common/helpers/root_bundle_data_store.dart b/packages/superdeck/lib/src/modules/common/helpers/root_bundle_data_store.dart new file mode 100644 index 00000000..cc8bed50 --- /dev/null +++ b/packages/superdeck/lib/src/modules/common/helpers/root_bundle_data_store.dart @@ -0,0 +1,9 @@ +import 'package:flutter/services.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +class AssetBundleDataStore extends LocalDataStore { + AssetBundleDataStore(super.configuration); + + @override + Future fileReader(String path) async => rootBundle.loadString(path); +} diff --git a/packages/superdeck/lib/helpers/syntax_highlighter.dart b/packages/superdeck/lib/src/modules/common/helpers/syntax_highlighter.dart similarity index 69% rename from packages/superdeck/lib/helpers/syntax_highlighter.dart rename to packages/superdeck/lib/src/modules/common/helpers/syntax_highlighter.dart index f805eeee..d0e78ef9 100644 --- a/packages/superdeck/lib/helpers/syntax_highlighter.dart +++ b/packages/superdeck/lib/src/modules/common/helpers/syntax_highlighter.dart @@ -27,7 +27,7 @@ class SyntaxHighlight { // Load the markdown grammar and add it to the highlighter. for (var language in _secondarySupportedLangs) { final grammar = await rootBundle.loadString( - 'packages/superdeck/grammars/$language.json', + 'packages/superdeck/assets/grammars/$language.json', ); Highlighter.addLanguage(language, grammar); } @@ -35,10 +35,55 @@ class SyntaxHighlight { static List render(String source, String? language) { final highlighter = Highlighter(language: language!, theme: _theme); - return [highlighter.highlight(source)]; + final code = highlighter.highlight(source); + return splitTextSpansByLines([code]); } } +/// Splits a list of TextSpans into separate lines based on line breaks. +/// Each returned TextSpan represents one line with preserved styling. +List splitTextSpansByLines(List spans) { + // This will hold lists of TextSpans, each list representing a line. + List> lines = [[]]; + + /// Recursively processes a TextSpan and splits it into lines. + void processSpan(TextSpan span) { + if (span.children != null && span.children!.isNotEmpty) { + // If the span has children, create a new span with the same style. + final newSpan = TextSpan(style: span.style, children: const []); + // Add this new span to the current line. + lines.last.add(newSpan); + // Recursively process each child. + for (var child in span.children!) { + if (child is TextSpan) { + processSpan(child); + } + } + } else if (span.text != null) { + // Split the text by line breaks. + List parts = span.text!.split('\n'); + for (int i = 0; i < parts.length; i++) { + if (i > 0) { + // For each new line after the first, add a new empty list. + lines.add([]); + } + if (parts[i].isNotEmpty) { + // Add the text part to the current line with the same style. + lines.last.add(TextSpan(text: parts[i], style: span.style)); + } + } + } + } + + // Process each top-level TextSpan. + for (var span in spans) { + processSpan(span); + } + + // Convert each line's list of TextSpans into a single TextSpan. + return lines.map((lineSpans) => TextSpan(children: lineSpans)).toList(); +} + List parseLineNumbers(String input) { // Regular expression to find the content within the curly braces final regExp = RegExp(r'\{(.*?)\}'); diff --git a/packages/superdeck/lib/helpers/theme.dart b/packages/superdeck/lib/src/modules/common/helpers/theme.dart similarity index 100% rename from packages/superdeck/lib/helpers/theme.dart rename to packages/superdeck/lib/src/modules/common/helpers/theme.dart diff --git a/packages/superdeck/lib/helpers/utils.dart b/packages/superdeck/lib/src/modules/common/helpers/utils.dart similarity index 72% rename from packages/superdeck/lib/helpers/utils.dart rename to packages/superdeck/lib/src/modules/common/helpers/utils.dart index 006cdd74..49a27367 100644 --- a/packages/superdeck/lib/helpers/utils.dart +++ b/packages/superdeck/lib/src/modules/common/helpers/utils.dart @@ -1,16 +1,28 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:mix/mix.dart'; -import '../styles/style_spec.dart'; +Offset getTotalModifierSpacing(Spec spec) { + final modifiers = spec.modifiers?.value ?? []; + final padding = modifiers.firstWhereOrNull( + (element) => element is PaddingModifierSpec, + ) as PaddingModifierSpec?; -BoxConstraints calculateConstraints(Size size, SlideSpec spec) { + return Offset( + padding?.padding.horizontal ?? 0.0, + padding?.padding.vertical ?? 0.0, + ); +} + +Offset calculateBlockOffset(BoxSpec spec) { // final outerContainer = spec.outerContainer; // final innerContainer = spec.innerContainer; - final contentContainer = spec.contentContainer; + final contentBlock = spec; double horizontalSpacing = 0.0; double verticalSpacing = 0.0; - for (final container in [contentContainer]) { + for (final container in [contentBlock]) { final padding = container.padding ?? EdgeInsets.zero; final margin = container.margin ?? EdgeInsets.zero; @@ -25,14 +37,17 @@ BoxConstraints calculateConstraints(Size size, SlideSpec spec) { } } + final modifier = getTotalModifierSpacing(container); + horizontalSpacing += - padding.horizontal + margin.horizontal + horizontalBorder; - verticalSpacing += padding.vertical + margin.vertical + verticalBorder; + padding.horizontal + margin.horizontal + horizontalBorder + modifier.dx; + verticalSpacing += + padding.vertical + margin.vertical + verticalBorder + modifier.dy; } - return BoxConstraints( - maxHeight: size.height - verticalSpacing, - maxWidth: size.width - horizontalSpacing, + return Offset( + horizontalSpacing, + verticalSpacing, ); } diff --git a/packages/superdeck/lib/src/modules/common/initializer_provider.dart b/packages/superdeck/lib/src/modules/common/initializer_provider.dart new file mode 100644 index 00000000..1ff47e54 --- /dev/null +++ b/packages/superdeck/lib/src/modules/common/initializer_provider.dart @@ -0,0 +1,45 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:localstorage/localstorage.dart'; +import 'package:window_manager/window_manager.dart'; + +import 'helpers/constants.dart'; +import 'helpers/syntax_highlighter.dart'; + +Future initializeDependencies() async { + WidgetsFlutterBinding.ensureInitialized(); + + await Future.wait([ + SyntaxHighlight.initialize(), + initLocalStorage(), + _initializeWindowManager(), + ]); +} + +Future _initializeWindowManager() async { + if (kIsWeb) return; + + // Must add this line. + await windowManager.ensureInitialized(); + + // final titleBarHeight = await windowManager.getTitleBarHeight(); + + final newSize = Size(kResolution.width, kResolution.height); + + final windowOptions = WindowOptions( + size: newSize, + backgroundColor: Colors.black, + skipTaskbar: false, + minimumSize: newSize, + windowButtonVisibility: true, + title: 'Superdeck', + titleBarStyle: TitleBarStyle.hidden, + ); + + windowManager.waitUntilReadyToShow(windowOptions, () async { + await windowManager.show(); + await windowManager.focus(); + }); + + await windowManager.setAspectRatio(kAspectRatio); +} diff --git a/packages/superdeck/lib/src/modules/common/markdown/builders/alert_element_builder.dart b/packages/superdeck/lib/src/modules/common/markdown/builders/alert_element_builder.dart new file mode 100644 index 00000000..bbc4dbb7 --- /dev/null +++ b/packages/superdeck/lib/src/modules/common/markdown/builders/alert_element_builder.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:markdown/markdown.dart' as md; + +import '../../styles/style_spec.dart'; + +enum AlertType { + note, + tip, + important, + warning, + caution; + + static AlertType fromString(String type) { + return AlertType.values.firstWhere( + (e) => type.toLowerCase() == e.name, + orElse: () => AlertType.note, + ); + } +} + +class AlertBlockSyntax extends md.AlertBlockSyntax { + const AlertBlockSyntax(); + + @override + md.Node parse(md.BlockParser parser) { + // Parse the alert type from the first line. + final type = + pattern.firstMatch(parser.current.content)!.group(1)!.toLowerCase(); + parser.advance(); + final childLines = parseChildLines(parser); + + // Combine the content into a single block + final content = childLines.map((line) => line.content).join('\n'); + + final alertElement = md.Element.text('alert', content) + ..attributes['type'] = type; + + return md.Element( + 'p', + [alertElement], + ); + } +} + +class AlertElementBuilder extends MarkdownElementBuilder { + final MarkdownAlertSpec? spec; + AlertElementBuilder(this.spec); + @override + Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) { + final spec = this.spec ?? const MarkdownAlertSpec(); + var alertType = AlertType.note; + + if (element.attributes['type'] != null) { + alertType = AlertType.fromString(element.attributes['type'] as String); + } + + final specType = switch (alertType) { + AlertType.note => spec.note, + AlertType.tip => spec.tip, + AlertType.important => spec.important, + AlertType.warning => spec.warning, + AlertType.caution => spec.caution, + }; + + final iconType = switch (alertType) { + AlertType.note => Icons.info_outline, + AlertType.tip => Icons.lightbulb_outline, + AlertType.important => Icons.label_important_outline, + AlertType.warning => Icons.warning_amber_outlined, + AlertType.caution => Icons.dangerous_outlined, + }; + + return specType.container( + child: specType.containerFlex( + direction: Axis.vertical, + children: [ + specType.headingFlex( + direction: Axis.horizontal, + children: [ + specType.icon(iconType), + specType.heading(alertType.name), + ], + ), + specType.description(element.textContent.trim()), + ], + ), + ); + } +} diff --git a/packages/superdeck/lib/src/modules/common/markdown/builders/code_element_builder.dart b/packages/superdeck/lib/src/modules/common/markdown/builders/code_element_builder.dart new file mode 100644 index 00000000..7e2a9f0d --- /dev/null +++ b/packages/superdeck/lib/src/modules/common/markdown/builders/code_element_builder.dart @@ -0,0 +1,179 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:markdown/markdown.dart' as md; +import 'package:mix/mix.dart'; +import 'package:superdeck/src/components/molecules/block_provider.dart'; + +import '../../../../../superdeck.dart'; +import '../../helpers/syntax_highlighter.dart'; +import '../../helpers/utils.dart'; +import '../markdown_helpers.dart'; +import 'element_data_provider.dart'; + +class CodeElementBuilder extends MarkdownElementBuilder { + final MarkdownCodeblockSpec? spec; + + CodeElementBuilder(this.spec); + + @override + Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) { + final spec = this.spec ?? const MarkdownCodeblockSpec(); + // Extract language from the class attribute, default to 'dart' + var language = 'dart'; + if (element.attributes['class'] != null) { + String lg = element.attributes['class'] as String; + if (lg.startsWith('language-')) { + language = lg.substring(9); + } + } + + // Extract hero tag if present + final tagAndContent = getTagAndContent(element.textContent); + final heroTag = tagAndContent.tag; + + final spans = SyntaxHighlight.render( + tagAndContent.content.trim(), + language, + ); + + // Build the code widget + Widget codeWidget = Row( + children: [ + Expanded( + child: BoxSpecWidget( + spec: spec.container, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: spans.map((span) { + return RichText( + text: TextSpan( + style: spec.textStyle, + children: [span], + ), + ); + }).toList(), + ), + ), + ), + ], + ); + + // Provide _CodeElementData for Hero animations + return Builder(builder: (context) { + final blockData = BlockData.of(context); + final slide = SlideConfiguration.of(context); + + final hasHero = heroTag != null && !slide.isExporting; + + final codeOffset = getTotalModifierSpacing(spec); + + final totalSize = Size( + blockData.size.width - codeOffset.dx, + blockData.size.height - codeOffset.dy, + ); + + // If a hero tag is present, wrap the widget in a Hero + if (hasHero) { + codeWidget = _CodeElementHero( + tag: heroTag, + child: codeWidget, + ); + } + return CodeElementDataProvider( + text: tagAndContent.content.trim(), + language: language, + spec: spec, + size: totalSize, + child: codeWidget, + ); + }); + } +} + +class _CodeElementHero extends StatelessWidget { + const _CodeElementHero({ + required this.tag, + required this.child, + }); + + final String tag; + final Widget child; + + @override + Widget build(BuildContext context) { + return Hero( + tag: tag, + child: child, + flightShuttleBuilder: ( + BuildContext flightContext, + Animation animation, + HeroFlightDirection flightDirection, + BuildContext fromHeroContext, + BuildContext toHeroContext, + ) { + final toBlock = ElementDataProvider.of( + toHeroContext, + ); + final fromBlock = ElementDataProvider.maybeOf( + fromHeroContext, + ); + + final fromBlockSize = + fromBlock?.size ?? ElementDataProvider.ofAny(fromHeroContext).size; + + final fromBlockText = fromBlock?.text ?? toBlock.text; + + final fromBlockSpec = fromBlock?.spec ?? toBlock.spec; + + return AnimatedBuilder( + animation: animation, + builder: (context, child) { + final interpolatedSpec = fromBlockSpec.lerp( + toBlock.spec, + animation.value, + ); + final interpolatedSize = Size.lerp( + fromBlockSize, + toBlock.size, + animation.value, + )!; + + final interpolatedText = lerpString( + fromBlockText, + toBlock.text, + animation.value, + ); + + final spans = SyntaxHighlight.render( + interpolatedText, + toBlock.language, + ); + + return Wrap( + clipBehavior: Clip.hardEdge, + children: [ + SizedBox.fromSize( + size: interpolatedSize, + child: BoxSpecWidget( + spec: interpolatedSpec.container, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: spans.map((span) { + return RichText( + text: TextSpan( + style: interpolatedSpec.textStyle, + children: [span], + ), + ); + }).toList(), + ), + ), + ), + ], + ); + }, + ); + }, + ); + } +} diff --git a/packages/superdeck/lib/src/modules/common/markdown/builders/element_data_provider.dart b/packages/superdeck/lib/src/modules/common/markdown/builders/element_data_provider.dart new file mode 100644 index 00000000..4c48ae29 --- /dev/null +++ b/packages/superdeck/lib/src/modules/common/markdown/builders/element_data_provider.dart @@ -0,0 +1,119 @@ +import 'package:flutter/widgets.dart'; +import 'package:mix/mix.dart'; + +import '../../styles/style_spec.dart'; + +abstract class ElementDataProvider extends InheritedWidget { + final Size size; + const ElementDataProvider({ + super.key, + required super.child, + required this.size, + }); + + static ElementDataProvider ofAny(BuildContext context) { + final result = ImageElementDataProvider.maybeOf(context) ?? + CodeElementDataProvider.maybeOf(context) ?? + TextElementDataProvider.maybeOf(context); + + if (result == null) { + throw FlutterError('No ElementDataProvider found in context'); + } + return result; + } + + static T? maybeOf(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType(); + } + + static T of(BuildContext context) { + final result = maybeOf(context); + if (result == null) { + throw FlutterError('No $T found in context'); + } + return result; + } +} + +class CodeElementDataProvider extends ElementDataProvider { + final String text; + final String language; + final MarkdownCodeblockSpec spec; + + const CodeElementDataProvider({ + super.key, + required super.child, + required this.text, + required this.language, + required this.spec, + required super.size, + }); + + static CodeElementDataProvider? maybeOf(BuildContext context) { + return context + .dependOnInheritedWidgetOfExactType(); + } + + @override + bool updateShouldNotify( + CodeElementDataProvider oldWidget, + ) { + return oldWidget.text != text || + oldWidget.language != language || + oldWidget.spec != spec || + oldWidget.size != size; + } +} + +class ImageElementDataProvider extends ElementDataProvider { + final ImageSpec spec; + final Uri uri; + const ImageElementDataProvider({ + super.key, + required super.child, + required super.size, + required this.spec, + required this.uri, + }); + + static ImageElementDataProvider? maybeOf(BuildContext context) { + return context + .dependOnInheritedWidgetOfExactType(); + } + + @override + bool updateShouldNotify( + ImageElementDataProvider oldWidget, + ) { + return oldWidget.size != size || + oldWidget.spec != spec || + oldWidget.uri != uri; + } +} + +class TextElementDataProvider extends ElementDataProvider { + final String text; + final TextSpec spec; + + const TextElementDataProvider({ + super.key, + required super.child, + required this.text, + required this.spec, + required super.size, + }); + + static TextElementDataProvider? maybeOf(BuildContext context) { + return context + .dependOnInheritedWidgetOfExactType(); + } + + @override + bool updateShouldNotify( + TextElementDataProvider oldWidget, + ) { + return oldWidget.text != text || + oldWidget.spec != spec || + oldWidget.size != size; + } +} diff --git a/packages/superdeck/lib/src/modules/common/markdown/builders/image_element_builder.dart b/packages/superdeck/lib/src/modules/common/markdown/builders/image_element_builder.dart new file mode 100644 index 00000000..229cd33b --- /dev/null +++ b/packages/superdeck/lib/src/modules/common/markdown/builders/image_element_builder.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:markdown/markdown.dart' as md; +import 'package:mix/mix.dart'; +import 'package:superdeck/src/components/molecules/block_provider.dart'; +import 'package:superdeck/src/modules/deck/slide_configuration.dart'; + +import '../../../../components/atoms/cache_image_widget.dart'; +import '../../helpers/utils.dart'; +import 'element_data_provider.dart'; + +class ImageElementBuilder extends MarkdownElementBuilder { + final ImageSpec spec; + + ImageElementBuilder(this.spec); + + @override + Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) { + final src = element.attributes['src']!; + final heroTag = element.attributes['hero']; + + final uri = Uri.parse(src); + + return Builder(builder: (context) { + final block = BlockData.of(context); + final slide = SlideConfiguration.of(context); + + final hasHero = heroTag != null && !slide.isExporting; + + final contentOffset = getTotalModifierSpacing(spec); + + final totalSize = Size( + block.size.width - contentOffset.dx, + block.size.height - contentOffset.dy, + ); + Widget current = AnimatedContainer( + duration: Durations.medium1, + constraints: BoxConstraints.tight(totalSize), + child: CachedImage( + uri: uri, + targetSize: totalSize, + spec: spec, + ), + ); + + if (hasHero) { + current = _ImageElementHero( + tag: heroTag, + child: current, + ); + } + + return ImageElementDataProvider( + size: totalSize, + spec: spec, + uri: uri, + child: current, + ); + }); + } +} + +class _ImageElementHero extends StatelessWidget { + const _ImageElementHero({ + required this.tag, + required this.child, + }); + + final String tag; + final Widget child; + + @override + Widget build(BuildContext context) { + return Hero( + tag: tag, + child: child, + flightShuttleBuilder: ( + BuildContext context, + Animation animation, + HeroFlightDirection flightDirection, + BuildContext fromHeroContext, + BuildContext toHeroContext, + ) { + final toBlock = ElementDataProvider.of( + toHeroContext, + ); + final fromBlock = ElementDataProvider.maybeOf( + fromHeroContext, + ); + + final fromBlockSize = + fromBlock?.size ?? ElementDataProvider.ofAny(fromHeroContext).size; + + final fromBlockSpec = fromBlock?.spec ?? toBlock.spec; + + final fromBlockUri = fromBlock?.uri ?? toBlock.uri; + + Widget buildImageWidget(Size size, ImageSpec spec, Uri uri) { + return Container( + constraints: BoxConstraints.tight(size), + child: CachedImage( + uri: uri, + spec: spec, + ), + ); + } + + final interpolatedSize = Size.lerp( + fromBlockSize, + toBlock.size, + animation.value, + )!; + + return AnimatedBuilder( + animation: animation, + builder: (context, child) { + return buildImageWidget( + interpolatedSize, + fromBlockSpec.lerp(toBlock.spec, animation.value), + animation.value < 0.5 ? fromBlockUri : toBlock.uri, + ); + }, + ); + }, + ); + } +} diff --git a/packages/superdeck/lib/src/modules/common/markdown/builders/text_element_builder.dart b/packages/superdeck/lib/src/modules/common/markdown/builders/text_element_builder.dart new file mode 100644 index 00000000..5ace5fc6 --- /dev/null +++ b/packages/superdeck/lib/src/modules/common/markdown/builders/text_element_builder.dart @@ -0,0 +1,90 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:markdown/markdown.dart' as md; +import 'package:mix/mix.dart'; +import 'package:superdeck/src/modules/deck/slide_configuration.dart'; + +import '../../../../components/molecules/block_provider.dart'; +import '../../helpers/utils.dart'; +import '../markdown_helpers.dart'; +import 'element_data_provider.dart'; + +String _transformLineBreaks(String text) => text.replaceAll('
', '\n'); + +class TextElementBuilder extends MarkdownElementBuilder { + final TextSpec? spec; + TextElementBuilder(this.spec); + @override + Widget? visitText(md.Text text, TextStyle? preferredStyle) { + final (:tag, :content) = getTagAndContent(text.text); + + return Builder(builder: (context) { + final block = BlockData.of(context); + final slide = SlideConfiguration.of(context); + + final hasHero = tag != null && !slide.isExporting; + + final contentOffset = getTotalModifierSpacing(spec ?? const TextSpec()); + + Widget current = TextSpecWidget( + _transformLineBreaks(content), + spec: spec, + ); + + if (hasHero) { + current = _TextElementHero( + tag: tag, + child: current, + ); + } + return TextElementDataProvider( + text: _transformLineBreaks(content), + spec: spec ?? const TextSpec(), + size: (block.size - contentOffset) as Size, + child: current, + ); + }); + } +} + +class _TextElementHero extends StatelessWidget { + const _TextElementHero({ + required this.tag, + required this.child, + }); + + final String tag; + final Widget child; + + @override + Widget build(BuildContext context) { + return Hero( + tag: tag, + child: child, + flightShuttleBuilder: ( + BuildContext context, + Animation animation, + HeroFlightDirection flightDirection, + BuildContext fromHeroContext, + BuildContext toHeroContext, + ) { + final toBlock = + ElementDataProvider.of(toHeroContext); + final fromBlock = ElementDataProvider.maybeOf( + fromHeroContext, + ) ?? + toBlock; + + return AnimatedBuilder( + animation: animation, + builder: (context, child) { + return TextSpecWidget( + lerpString(fromBlock.text, toBlock.text, animation.value), + spec: fromBlock.spec.lerp(toBlock.spec, animation.value), + ); + }, + ); + }, + ); + } +} diff --git a/packages/superdeck/lib/src/modules/common/markdown/builders/zero_padding_builder.dart b/packages/superdeck/lib/src/modules/common/markdown/builders/zero_padding_builder.dart new file mode 100644 index 00000000..8f2e191d --- /dev/null +++ b/packages/superdeck/lib/src/modules/common/markdown/builders/zero_padding_builder.dart @@ -0,0 +1,7 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; + +class ZeroPaddingBuilder extends MarkdownPaddingBuilder { + @override + EdgeInsets getPadding() => EdgeInsets.zero; +} diff --git a/packages/superdeck/lib/src/modules/common/markdown/markdown_element_builders.dart b/packages/superdeck/lib/src/modules/common/markdown/markdown_element_builders.dart new file mode 100644 index 00000000..9d2a64aa --- /dev/null +++ b/packages/superdeck/lib/src/modules/common/markdown/markdown_element_builders.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:markdown/markdown.dart' as md; +import 'package:mix/mix.dart'; + +import '../../../../superdeck.dart'; +import 'builders/alert_element_builder.dart'; +import 'builders/code_element_builder.dart'; +import 'builders/image_element_builder.dart'; +import 'builders/text_element_builder.dart'; +import 'builders/zero_padding_builder.dart'; +import 'markdown_helpers.dart'; + +class SpecMarkdownBuilders { + final SlideSpec spec; + + SpecMarkdownBuilders(this.spec); + + final List blockSyntaxes = [ + const CustomHeaderSyntax(), + const AlertBlockSyntax(), + ]; + + final List inlineSyntaxes = [ + CustomImageSyntax(), + ]; + + late final builders = { + 'h1': TextElementBuilder(spec.h1), + 'h2': TextElementBuilder(spec.h2), + 'h3': TextElementBuilder(spec.h3), + 'h4': TextElementBuilder(spec.h4), + 'h5': TextElementBuilder(spec.h5), + 'h6': TextElementBuilder(spec.h6), + 'alert': AlertElementBuilder(spec.alert), + 'p': TextElementBuilder(spec.p), + 'code': CodeElementBuilder(spec.code), + 'img': ImageElementBuilder(spec.image), + 'li': TextElementBuilder(spec.list?.text), + }; + + Map get paddingBuilders { + return _kBlockTags.fold( + {}, + (map, tag) => map..[tag] = ZeroPaddingBuilder(), + ); + } + + Widget Function(bool) get checkboxBuilder { + return (bool checked) { + final icon = checked ? Icons.check_box : Icons.check_box_outline_blank; + return IconSpecWidget(icon, spec: spec.checkbox?.icon); + }; + } + + Widget Function(MarkdownBulletParameters params) get bulletBuilder { + return (parameters) { + final contents = switch (parameters.style) { + BulletStyle.unorderedList => '•', + BulletStyle.orderedList => '${parameters.index + 1}.', + }; + return TextSpecWidget(spec: spec.list?.bullet, contents); + }; + } +} + +final _kBlockTags = [ + 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', // + 'ul', 'ol', 'li', 'blockquote', // + 'pre', 'ol', 'ul', 'hr', 'table', // + 'thead', 'tbody', 'tr', 'section' +]; + +class CustomHeaderSyntax extends md.HeaderSyntax { + const CustomHeaderSyntax(); + + @override + md.Node parse(md.BlockParser parser) { + final element = super.parse(parser) as md.Element; + + final tag = getTagAndContent(parser.lines.first.content).tag; + if (tag != null) { + element.attributes['hero'] = tag; + } + return element; + } +} + +class CustomImageSyntax extends md.InlineSyntax { + CustomImageSyntax() : super(_pattern); + + static const String _pattern = r'!\[(.*?)\]\((.*?)\)(?:\s*\{\.([^\}]+)\})?'; + + @override + bool onMatch(md.InlineParser parser, Match match) { + final altText = match.group(1)!; + final url = match.group(2)!; + final tag = match.group(3); + + final element = md.Element.empty('img'); + element.attributes['src'] = url; + element.attributes['alt'] = altText; + + if (tag != null) { + element.attributes['hero'] = tag; + } + + parser.addNode(element); + return true; + } +} diff --git a/packages/superdeck/lib/src/modules/common/markdown/markdown_helpers.dart b/packages/superdeck/lib/src/modules/common/markdown/markdown_helpers.dart new file mode 100644 index 00000000..f8f188cf --- /dev/null +++ b/packages/superdeck/lib/src/modules/common/markdown/markdown_helpers.dart @@ -0,0 +1,111 @@ +import 'dart:math' as math; + +import 'package:flutter/widgets.dart'; + +({ + String? tag, + String content, +}) getTagAndContent(String text) { + text = text.trim(); + final regExp = RegExp(r'{\.(.*?)}'); + + final match = regExp.firstMatch(text); + + final tag = match?.group(1); + + String content = text.replaceAll(regExp, '').trim(); + // TODO: Remove this for code element after + content = content.replaceAll('```', ''); + + return ( + tag: tag, + content: content, + ); +} + +String lerpString(String start, String end, double t) { + // Clamp t between 0 and 1 + t = t.clamp(0.0, 1.0); + + final commonPrefixLen = start.commonPrefixLength(end); + final startSuffix = start.substring(commonPrefixLen); + final endSuffix = end.substring(commonPrefixLen); + + final result = StringBuffer(); + result.write(end.substring(0, commonPrefixLen)); + + if (t <= 0.5) { + final progress = t / 0.5; + final startLength = startSuffix.length; + final numCharsToShow = ((1 - progress) * startLength).round(); + if (numCharsToShow > 0) { + result.write(startSuffix.substring(0, numCharsToShow)); + } + } else { + final progress = (t - 0.5) / 0.5; + final endLength = endSuffix.length; + final numCharsToShow = (progress * endLength).round(); + if (numCharsToShow > 0) { + result.write(endSuffix.substring(0, numCharsToShow)); + } + } + + return result.toString(); +} + +extension on String { + int commonPrefixLength(String other) { + final len = math.min(length, other.length); + for (int i = 0; i < len; i++) { + if (this[i] != other[i]) { + return i; + } + } + return len; + } +} + +List lerpTextSpans( + List start, + List end, + double t, +) { + final maxLines = math.max(start.length, end.length); + List interpolatedSpans = []; + + for (int i = 0; i < maxLines; i++) { + final startSpan = i < start.length ? start[i] : const TextSpan(text: ''); + final endSpan = i < end.length ? end[i] : const TextSpan(text: ''); + + if (startSpan.text == null && endSpan.text == null) { + // if chilrens are not null recursive + if (startSpan.children != null && endSpan.children != null) { + if (startSpan.children!.isEmpty && endSpan.children!.isEmpty) { + continue; + } + final children = lerpTextSpans( + startSpan.children! as List, + endSpan.children! as List, + t, + ); + final interpolatedSpan = TextSpan( + children: children, + style: TextStyle.lerp(startSpan.style, endSpan.style, t), + ); + interpolatedSpans.add(interpolatedSpan); + continue; + } + } + + final interpolatedText = + lerpString(startSpan.text ?? '', endSpan.text ?? '', t); + final interpolatedStyle = TextStyle.lerp(startSpan.style, endSpan.style, t); + + final interpolatedSpan = + TextSpan(text: interpolatedText, style: interpolatedStyle); + + interpolatedSpans.add(interpolatedSpan); + } + + return interpolatedSpans; +} diff --git a/packages/superdeck/lib/src/modules/common/styles/style.dart b/packages/superdeck/lib/src/modules/common/styles/style.dart new file mode 100644 index 00000000..d55b884c --- /dev/null +++ b/packages/superdeck/lib/src/modules/common/styles/style.dart @@ -0,0 +1,260 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:mix/mix.dart'; +import 'package:superdeck/src/modules/common/helpers/extensions.dart'; + +import 'style_spec.dart'; + +TextStyle get _baseTextStyle => GoogleFonts.poppins().copyWith( + fontSize: 24, + color: Colors.white, + ); + +const onGist = Variant('gist'); +const onDebug = Variant('debug'); +const onImage = Variant('image'); + +class DeckStyle { + const DeckStyle(); + SlideSpecUtility get $ => SlideSpecUtility.self; + + Style build() { + final containers = [ + $.blockContainer.padding.all(40), + $.section.image( + $.blockContainer.padding.all(0), + ), + $.section.gist( + $.blockContainer.chain + ..margin.all(0) + ..padding.all(0), + ), + ]; + + final alertStyle = [ + // Heading + $.alert.all.heading.chain + ..capitalize() + ..style.as(_baseTextStyle) + ..style.bold(), + // Container + $.alert.all.container.chain + ..padding.horizontal(24) + ..padding.vertical(8) + ..margin.bottom(12) + ..color.withOpacity(0.05) + ..border.left.width(4), + // Description + $.alert.all.description.chain + ..style.as(_baseTextStyle) + ..textAlign.left() + ..style.height(1.6), + + // Remaining + $.alert.all.chain + ..containerFlex.gap(12) + ..containerFlex.crossAxisAlignment.start() + ..headingFlex.gap(8), + // Types + $.alert.note.color(Colors.blue), + $.alert.tip.color(Colors.green), + $.alert.important.color(Colors.deepPurpleAccent), + $.alert.warning.color(Colors.amber), + $.alert.caution.color(Colors.redAccent), + ]; + + final typography = [ + $.headingTextStyle.chain, + $.baseTextStyle.as(_baseTextStyle), + $.h1.chain + ..style.fontSize(96) + ..style.fontWeight.bold() + ..style.height(1.1) + ..wrap.padding.bottom(16), + $.h2.chain + ..style.fontSize(72) + ..style.fontWeight.bold() + ..style.height(1.2) + ..wrap.padding.bottom(12), + $.h3.chain + ..style.fontSize(48) + ..style.fontWeight.w600() + ..style.height(1.3) + ..wrap.padding.bottom(12), + $.h4.chain + ..style.fontSize(36) + ..style.fontWeight.normal() + ..style.height(1.3) + ..wrap.padding.bottom(8), + $.h5.chain + ..style.fontSize(24) + ..style.fontWeight.normal() + ..style.height(1.4) + ..wrap.padding.bottom(4), + $.h6.chain + ..style.as(_baseTextStyle) + ..style.height(1.4) + ..style.fontWeight.normal() + ..wrap.padding.bottom(3), + $.p.chain + ..style.as(_baseTextStyle) + ..style.height(1.6) + ..wrap.padding.bottom(12), + ]; + + final codeStyle = $.code.chain + ..textStyle.as(GoogleFonts.jetBrainsMono()) + ..textStyle.fontSize(18) + ..container.padding.all(32) + ..container.color(const Color.fromARGB(255, 0, 0, 0)) + ..container.borderRadius.circular(10); + + final tableStyle = $.table.chain + ..headStyle.as(_baseTextStyle) + ..headStyle.fontWeight.bold() + ..bodyStyle.as(_baseTextStyle) + ..cellPadding.all(12) + ..border.all(color: Colors.grey, width: 2) + ..cellDecoration.color(Colors.grey.useOpacity(0.1)); + + final blockquoteStyle = $.blockquote.chain + ..textStyle.as(_baseTextStyle) + ..textStyle.fontSize(32) + ..padding( + bottom: 12, + left: 30, + ) + ..decoration.border.left( + color: Colors.grey, + width: 4, + ); + + final listStyle = $.list.chain + ..bullet.style.as(_baseTextStyle) + ..text.style.as(_baseTextStyle) + ..text.style.height(1.6) + ..text.wrap.padding.bottom(8); + + return Style.create([ + ...containers, + ...typography, + codeStyle, + listStyle, + ...alertStyle, + + $.link.color(const Color.fromARGB(255, 66, 82, 96)), + $.list.bullet.style.as(_baseTextStyle), + $.checkbox.textStyle.as(_baseTextStyle), + + tableStyle, + blockquoteStyle, + // divider + $.divider.border(width: 2, color: Colors.grey), + // $.image.fit.cover(), + ]); + } +} + +class SlideSpecSectionsUtility { + const SlideSpecSectionsUtility(); + + final gist = const Variant('gist'); + + final image = const Variant('image'); +} + +extension SlideSpecUtilityX on SlideSpecUtility { + Variant get debug => const Variant('debug'); + TextStyleUtility get baseTextStyle { + return TextStyleUtility( + (value) => only( + link: value, + a: value, + em: value, + strong: value, + p: TextSpecAttribute(style: value), + h1: TextSpecAttribute(style: value), + h2: TextSpecAttribute(style: value), + h3: TextSpecAttribute(style: value), + h4: TextSpecAttribute(style: value), + h5: TextSpecAttribute(style: value), + h6: TextSpecAttribute(style: value), + list: MarkdownListSpecAttribute( + bullet: TextSpecAttribute( + style: value, + )), + table: MarkdownTableSpecAttribute(bodyStyle: value, headStyle: value), + code: MarkdownCodeblockSpecAttribute(textStyle: value), + blockquote: MarkdownBlockquoteSpecAttribute(textStyle: value), + ), + ); + } + + // PaddingModifierSpecUtility get blockSpacing { + // return PaddingModifierSpecUtility((value) { + // final modifier = WidgetModifiersDataDto([value]); + // return only( + // h1: TextSpecAttribute(modifiers: modifier), + // h2: TextSpecAttribute(modifiers: modifier), + // h3: TextSpecAttribute(modifiers: modifier), + // h4: TextSpecAttribute(modifiers: modifier), + // h5: TextSpecAttribute(modifiers: modifier), + // h6: TextSpecAttribute(modifiers: modifier), + // p: TextSpecAttribute(modifiers: modifier), + // list: MarkdownListSpecAttribute( + // bullet: TextSpecAttribute(modifiers: modifier), + // ), + // blockquote: MarkdownBlockquoteSpecAttribute( + // modifiers: modifier, + // ), + // image: ImageSpecAttribute(modifiers: modifier), + // code: MarkdownCodeblockSpecAttribute( + // modifiers: modifier, + // ), + // table: MarkdownTableSpecAttribute( + // modifiers: modifier, + // ), + // ); + // }); + // } + + SlideSpecSectionsUtility get section => const SlideSpecSectionsUtility(); + + TextSpecUtility get headingTextStyle { + return TextSpecUtility( + (value) => only( + h1: value, + h2: value, + h3: value, + h4: value, + h5: value, + h6: value, + ), + ); + } +} + +extension TextDirectiveX on TextDirectiveUtility { + T sameWidthLines(int lines) => call((String text) { + final words = text.split(' '); + final averageLineLength = text.length / lines; + + final formattedLines = []; + var currentLine = StringBuffer(); + + for (var word in words) { + final newLineLength = currentLine.length + word.length + 1; + if (newLineLength > averageLineLength && currentLine.isNotEmpty) { + formattedLines.add(currentLine.toString()); + currentLine.clear(); + } + currentLine.write(currentLine.isEmpty ? word : ' $word'); + } + + if (currentLine.isNotEmpty) { + formattedLines.add(currentLine.toString()); + } + + return formattedLines.join('\n'); + }); +} diff --git a/packages/superdeck/lib/src/modules/common/styles/style_spec.dart b/packages/superdeck/lib/src/modules/common/styles/style_spec.dart new file mode 100644 index 00000000..101af16d --- /dev/null +++ b/packages/superdeck/lib/src/modules/common/styles/style_spec.dart @@ -0,0 +1,312 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:mix/mix.dart'; +import 'package:mix_annotations/mix_annotations.dart'; + +part 'style_spec.g.dart'; + +const _mdCode = MixableFieldDto(type: 'MarkdownCodeblockSpecAttribute'); +const _mdList = MixableFieldDto(type: 'MarkdownListSpecAttribute'); +const _mdTable = MixableFieldDto(type: 'MarkdownTableSpecAttribute'); +const _mdCheckbox = MixableFieldDto(type: 'MarkdownCheckboxSpecAttribute'); + +const _mdBlockQuote = MixableFieldDto(type: 'MarkdownBlockquoteSpecAttribute'); +const _mdAlert = MixableFieldDto(type: 'MarkdownAlertSpecAttribute'); + +const _mdAlertItem = MixableFieldDto(type: 'MarkdownAlertTypeSpecAttribute'); + +@MixableSpec() +final class MarkdownTextSpec extends Spec + with _$MarkdownTextSpec { + final TextStyle? textStyle; + final EdgeInsets? padding; + final WrapAlignment? alignment; + + const MarkdownTextSpec({ + this.textStyle, + this.padding, + this.alignment, + }); +} + +@MixableSpec() +final class MarkdownListSpec extends Spec + with _$MarkdownListSpec { + final TextSpec? bullet; + final TextSpec? text; + + final WrapAlignment? orderedAlignment; + final WrapAlignment? unorderedAlignment; + + const MarkdownListSpec({ + this.bullet, + this.text, + this.orderedAlignment, + this.unorderedAlignment, + }); +} + +@MixableSpec() +final class MarkdownAlertSpec extends Spec + with _$MarkdownAlertSpec { + @MixableProperty(dto: _mdAlertItem) + final MarkdownAlertTypeSpec note; + + @MixableProperty(dto: _mdAlertItem) + final MarkdownAlertTypeSpec tip; + + @MixableProperty(dto: _mdAlertItem) + final MarkdownAlertTypeSpec important; + + @MixableProperty(dto: _mdAlertItem) + final MarkdownAlertTypeSpec warning; + + @MixableProperty(dto: _mdAlertItem) + final MarkdownAlertTypeSpec caution; + + const MarkdownAlertSpec({ + MarkdownAlertTypeSpec? note, + MarkdownAlertTypeSpec? tip, + MarkdownAlertTypeSpec? important, + MarkdownAlertTypeSpec? warning, + MarkdownAlertTypeSpec? caution, + }) : note = note ?? const MarkdownAlertTypeSpec(), + tip = tip ?? const MarkdownAlertTypeSpec(), + important = important ?? const MarkdownAlertTypeSpec(), + warning = warning ?? const MarkdownAlertTypeSpec(), + caution = caution ?? const MarkdownAlertTypeSpec(); +} + +@MixableSpec() +final class MarkdownAlertTypeSpec extends Spec + with _$MarkdownAlertTypeSpec { + final TextSpec heading; + final TextSpec description; + final IconSpec icon; + final BoxSpec container; + final FlexSpec containerFlex; + final FlexSpec headingFlex; + + const MarkdownAlertTypeSpec({ + TextSpec? heading, + TextSpec? description, + IconSpec? icon, + BoxSpec? container, + FlexSpec? headingFlex, + FlexSpec? containerFlex, + }) : heading = heading ?? const TextSpec(), + description = description ?? const TextSpec(), + icon = icon ?? const IconSpec(), + containerFlex = containerFlex ?? const FlexSpec(), + headingFlex = headingFlex ?? const FlexSpec(), + container = container ?? const BoxSpec(); +} + +extension MarkdownAlertSpecUtilityX + on MarkdownAlertSpecUtility { + MarkdownAlertTypeSpecUtility get all => MarkdownAlertTypeSpecUtility( + (value) => only( + note: value, + tip: value, + important: value, + warning: value, + caution: value, + ), + ); +} +//TODO Mix example + +extension MarkdownAlertTypeSpecUtilityX + on MarkdownAlertTypeSpecUtility { + ColorUtility get color => ColorUtility((value) { + return heading.style + .only(color: value) + .merge(container.border.left.only(color: value)) + .merge(icon.only(color: value)) as T; + }); +} + +@MixableSpec() +final class MarkdownTableSpec extends Spec + with _$MarkdownTableSpec { + final TextStyle? headStyle; + final TextStyle? bodyStyle; + final TextAlign? headAlignment; + final EdgeInsets? padding; + final TableBorder? border; + final TableColumnWidth? columnWidth; + final EdgeInsets? cellPadding; + + final BoxDecoration? cellDecoration; + final TableCellVerticalAlignment? verticalAlignment; + + const MarkdownTableSpec({ + this.headStyle, + this.bodyStyle, + this.headAlignment, + this.padding, + this.border, + this.columnWidth, + this.cellPadding, + this.cellDecoration, + this.verticalAlignment, + super.modifiers, + super.animated, + }); +} + +@MixableSpec() +final class MarkdownBlockquoteSpec extends Spec + with _$MarkdownBlockquoteSpec { + final TextStyle? textStyle; + final EdgeInsets? padding; + final BoxDecoration? decoration; + final WrapAlignment? alignment; + + const MarkdownBlockquoteSpec({ + this.textStyle, + this.padding, + this.decoration, + this.alignment, + super.modifiers, + super.animated, + }); +} + +@MixableSpec() +final class MarkdownCodeblockSpec extends Spec + with _$MarkdownCodeblockSpec { + final TextStyle? textStyle; + final BoxSpec? container; + + final WrapAlignment? alignment; + + const MarkdownCodeblockSpec({ + this.textStyle, + this.container, + this.alignment, + super.modifiers, + super.animated, + }); +} + +@MixableSpec() +class MarkdownCheckboxSpec extends Spec + with _$MarkdownCheckboxSpec { + final TextStyle? textStyle; + final IconSpec? icon; + + const MarkdownCheckboxSpec({ + this.textStyle, + this.icon, + super.modifiers, + super.animated, + }); +} + +@MixableSpec() +final class SlideSpec extends Spec with _$SlideSpec { + final TextSpec? h1; + + final TextSpec? h2; + + final TextStyle? a; + final TextStyle? em; + final TextStyle? strong; + final TextStyle? del; + final TextStyle? img; + + final TextScaler? textScaleFactor; + + final TextSpec? h3; + + final TextSpec? h4; + + final TextSpec? h5; + + final TextSpec? h6; + + final TextSpec? p; + + final TextStyle? link; + + @MixableProperty(dto: _mdAlert) + final MarkdownAlertSpec alert; + + @MixableProperty(utilities: [MixableUtility(alias: 'divider')]) + final BoxDecoration? horizontalRuleDecoration; + @MixableProperty(dto: _mdBlockQuote) + final MarkdownBlockquoteSpec? blockquote; + + @MixableProperty(dto: _mdList) + final MarkdownListSpec? list; + + @MixableProperty(dto: _mdTable) + final MarkdownTableSpec? table; + + @MixableProperty(dto: _mdCode) + final MarkdownCodeblockSpec? code; + + final BoxSpec blockContainer; + final ImageSpec image; + + @MixableProperty(dto: _mdCheckbox) + final MarkdownCheckboxSpec? checkbox; + + static const of = _$SlideSpec.of; + static const from = _$SlideSpec.from; + + const SlideSpec({ + this.h1, + this.h2, + this.h3, + this.h4, + this.h5, + this.h6, + this.p, + this.link, + this.blockquote, + this.list, + this.table, + this.checkbox, + this.code, + this.a, + this.em, + this.strong, + this.del, + this.img, + this.horizontalRuleDecoration, + this.textScaleFactor, + BoxSpec? blockContainer, + ImageSpec? image, + MarkdownAlertSpec? alert, + super.modifiers, + super.animated, + }) : blockContainer = blockContainer ?? const BoxSpec(), + image = image ?? const ImageSpec(), + alert = alert ?? const MarkdownAlertSpec(); + + MarkdownStyleSheet toStyle() { + return MarkdownStyleSheet( + a: link, + blockquote: blockquote?.textStyle, + blockquotePadding: blockquote?.padding, + horizontalRuleDecoration: horizontalRuleDecoration, + blockquoteDecoration: blockquote?.decoration, + blockquoteAlign: blockquote?.alignment ?? WrapAlignment.start, + orderedListAlign: list?.orderedAlignment ?? WrapAlignment.start, + unorderedListAlign: list?.unorderedAlignment ?? WrapAlignment.start, + tableHead: table?.headStyle, + tableBody: table?.bodyStyle, + tableHeadAlign: table?.headAlignment, + tablePadding: table?.padding, + tableBorder: table?.border, + tableColumnWidth: table?.columnWidth, + tableCellsPadding: table?.cellPadding, + tableCellsDecoration: table?.cellDecoration, + tableVerticalAlignment: + table?.verticalAlignment ?? TableCellVerticalAlignment.middle, + checkbox: checkbox?.textStyle, + ); + } +} diff --git a/packages/superdeck/lib/src/modules/common/styles/style_spec.g.dart b/packages/superdeck/lib/src/modules/common/styles/style_spec.g.dart new file mode 100644 index 00000000..86090408 --- /dev/null +++ b/packages/superdeck/lib/src/modules/common/styles/style_spec.g.dart @@ -0,0 +1,2517 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'style_spec.dart'; + +// ************************************************************************** +// MixableSpecGenerator +// ************************************************************************** + +mixin _$MarkdownTextSpec on Spec { + static MarkdownTextSpec from(MixData mix) { + return mix.attributeOf()?.resolve(mix) ?? + const MarkdownTextSpec(); + } + + /// {@template markdown_text_spec_of} + /// Retrieves the [MarkdownTextSpec] from the nearest [Mix] ancestor in the widget tree. + /// + /// This method uses [Mix.of] to obtain the [Mix] instance associated with the + /// given [BuildContext], and then retrieves the [MarkdownTextSpec] from that [Mix]. + /// If no ancestor [Mix] is found, this method returns an empty [MarkdownTextSpec]. + /// + /// Example: + /// + /// ```dart + /// final markdownTextSpec = MarkdownTextSpec.of(context); + /// ``` + /// {@endtemplate} + static MarkdownTextSpec of(BuildContext context) { + return _$MarkdownTextSpec.from(Mix.of(context)); + } + + /// Creates a copy of this [MarkdownTextSpec] but with the given fields + /// replaced with the new values. + @override + MarkdownTextSpec copyWith({ + TextStyle? textStyle, + EdgeInsets? padding, + WrapAlignment? alignment, + }) { + return MarkdownTextSpec( + textStyle: textStyle ?? _$this.textStyle, + padding: padding ?? _$this.padding, + alignment: alignment ?? _$this.alignment, + ); + } + + /// Linearly interpolates between this [MarkdownTextSpec] and another [MarkdownTextSpec] based on the given parameter [t]. + /// + /// The parameter [t] represents the interpolation factor, typically ranging from 0.0 to 1.0. + /// When [t] is 0.0, the current [MarkdownTextSpec] is returned. When [t] is 1.0, the [other] [MarkdownTextSpec] is returned. + /// For values of [t] between 0.0 and 1.0, an interpolated [MarkdownTextSpec] is returned. + /// + /// If [other] is null, this method returns the current [MarkdownTextSpec] instance. + /// + /// The interpolation is performed on each property of the [MarkdownTextSpec] using the appropriate + /// interpolation method: + /// + /// - [MixHelpers.lerpTextStyle] for [textStyle]. + /// - [EdgeInsets.lerp] for [padding]. + + /// For [alignment], the interpolation is performed using a step function. + /// If [t] is less than 0.5, the value from the current [MarkdownTextSpec] is used. Otherwise, the value + /// from the [other] [MarkdownTextSpec] is used. + /// + /// This method is typically used in animations to smoothly transition between + /// different [MarkdownTextSpec] configurations. + @override + MarkdownTextSpec lerp(MarkdownTextSpec? other, double t) { + if (other == null) return _$this; + + return MarkdownTextSpec( + textStyle: MixHelpers.lerpTextStyle(_$this.textStyle, other.textStyle, t), + padding: EdgeInsets.lerp(_$this.padding, other.padding, t), + alignment: t < 0.5 ? _$this.alignment : other.alignment, + ); + } + + /// The list of properties that constitute the state of this [MarkdownTextSpec]. + /// + /// This property is used by the [==] operator and the [hashCode] getter to + /// compare two [MarkdownTextSpec] instances for equality. + @override + List get props => [ + _$this.textStyle, + _$this.padding, + _$this.alignment, + ]; + + MarkdownTextSpec get _$this => this as MarkdownTextSpec; +} + +/// Represents the attributes of a [MarkdownTextSpec]. +/// +/// This class encapsulates properties defining the layout and +/// appearance of a [MarkdownTextSpec]. +/// +/// Use this class to configure the attributes of a [MarkdownTextSpec] and pass it to +/// the [MarkdownTextSpec] constructor. +final class MarkdownTextSpecAttribute extends SpecAttribute { + final TextStyleDto? textStyle; + final EdgeInsetsDto? padding; + final WrapAlignment? alignment; + + const MarkdownTextSpecAttribute({ + this.textStyle, + this.padding, + this.alignment, + }); + + /// Resolves to [MarkdownTextSpec] using the provided [MixData]. + /// + /// If a property is null in the [MixData], it falls back to the + /// default value defined in the `defaultValue` for that property. + /// + /// ```dart + /// final markdownTextSpec = MarkdownTextSpecAttribute(...).resolve(mix); + /// ``` + @override + MarkdownTextSpec resolve(MixData mix) { + return MarkdownTextSpec( + textStyle: textStyle?.resolve(mix), + padding: padding?.resolve(mix), + alignment: alignment, + ); + } + + /// Merges the properties of this [MarkdownTextSpecAttribute] with the properties of [other]. + /// + /// If [other] is null, returns this instance unchanged. Otherwise, returns a new + /// [MarkdownTextSpecAttribute] with the properties of [other] taking precedence over + /// the corresponding properties of this instance. + /// + /// Properties from [other] that are null will fall back + /// to the values from this instance. + @override + MarkdownTextSpecAttribute merge(MarkdownTextSpecAttribute? other) { + if (other == null) return this; + + return MarkdownTextSpecAttribute( + textStyle: textStyle?.merge(other.textStyle) ?? other.textStyle, + padding: padding?.merge(other.padding) ?? other.padding, + alignment: other.alignment ?? alignment, + ); + } + + /// The list of properties that constitute the state of this [MarkdownTextSpecAttribute]. + /// + /// This property is used by the [==] operator and the [hashCode] getter to + /// compare two [MarkdownTextSpecAttribute] instances for equality. + @override + List get props => [ + textStyle, + padding, + alignment, + ]; +} + +/// Utility class for configuring [MarkdownTextSpec] properties. +/// +/// This class provides methods to set individual properties of a [MarkdownTextSpec]. +/// Use the methods of this class to configure specific properties of a [MarkdownTextSpec]. +class MarkdownTextSpecUtility + extends SpecUtility { + /// Utility for defining [MarkdownTextSpecAttribute.textStyle] + late final textStyle = TextStyleUtility((v) => only(textStyle: v)); + + /// Utility for defining [MarkdownTextSpecAttribute.padding] + late final padding = EdgeInsetsUtility((v) => only(padding: v)); + + /// Utility for defining [MarkdownTextSpecAttribute.alignment] + late final alignment = WrapAlignmentUtility((v) => only(alignment: v)); + + MarkdownTextSpecUtility(super.builder, {super.mutable}); + + MarkdownTextSpecUtility get chain => + MarkdownTextSpecUtility(attributeBuilder, mutable: true); + + static MarkdownTextSpecUtility get self => + MarkdownTextSpecUtility((v) => v); + + /// Returns a new [MarkdownTextSpecAttribute] with the specified properties. + @override + T only({ + TextStyleDto? textStyle, + EdgeInsetsDto? padding, + WrapAlignment? alignment, + }) { + return builder(MarkdownTextSpecAttribute( + textStyle: textStyle, + padding: padding, + alignment: alignment, + )); + } +} + +/// A tween that interpolates between two [MarkdownTextSpec] instances. +/// +/// This class can be used in animations to smoothly transition between +/// different [MarkdownTextSpec] specifications. +class MarkdownTextSpecTween extends Tween { + MarkdownTextSpecTween({ + super.begin, + super.end, + }); + + @override + MarkdownTextSpec lerp(double t) { + if (begin == null && end == null) { + return const MarkdownTextSpec(); + } + + if (begin == null) { + return end!; + } + + return begin!.lerp(end!, t); + } +} + +mixin _$MarkdownListSpec on Spec { + static MarkdownListSpec from(MixData mix) { + return mix.attributeOf()?.resolve(mix) ?? + const MarkdownListSpec(); + } + + /// {@template markdown_list_spec_of} + /// Retrieves the [MarkdownListSpec] from the nearest [Mix] ancestor in the widget tree. + /// + /// This method uses [Mix.of] to obtain the [Mix] instance associated with the + /// given [BuildContext], and then retrieves the [MarkdownListSpec] from that [Mix]. + /// If no ancestor [Mix] is found, this method returns an empty [MarkdownListSpec]. + /// + /// Example: + /// + /// ```dart + /// final markdownListSpec = MarkdownListSpec.of(context); + /// ``` + /// {@endtemplate} + static MarkdownListSpec of(BuildContext context) { + return _$MarkdownListSpec.from(Mix.of(context)); + } + + /// Creates a copy of this [MarkdownListSpec] but with the given fields + /// replaced with the new values. + @override + MarkdownListSpec copyWith({ + TextSpec? bullet, + TextSpec? text, + WrapAlignment? orderedAlignment, + WrapAlignment? unorderedAlignment, + }) { + return MarkdownListSpec( + bullet: bullet ?? _$this.bullet, + text: text ?? _$this.text, + orderedAlignment: orderedAlignment ?? _$this.orderedAlignment, + unorderedAlignment: unorderedAlignment ?? _$this.unorderedAlignment, + ); + } + + /// Linearly interpolates between this [MarkdownListSpec] and another [MarkdownListSpec] based on the given parameter [t]. + /// + /// The parameter [t] represents the interpolation factor, typically ranging from 0.0 to 1.0. + /// When [t] is 0.0, the current [MarkdownListSpec] is returned. When [t] is 1.0, the [other] [MarkdownListSpec] is returned. + /// For values of [t] between 0.0 and 1.0, an interpolated [MarkdownListSpec] is returned. + /// + /// If [other] is null, this method returns the current [MarkdownListSpec] instance. + /// + /// The interpolation is performed on each property of the [MarkdownListSpec] using the appropriate + /// interpolation method: + /// + /// - [TextSpec.lerp] for [bullet] and [text]. + + /// For [orderedAlignment] and [unorderedAlignment], the interpolation is performed using a step function. + /// If [t] is less than 0.5, the value from the current [MarkdownListSpec] is used. Otherwise, the value + /// from the [other] [MarkdownListSpec] is used. + /// + /// This method is typically used in animations to smoothly transition between + /// different [MarkdownListSpec] configurations. + @override + MarkdownListSpec lerp(MarkdownListSpec? other, double t) { + if (other == null) return _$this; + + return MarkdownListSpec( + bullet: _$this.bullet?.lerp(other.bullet, t) ?? other.bullet, + text: _$this.text?.lerp(other.text, t) ?? other.text, + orderedAlignment: + t < 0.5 ? _$this.orderedAlignment : other.orderedAlignment, + unorderedAlignment: + t < 0.5 ? _$this.unorderedAlignment : other.unorderedAlignment, + ); + } + + /// The list of properties that constitute the state of this [MarkdownListSpec]. + /// + /// This property is used by the [==] operator and the [hashCode] getter to + /// compare two [MarkdownListSpec] instances for equality. + @override + List get props => [ + _$this.bullet, + _$this.text, + _$this.orderedAlignment, + _$this.unorderedAlignment, + ]; + + MarkdownListSpec get _$this => this as MarkdownListSpec; +} + +/// Represents the attributes of a [MarkdownListSpec]. +/// +/// This class encapsulates properties defining the layout and +/// appearance of a [MarkdownListSpec]. +/// +/// Use this class to configure the attributes of a [MarkdownListSpec] and pass it to +/// the [MarkdownListSpec] constructor. +final class MarkdownListSpecAttribute extends SpecAttribute { + final TextSpecAttribute? bullet; + final TextSpecAttribute? text; + final WrapAlignment? orderedAlignment; + final WrapAlignment? unorderedAlignment; + + const MarkdownListSpecAttribute({ + this.bullet, + this.text, + this.orderedAlignment, + this.unorderedAlignment, + }); + + /// Resolves to [MarkdownListSpec] using the provided [MixData]. + /// + /// If a property is null in the [MixData], it falls back to the + /// default value defined in the `defaultValue` for that property. + /// + /// ```dart + /// final markdownListSpec = MarkdownListSpecAttribute(...).resolve(mix); + /// ``` + @override + MarkdownListSpec resolve(MixData mix) { + return MarkdownListSpec( + bullet: bullet?.resolve(mix), + text: text?.resolve(mix), + orderedAlignment: orderedAlignment, + unorderedAlignment: unorderedAlignment, + ); + } + + /// Merges the properties of this [MarkdownListSpecAttribute] with the properties of [other]. + /// + /// If [other] is null, returns this instance unchanged. Otherwise, returns a new + /// [MarkdownListSpecAttribute] with the properties of [other] taking precedence over + /// the corresponding properties of this instance. + /// + /// Properties from [other] that are null will fall back + /// to the values from this instance. + @override + MarkdownListSpecAttribute merge(MarkdownListSpecAttribute? other) { + if (other == null) return this; + + return MarkdownListSpecAttribute( + bullet: bullet?.merge(other.bullet) ?? other.bullet, + text: text?.merge(other.text) ?? other.text, + orderedAlignment: other.orderedAlignment ?? orderedAlignment, + unorderedAlignment: other.unorderedAlignment ?? unorderedAlignment, + ); + } + + /// The list of properties that constitute the state of this [MarkdownListSpecAttribute]. + /// + /// This property is used by the [==] operator and the [hashCode] getter to + /// compare two [MarkdownListSpecAttribute] instances for equality. + @override + List get props => [ + bullet, + text, + orderedAlignment, + unorderedAlignment, + ]; +} + +/// Utility class for configuring [MarkdownListSpec] properties. +/// +/// This class provides methods to set individual properties of a [MarkdownListSpec]. +/// Use the methods of this class to configure specific properties of a [MarkdownListSpec]. +class MarkdownListSpecUtility + extends SpecUtility { + /// Utility for defining [MarkdownListSpecAttribute.bullet] + late final bullet = TextSpecUtility((v) => only(bullet: v)); + + /// Utility for defining [MarkdownListSpecAttribute.text] + late final text = TextSpecUtility((v) => only(text: v)); + + /// Utility for defining [MarkdownListSpecAttribute.orderedAlignment] + late final orderedAlignment = + WrapAlignmentUtility((v) => only(orderedAlignment: v)); + + /// Utility for defining [MarkdownListSpecAttribute.unorderedAlignment] + late final unorderedAlignment = + WrapAlignmentUtility((v) => only(unorderedAlignment: v)); + + MarkdownListSpecUtility(super.builder, {super.mutable}); + + MarkdownListSpecUtility get chain => + MarkdownListSpecUtility(attributeBuilder, mutable: true); + + static MarkdownListSpecUtility get self => + MarkdownListSpecUtility((v) => v); + + /// Returns a new [MarkdownListSpecAttribute] with the specified properties. + @override + T only({ + TextSpecAttribute? bullet, + TextSpecAttribute? text, + WrapAlignment? orderedAlignment, + WrapAlignment? unorderedAlignment, + }) { + return builder(MarkdownListSpecAttribute( + bullet: bullet, + text: text, + orderedAlignment: orderedAlignment, + unorderedAlignment: unorderedAlignment, + )); + } +} + +/// A tween that interpolates between two [MarkdownListSpec] instances. +/// +/// This class can be used in animations to smoothly transition between +/// different [MarkdownListSpec] specifications. +class MarkdownListSpecTween extends Tween { + MarkdownListSpecTween({ + super.begin, + super.end, + }); + + @override + MarkdownListSpec lerp(double t) { + if (begin == null && end == null) { + return const MarkdownListSpec(); + } + + if (begin == null) { + return end!; + } + + return begin!.lerp(end!, t); + } +} + +mixin _$MarkdownAlertSpec on Spec { + static MarkdownAlertSpec from(MixData mix) { + return mix.attributeOf()?.resolve(mix) ?? + const MarkdownAlertSpec(); + } + + /// {@template markdown_alert_spec_of} + /// Retrieves the [MarkdownAlertSpec] from the nearest [Mix] ancestor in the widget tree. + /// + /// This method uses [Mix.of] to obtain the [Mix] instance associated with the + /// given [BuildContext], and then retrieves the [MarkdownAlertSpec] from that [Mix]. + /// If no ancestor [Mix] is found, this method returns an empty [MarkdownAlertSpec]. + /// + /// Example: + /// + /// ```dart + /// final markdownAlertSpec = MarkdownAlertSpec.of(context); + /// ``` + /// {@endtemplate} + static MarkdownAlertSpec of(BuildContext context) { + return _$MarkdownAlertSpec.from(Mix.of(context)); + } + + /// Creates a copy of this [MarkdownAlertSpec] but with the given fields + /// replaced with the new values. + @override + MarkdownAlertSpec copyWith({ + MarkdownAlertTypeSpec? note, + MarkdownAlertTypeSpec? tip, + MarkdownAlertTypeSpec? important, + MarkdownAlertTypeSpec? warning, + MarkdownAlertTypeSpec? caution, + }) { + return MarkdownAlertSpec( + note: note ?? _$this.note, + tip: tip ?? _$this.tip, + important: important ?? _$this.important, + warning: warning ?? _$this.warning, + caution: caution ?? _$this.caution, + ); + } + + /// Linearly interpolates between this [MarkdownAlertSpec] and another [MarkdownAlertSpec] based on the given parameter [t]. + /// + /// The parameter [t] represents the interpolation factor, typically ranging from 0.0 to 1.0. + /// When [t] is 0.0, the current [MarkdownAlertSpec] is returned. When [t] is 1.0, the [other] [MarkdownAlertSpec] is returned. + /// For values of [t] between 0.0 and 1.0, an interpolated [MarkdownAlertSpec] is returned. + /// + /// If [other] is null, this method returns the current [MarkdownAlertSpec] instance. + /// + /// The interpolation is performed on each property of the [MarkdownAlertSpec] using the appropriate + /// interpolation method: + /// + + /// For [note] and [tip] and [important] and [warning] and [caution], the interpolation is performed using a step function. + /// If [t] is less than 0.5, the value from the current [MarkdownAlertSpec] is used. Otherwise, the value + /// from the [other] [MarkdownAlertSpec] is used. + /// + /// This method is typically used in animations to smoothly transition between + /// different [MarkdownAlertSpec] configurations. + @override + MarkdownAlertSpec lerp(MarkdownAlertSpec? other, double t) { + if (other == null) return _$this; + + return MarkdownAlertSpec( + note: _$this.note.lerp(other.note, t), + tip: _$this.tip.lerp(other.tip, t), + important: _$this.important.lerp(other.important, t), + warning: _$this.warning.lerp(other.warning, t), + caution: _$this.caution.lerp(other.caution, t), + ); + } + + /// The list of properties that constitute the state of this [MarkdownAlertSpec]. + /// + /// This property is used by the [==] operator and the [hashCode] getter to + /// compare two [MarkdownAlertSpec] instances for equality. + @override + List get props => [ + _$this.note, + _$this.tip, + _$this.important, + _$this.warning, + _$this.caution, + ]; + + MarkdownAlertSpec get _$this => this as MarkdownAlertSpec; +} + +/// Represents the attributes of a [MarkdownAlertSpec]. +/// +/// This class encapsulates properties defining the layout and +/// appearance of a [MarkdownAlertSpec]. +/// +/// Use this class to configure the attributes of a [MarkdownAlertSpec] and pass it to +/// the [MarkdownAlertSpec] constructor. +final class MarkdownAlertSpecAttribute + extends SpecAttribute { + final MarkdownAlertTypeSpecAttribute? note; + final MarkdownAlertTypeSpecAttribute? tip; + final MarkdownAlertTypeSpecAttribute? important; + final MarkdownAlertTypeSpecAttribute? warning; + final MarkdownAlertTypeSpecAttribute? caution; + + const MarkdownAlertSpecAttribute({ + this.note, + this.tip, + this.important, + this.warning, + this.caution, + }); + + /// Resolves to [MarkdownAlertSpec] using the provided [MixData]. + /// + /// If a property is null in the [MixData], it falls back to the + /// default value defined in the `defaultValue` for that property. + /// + /// ```dart + /// final markdownAlertSpec = MarkdownAlertSpecAttribute(...).resolve(mix); + /// ``` + @override + MarkdownAlertSpec resolve(MixData mix) { + return MarkdownAlertSpec( + note: note?.resolve(mix), + tip: tip?.resolve(mix), + important: important?.resolve(mix), + warning: warning?.resolve(mix), + caution: caution?.resolve(mix), + ); + } + + /// Merges the properties of this [MarkdownAlertSpecAttribute] with the properties of [other]. + /// + /// If [other] is null, returns this instance unchanged. Otherwise, returns a new + /// [MarkdownAlertSpecAttribute] with the properties of [other] taking precedence over + /// the corresponding properties of this instance. + /// + /// Properties from [other] that are null will fall back + /// to the values from this instance. + @override + MarkdownAlertSpecAttribute merge(MarkdownAlertSpecAttribute? other) { + if (other == null) return this; + + return MarkdownAlertSpecAttribute( + note: note?.merge(other.note) ?? other.note, + tip: tip?.merge(other.tip) ?? other.tip, + important: important?.merge(other.important) ?? other.important, + warning: warning?.merge(other.warning) ?? other.warning, + caution: caution?.merge(other.caution) ?? other.caution, + ); + } + + /// The list of properties that constitute the state of this [MarkdownAlertSpecAttribute]. + /// + /// This property is used by the [==] operator and the [hashCode] getter to + /// compare two [MarkdownAlertSpecAttribute] instances for equality. + @override + List get props => [ + note, + tip, + important, + warning, + caution, + ]; +} + +/// Utility class for configuring [MarkdownAlertSpec] properties. +/// +/// This class provides methods to set individual properties of a [MarkdownAlertSpec]. +/// Use the methods of this class to configure specific properties of a [MarkdownAlertSpec]. +class MarkdownAlertSpecUtility + extends SpecUtility { + /// Utility for defining [MarkdownAlertSpecAttribute.note] + late final note = MarkdownAlertTypeSpecUtility((v) => only(note: v)); + + /// Utility for defining [MarkdownAlertSpecAttribute.tip] + late final tip = MarkdownAlertTypeSpecUtility((v) => only(tip: v)); + + /// Utility for defining [MarkdownAlertSpecAttribute.important] + late final important = + MarkdownAlertTypeSpecUtility((v) => only(important: v)); + + /// Utility for defining [MarkdownAlertSpecAttribute.warning] + late final warning = MarkdownAlertTypeSpecUtility((v) => only(warning: v)); + + /// Utility for defining [MarkdownAlertSpecAttribute.caution] + late final caution = MarkdownAlertTypeSpecUtility((v) => only(caution: v)); + + MarkdownAlertSpecUtility(super.builder, {super.mutable}); + + MarkdownAlertSpecUtility get chain => + MarkdownAlertSpecUtility(attributeBuilder, mutable: true); + + static MarkdownAlertSpecUtility get self => + MarkdownAlertSpecUtility((v) => v); + + /// Returns a new [MarkdownAlertSpecAttribute] with the specified properties. + @override + T only({ + MarkdownAlertTypeSpecAttribute? note, + MarkdownAlertTypeSpecAttribute? tip, + MarkdownAlertTypeSpecAttribute? important, + MarkdownAlertTypeSpecAttribute? warning, + MarkdownAlertTypeSpecAttribute? caution, + }) { + return builder(MarkdownAlertSpecAttribute( + note: note, + tip: tip, + important: important, + warning: warning, + caution: caution, + )); + } +} + +/// A tween that interpolates between two [MarkdownAlertSpec] instances. +/// +/// This class can be used in animations to smoothly transition between +/// different [MarkdownAlertSpec] specifications. +class MarkdownAlertSpecTween extends Tween { + MarkdownAlertSpecTween({ + super.begin, + super.end, + }); + + @override + MarkdownAlertSpec lerp(double t) { + if (begin == null && end == null) { + return const MarkdownAlertSpec(); + } + + if (begin == null) { + return end!; + } + + return begin!.lerp(end!, t); + } +} + +mixin _$MarkdownAlertTypeSpec on Spec { + static MarkdownAlertTypeSpec from(MixData mix) { + return mix.attributeOf()?.resolve(mix) ?? + const MarkdownAlertTypeSpec(); + } + + /// {@template markdown_alert_type_spec_of} + /// Retrieves the [MarkdownAlertTypeSpec] from the nearest [Mix] ancestor in the widget tree. + /// + /// This method uses [Mix.of] to obtain the [Mix] instance associated with the + /// given [BuildContext], and then retrieves the [MarkdownAlertTypeSpec] from that [Mix]. + /// If no ancestor [Mix] is found, this method returns an empty [MarkdownAlertTypeSpec]. + /// + /// Example: + /// + /// ```dart + /// final markdownAlertTypeSpec = MarkdownAlertTypeSpec.of(context); + /// ``` + /// {@endtemplate} + static MarkdownAlertTypeSpec of(BuildContext context) { + return _$MarkdownAlertTypeSpec.from(Mix.of(context)); + } + + /// Creates a copy of this [MarkdownAlertTypeSpec] but with the given fields + /// replaced with the new values. + @override + MarkdownAlertTypeSpec copyWith({ + TextSpec? heading, + TextSpec? description, + IconSpec? icon, + BoxSpec? container, + FlexSpec? headingFlex, + FlexSpec? containerFlex, + }) { + return MarkdownAlertTypeSpec( + heading: heading ?? _$this.heading, + description: description ?? _$this.description, + icon: icon ?? _$this.icon, + container: container ?? _$this.container, + headingFlex: headingFlex ?? _$this.headingFlex, + containerFlex: containerFlex ?? _$this.containerFlex, + ); + } + + /// Linearly interpolates between this [MarkdownAlertTypeSpec] and another [MarkdownAlertTypeSpec] based on the given parameter [t]. + /// + /// The parameter [t] represents the interpolation factor, typically ranging from 0.0 to 1.0. + /// When [t] is 0.0, the current [MarkdownAlertTypeSpec] is returned. When [t] is 1.0, the [other] [MarkdownAlertTypeSpec] is returned. + /// For values of [t] between 0.0 and 1.0, an interpolated [MarkdownAlertTypeSpec] is returned. + /// + /// If [other] is null, this method returns the current [MarkdownAlertTypeSpec] instance. + /// + /// The interpolation is performed on each property of the [MarkdownAlertTypeSpec] using the appropriate + /// interpolation method: + /// + /// - [TextSpec.lerp] for [heading] and [description]. + /// - [IconSpec.lerp] for [icon]. + /// - [BoxSpec.lerp] for [container]. + /// - [FlexSpec.lerp] for [headingFlex] and [containerFlex]. + + /// For , the interpolation is performed using a step function. + /// If [t] is less than 0.5, the value from the current [MarkdownAlertTypeSpec] is used. Otherwise, the value + /// from the [other] [MarkdownAlertTypeSpec] is used. + /// + /// This method is typically used in animations to smoothly transition between + /// different [MarkdownAlertTypeSpec] configurations. + @override + MarkdownAlertTypeSpec lerp(MarkdownAlertTypeSpec? other, double t) { + if (other == null) return _$this; + + return MarkdownAlertTypeSpec( + heading: _$this.heading.lerp(other.heading, t), + description: _$this.description.lerp(other.description, t), + icon: _$this.icon.lerp(other.icon, t), + container: _$this.container.lerp(other.container, t), + headingFlex: _$this.headingFlex.lerp(other.headingFlex, t), + containerFlex: _$this.containerFlex.lerp(other.containerFlex, t), + ); + } + + /// The list of properties that constitute the state of this [MarkdownAlertTypeSpec]. + /// + /// This property is used by the [==] operator and the [hashCode] getter to + /// compare two [MarkdownAlertTypeSpec] instances for equality. + @override + List get props => [ + _$this.heading, + _$this.description, + _$this.icon, + _$this.container, + _$this.headingFlex, + _$this.containerFlex, + ]; + + MarkdownAlertTypeSpec get _$this => this as MarkdownAlertTypeSpec; +} + +/// Represents the attributes of a [MarkdownAlertTypeSpec]. +/// +/// This class encapsulates properties defining the layout and +/// appearance of a [MarkdownAlertTypeSpec]. +/// +/// Use this class to configure the attributes of a [MarkdownAlertTypeSpec] and pass it to +/// the [MarkdownAlertTypeSpec] constructor. +final class MarkdownAlertTypeSpecAttribute + extends SpecAttribute { + final TextSpecAttribute? heading; + final TextSpecAttribute? description; + final IconSpecAttribute? icon; + final BoxSpecAttribute? container; + final FlexSpecAttribute? headingFlex; + final FlexSpecAttribute? containerFlex; + + const MarkdownAlertTypeSpecAttribute({ + this.heading, + this.description, + this.icon, + this.container, + this.headingFlex, + this.containerFlex, + }); + + /// Resolves to [MarkdownAlertTypeSpec] using the provided [MixData]. + /// + /// If a property is null in the [MixData], it falls back to the + /// default value defined in the `defaultValue` for that property. + /// + /// ```dart + /// final markdownAlertTypeSpec = MarkdownAlertTypeSpecAttribute(...).resolve(mix); + /// ``` + @override + MarkdownAlertTypeSpec resolve(MixData mix) { + return MarkdownAlertTypeSpec( + heading: heading?.resolve(mix), + description: description?.resolve(mix), + icon: icon?.resolve(mix), + container: container?.resolve(mix), + headingFlex: headingFlex?.resolve(mix), + containerFlex: containerFlex?.resolve(mix), + ); + } + + /// Merges the properties of this [MarkdownAlertTypeSpecAttribute] with the properties of [other]. + /// + /// If [other] is null, returns this instance unchanged. Otherwise, returns a new + /// [MarkdownAlertTypeSpecAttribute] with the properties of [other] taking precedence over + /// the corresponding properties of this instance. + /// + /// Properties from [other] that are null will fall back + /// to the values from this instance. + @override + MarkdownAlertTypeSpecAttribute merge(MarkdownAlertTypeSpecAttribute? other) { + if (other == null) return this; + + return MarkdownAlertTypeSpecAttribute( + heading: heading?.merge(other.heading) ?? other.heading, + description: description?.merge(other.description) ?? other.description, + icon: icon?.merge(other.icon) ?? other.icon, + container: container?.merge(other.container) ?? other.container, + headingFlex: headingFlex?.merge(other.headingFlex) ?? other.headingFlex, + containerFlex: + containerFlex?.merge(other.containerFlex) ?? other.containerFlex, + ); + } + + /// The list of properties that constitute the state of this [MarkdownAlertTypeSpecAttribute]. + /// + /// This property is used by the [==] operator and the [hashCode] getter to + /// compare two [MarkdownAlertTypeSpecAttribute] instances for equality. + @override + List get props => [ + heading, + description, + icon, + container, + headingFlex, + containerFlex, + ]; +} + +/// Utility class for configuring [MarkdownAlertTypeSpec] properties. +/// +/// This class provides methods to set individual properties of a [MarkdownAlertTypeSpec]. +/// Use the methods of this class to configure specific properties of a [MarkdownAlertTypeSpec]. +class MarkdownAlertTypeSpecUtility + extends SpecUtility { + /// Utility for defining [MarkdownAlertTypeSpecAttribute.heading] + late final heading = TextSpecUtility((v) => only(heading: v)); + + /// Utility for defining [MarkdownAlertTypeSpecAttribute.description] + late final description = TextSpecUtility((v) => only(description: v)); + + /// Utility for defining [MarkdownAlertTypeSpecAttribute.icon] + late final icon = IconSpecUtility((v) => only(icon: v)); + + /// Utility for defining [MarkdownAlertTypeSpecAttribute.container] + late final container = BoxSpecUtility((v) => only(container: v)); + + /// Utility for defining [MarkdownAlertTypeSpecAttribute.headingFlex] + late final headingFlex = FlexSpecUtility((v) => only(headingFlex: v)); + + /// Utility for defining [MarkdownAlertTypeSpecAttribute.containerFlex] + late final containerFlex = FlexSpecUtility((v) => only(containerFlex: v)); + + MarkdownAlertTypeSpecUtility(super.builder, {super.mutable}); + + MarkdownAlertTypeSpecUtility get chain => + MarkdownAlertTypeSpecUtility(attributeBuilder, mutable: true); + + static MarkdownAlertTypeSpecUtility + get self => MarkdownAlertTypeSpecUtility((v) => v); + + /// Returns a new [MarkdownAlertTypeSpecAttribute] with the specified properties. + @override + T only({ + TextSpecAttribute? heading, + TextSpecAttribute? description, + IconSpecAttribute? icon, + BoxSpecAttribute? container, + FlexSpecAttribute? headingFlex, + FlexSpecAttribute? containerFlex, + }) { + return builder(MarkdownAlertTypeSpecAttribute( + heading: heading, + description: description, + icon: icon, + container: container, + headingFlex: headingFlex, + containerFlex: containerFlex, + )); + } +} + +/// A tween that interpolates between two [MarkdownAlertTypeSpec] instances. +/// +/// This class can be used in animations to smoothly transition between +/// different [MarkdownAlertTypeSpec] specifications. +class MarkdownAlertTypeSpecTween extends Tween { + MarkdownAlertTypeSpecTween({ + super.begin, + super.end, + }); + + @override + MarkdownAlertTypeSpec lerp(double t) { + if (begin == null && end == null) { + return const MarkdownAlertTypeSpec(); + } + + if (begin == null) { + return end!; + } + + return begin!.lerp(end!, t); + } +} + +mixin _$MarkdownTableSpec on Spec { + static MarkdownTableSpec from(MixData mix) { + return mix.attributeOf()?.resolve(mix) ?? + const MarkdownTableSpec(); + } + + /// {@template markdown_table_spec_of} + /// Retrieves the [MarkdownTableSpec] from the nearest [Mix] ancestor in the widget tree. + /// + /// This method uses [Mix.of] to obtain the [Mix] instance associated with the + /// given [BuildContext], and then retrieves the [MarkdownTableSpec] from that [Mix]. + /// If no ancestor [Mix] is found, this method returns an empty [MarkdownTableSpec]. + /// + /// Example: + /// + /// ```dart + /// final markdownTableSpec = MarkdownTableSpec.of(context); + /// ``` + /// {@endtemplate} + static MarkdownTableSpec of(BuildContext context) { + return _$MarkdownTableSpec.from(Mix.of(context)); + } + + /// Creates a copy of this [MarkdownTableSpec] but with the given fields + /// replaced with the new values. + @override + MarkdownTableSpec copyWith({ + TextStyle? headStyle, + TextStyle? bodyStyle, + TextAlign? headAlignment, + EdgeInsets? padding, + TableBorder? border, + TableColumnWidth? columnWidth, + EdgeInsets? cellPadding, + BoxDecoration? cellDecoration, + TableCellVerticalAlignment? verticalAlignment, + WidgetModifiersData? modifiers, + AnimatedData? animated, + }) { + return MarkdownTableSpec( + headStyle: headStyle ?? _$this.headStyle, + bodyStyle: bodyStyle ?? _$this.bodyStyle, + headAlignment: headAlignment ?? _$this.headAlignment, + padding: padding ?? _$this.padding, + border: border ?? _$this.border, + columnWidth: columnWidth ?? _$this.columnWidth, + cellPadding: cellPadding ?? _$this.cellPadding, + cellDecoration: cellDecoration ?? _$this.cellDecoration, + verticalAlignment: verticalAlignment ?? _$this.verticalAlignment, + modifiers: modifiers ?? _$this.modifiers, + animated: animated ?? _$this.animated, + ); + } + + /// Linearly interpolates between this [MarkdownTableSpec] and another [MarkdownTableSpec] based on the given parameter [t]. + /// + /// The parameter [t] represents the interpolation factor, typically ranging from 0.0 to 1.0. + /// When [t] is 0.0, the current [MarkdownTableSpec] is returned. When [t] is 1.0, the [other] [MarkdownTableSpec] is returned. + /// For values of [t] between 0.0 and 1.0, an interpolated [MarkdownTableSpec] is returned. + /// + /// If [other] is null, this method returns the current [MarkdownTableSpec] instance. + /// + /// The interpolation is performed on each property of the [MarkdownTableSpec] using the appropriate + /// interpolation method: + /// + /// - [MixHelpers.lerpTextStyle] for [headStyle] and [bodyStyle]. + /// - [EdgeInsets.lerp] for [padding] and [cellPadding]. + /// - [TableBorder.lerp] for [border]. + /// - [BoxDecoration.lerp] for [cellDecoration]. + + /// For [headAlignment] and [columnWidth] and [verticalAlignment] and [modifiers] and [animated], the interpolation is performed using a step function. + /// If [t] is less than 0.5, the value from the current [MarkdownTableSpec] is used. Otherwise, the value + /// from the [other] [MarkdownTableSpec] is used. + /// + /// This method is typically used in animations to smoothly transition between + /// different [MarkdownTableSpec] configurations. + @override + MarkdownTableSpec lerp(MarkdownTableSpec? other, double t) { + if (other == null) return _$this; + + return MarkdownTableSpec( + headStyle: MixHelpers.lerpTextStyle(_$this.headStyle, other.headStyle, t), + bodyStyle: MixHelpers.lerpTextStyle(_$this.bodyStyle, other.bodyStyle, t), + headAlignment: t < 0.5 ? _$this.headAlignment : other.headAlignment, + padding: EdgeInsets.lerp(_$this.padding, other.padding, t), + border: TableBorder.lerp(_$this.border, other.border, t), + columnWidth: t < 0.5 ? _$this.columnWidth : other.columnWidth, + cellPadding: EdgeInsets.lerp(_$this.cellPadding, other.cellPadding, t), + cellDecoration: + BoxDecoration.lerp(_$this.cellDecoration, other.cellDecoration, t), + verticalAlignment: + t < 0.5 ? _$this.verticalAlignment : other.verticalAlignment, + modifiers: other.modifiers, + animated: t < 0.5 ? _$this.animated : other.animated, + ); + } + + /// The list of properties that constitute the state of this [MarkdownTableSpec]. + /// + /// This property is used by the [==] operator and the [hashCode] getter to + /// compare two [MarkdownTableSpec] instances for equality. + @override + List get props => [ + _$this.headStyle, + _$this.bodyStyle, + _$this.headAlignment, + _$this.padding, + _$this.border, + _$this.columnWidth, + _$this.cellPadding, + _$this.cellDecoration, + _$this.verticalAlignment, + _$this.modifiers, + _$this.animated, + ]; + + MarkdownTableSpec get _$this => this as MarkdownTableSpec; +} + +/// Represents the attributes of a [MarkdownTableSpec]. +/// +/// This class encapsulates properties defining the layout and +/// appearance of a [MarkdownTableSpec]. +/// +/// Use this class to configure the attributes of a [MarkdownTableSpec] and pass it to +/// the [MarkdownTableSpec] constructor. +final class MarkdownTableSpecAttribute + extends SpecAttribute { + final TextStyleDto? headStyle; + final TextStyleDto? bodyStyle; + final TextAlign? headAlignment; + final EdgeInsetsDto? padding; + final TableBorder? border; + final TableColumnWidth? columnWidth; + final EdgeInsetsDto? cellPadding; + final BoxDecorationDto? cellDecoration; + final TableCellVerticalAlignment? verticalAlignment; + + const MarkdownTableSpecAttribute({ + this.headStyle, + this.bodyStyle, + this.headAlignment, + this.padding, + this.border, + this.columnWidth, + this.cellPadding, + this.cellDecoration, + this.verticalAlignment, + super.modifiers, + super.animated, + }); + + /// Resolves to [MarkdownTableSpec] using the provided [MixData]. + /// + /// If a property is null in the [MixData], it falls back to the + /// default value defined in the `defaultValue` for that property. + /// + /// ```dart + /// final markdownTableSpec = MarkdownTableSpecAttribute(...).resolve(mix); + /// ``` + @override + MarkdownTableSpec resolve(MixData mix) { + return MarkdownTableSpec( + headStyle: headStyle?.resolve(mix), + bodyStyle: bodyStyle?.resolve(mix), + headAlignment: headAlignment, + padding: padding?.resolve(mix), + border: border, + columnWidth: columnWidth, + cellPadding: cellPadding?.resolve(mix), + cellDecoration: cellDecoration?.resolve(mix), + verticalAlignment: verticalAlignment, + modifiers: modifiers?.resolve(mix), + animated: animated?.resolve(mix) ?? mix.animation, + ); + } + + /// Merges the properties of this [MarkdownTableSpecAttribute] with the properties of [other]. + /// + /// If [other] is null, returns this instance unchanged. Otherwise, returns a new + /// [MarkdownTableSpecAttribute] with the properties of [other] taking precedence over + /// the corresponding properties of this instance. + /// + /// Properties from [other] that are null will fall back + /// to the values from this instance. + @override + MarkdownTableSpecAttribute merge(MarkdownTableSpecAttribute? other) { + if (other == null) return this; + + return MarkdownTableSpecAttribute( + headStyle: headStyle?.merge(other.headStyle) ?? other.headStyle, + bodyStyle: bodyStyle?.merge(other.bodyStyle) ?? other.bodyStyle, + headAlignment: other.headAlignment ?? headAlignment, + padding: padding?.merge(other.padding) ?? other.padding, + border: other.border ?? border, + columnWidth: other.columnWidth ?? columnWidth, + cellPadding: cellPadding?.merge(other.cellPadding) ?? other.cellPadding, + cellDecoration: + cellDecoration?.merge(other.cellDecoration) ?? other.cellDecoration, + verticalAlignment: other.verticalAlignment ?? verticalAlignment, + modifiers: modifiers?.merge(other.modifiers) ?? other.modifiers, + animated: animated?.merge(other.animated) ?? other.animated, + ); + } + + /// The list of properties that constitute the state of this [MarkdownTableSpecAttribute]. + /// + /// This property is used by the [==] operator and the [hashCode] getter to + /// compare two [MarkdownTableSpecAttribute] instances for equality. + @override + List get props => [ + headStyle, + bodyStyle, + headAlignment, + padding, + border, + columnWidth, + cellPadding, + cellDecoration, + verticalAlignment, + modifiers, + animated, + ]; +} + +/// Utility class for configuring [MarkdownTableSpec] properties. +/// +/// This class provides methods to set individual properties of a [MarkdownTableSpec]. +/// Use the methods of this class to configure specific properties of a [MarkdownTableSpec]. +class MarkdownTableSpecUtility + extends SpecUtility { + /// Utility for defining [MarkdownTableSpecAttribute.headStyle] + late final headStyle = TextStyleUtility((v) => only(headStyle: v)); + + /// Utility for defining [MarkdownTableSpecAttribute.bodyStyle] + late final bodyStyle = TextStyleUtility((v) => only(bodyStyle: v)); + + /// Utility for defining [MarkdownTableSpecAttribute.headAlignment] + late final headAlignment = TextAlignUtility((v) => only(headAlignment: v)); + + /// Utility for defining [MarkdownTableSpecAttribute.padding] + late final padding = EdgeInsetsUtility((v) => only(padding: v)); + + /// Utility for defining [MarkdownTableSpecAttribute.border] + late final border = TableBorderUtility((v) => only(border: v)); + + /// Utility for defining [MarkdownTableSpecAttribute.columnWidth] + late final columnWidth = TableColumnWidthUtility((v) => only(columnWidth: v)); + + /// Utility for defining [MarkdownTableSpecAttribute.cellPadding] + late final cellPadding = EdgeInsetsUtility((v) => only(cellPadding: v)); + + /// Utility for defining [MarkdownTableSpecAttribute.cellDecoration] + late final cellDecoration = + BoxDecorationUtility((v) => only(cellDecoration: v)); + + /// Utility for defining [MarkdownTableSpecAttribute.verticalAlignment] + late final verticalAlignment = + TableCellVerticalAlignmentUtility((v) => only(verticalAlignment: v)); + + /// Utility for defining [MarkdownTableSpecAttribute.modifiers] + late final wrap = SpecModifierUtility((v) => only(modifiers: v)); + + /// Utility for defining [MarkdownTableSpecAttribute.animated] + late final animated = AnimatedUtility((v) => only(animated: v)); + + MarkdownTableSpecUtility(super.builder, {super.mutable}); + + MarkdownTableSpecUtility get chain => + MarkdownTableSpecUtility(attributeBuilder, mutable: true); + + static MarkdownTableSpecUtility get self => + MarkdownTableSpecUtility((v) => v); + + /// Returns a new [MarkdownTableSpecAttribute] with the specified properties. + @override + T only({ + TextStyleDto? headStyle, + TextStyleDto? bodyStyle, + TextAlign? headAlignment, + EdgeInsetsDto? padding, + TableBorder? border, + TableColumnWidth? columnWidth, + EdgeInsetsDto? cellPadding, + BoxDecorationDto? cellDecoration, + TableCellVerticalAlignment? verticalAlignment, + WidgetModifiersDataDto? modifiers, + AnimatedDataDto? animated, + }) { + return builder(MarkdownTableSpecAttribute( + headStyle: headStyle, + bodyStyle: bodyStyle, + headAlignment: headAlignment, + padding: padding, + border: border, + columnWidth: columnWidth, + cellPadding: cellPadding, + cellDecoration: cellDecoration, + verticalAlignment: verticalAlignment, + modifiers: modifiers, + animated: animated, + )); + } +} + +/// A tween that interpolates between two [MarkdownTableSpec] instances. +/// +/// This class can be used in animations to smoothly transition between +/// different [MarkdownTableSpec] specifications. +class MarkdownTableSpecTween extends Tween { + MarkdownTableSpecTween({ + super.begin, + super.end, + }); + + @override + MarkdownTableSpec lerp(double t) { + if (begin == null && end == null) { + return const MarkdownTableSpec(); + } + + if (begin == null) { + return end!; + } + + return begin!.lerp(end!, t); + } +} + +mixin _$MarkdownBlockquoteSpec on Spec { + static MarkdownBlockquoteSpec from(MixData mix) { + return mix.attributeOf()?.resolve(mix) ?? + const MarkdownBlockquoteSpec(); + } + + /// {@template markdown_blockquote_spec_of} + /// Retrieves the [MarkdownBlockquoteSpec] from the nearest [Mix] ancestor in the widget tree. + /// + /// This method uses [Mix.of] to obtain the [Mix] instance associated with the + /// given [BuildContext], and then retrieves the [MarkdownBlockquoteSpec] from that [Mix]. + /// If no ancestor [Mix] is found, this method returns an empty [MarkdownBlockquoteSpec]. + /// + /// Example: + /// + /// ```dart + /// final markdownBlockquoteSpec = MarkdownBlockquoteSpec.of(context); + /// ``` + /// {@endtemplate} + static MarkdownBlockquoteSpec of(BuildContext context) { + return _$MarkdownBlockquoteSpec.from(Mix.of(context)); + } + + /// Creates a copy of this [MarkdownBlockquoteSpec] but with the given fields + /// replaced with the new values. + @override + MarkdownBlockquoteSpec copyWith({ + TextStyle? textStyle, + EdgeInsets? padding, + BoxDecoration? decoration, + WrapAlignment? alignment, + WidgetModifiersData? modifiers, + AnimatedData? animated, + }) { + return MarkdownBlockquoteSpec( + textStyle: textStyle ?? _$this.textStyle, + padding: padding ?? _$this.padding, + decoration: decoration ?? _$this.decoration, + alignment: alignment ?? _$this.alignment, + modifiers: modifiers ?? _$this.modifiers, + animated: animated ?? _$this.animated, + ); + } + + /// Linearly interpolates between this [MarkdownBlockquoteSpec] and another [MarkdownBlockquoteSpec] based on the given parameter [t]. + /// + /// The parameter [t] represents the interpolation factor, typically ranging from 0.0 to 1.0. + /// When [t] is 0.0, the current [MarkdownBlockquoteSpec] is returned. When [t] is 1.0, the [other] [MarkdownBlockquoteSpec] is returned. + /// For values of [t] between 0.0 and 1.0, an interpolated [MarkdownBlockquoteSpec] is returned. + /// + /// If [other] is null, this method returns the current [MarkdownBlockquoteSpec] instance. + /// + /// The interpolation is performed on each property of the [MarkdownBlockquoteSpec] using the appropriate + /// interpolation method: + /// + /// - [MixHelpers.lerpTextStyle] for [textStyle]. + /// - [EdgeInsets.lerp] for [padding]. + /// - [BoxDecoration.lerp] for [decoration]. + + /// For [alignment] and [modifiers] and [animated], the interpolation is performed using a step function. + /// If [t] is less than 0.5, the value from the current [MarkdownBlockquoteSpec] is used. Otherwise, the value + /// from the [other] [MarkdownBlockquoteSpec] is used. + /// + /// This method is typically used in animations to smoothly transition between + /// different [MarkdownBlockquoteSpec] configurations. + @override + MarkdownBlockquoteSpec lerp(MarkdownBlockquoteSpec? other, double t) { + if (other == null) return _$this; + + return MarkdownBlockquoteSpec( + textStyle: MixHelpers.lerpTextStyle(_$this.textStyle, other.textStyle, t), + padding: EdgeInsets.lerp(_$this.padding, other.padding, t), + decoration: BoxDecoration.lerp(_$this.decoration, other.decoration, t), + alignment: t < 0.5 ? _$this.alignment : other.alignment, + modifiers: other.modifiers, + animated: t < 0.5 ? _$this.animated : other.animated, + ); + } + + /// The list of properties that constitute the state of this [MarkdownBlockquoteSpec]. + /// + /// This property is used by the [==] operator and the [hashCode] getter to + /// compare two [MarkdownBlockquoteSpec] instances for equality. + @override + List get props => [ + _$this.textStyle, + _$this.padding, + _$this.decoration, + _$this.alignment, + _$this.modifiers, + _$this.animated, + ]; + + MarkdownBlockquoteSpec get _$this => this as MarkdownBlockquoteSpec; +} + +/// Represents the attributes of a [MarkdownBlockquoteSpec]. +/// +/// This class encapsulates properties defining the layout and +/// appearance of a [MarkdownBlockquoteSpec]. +/// +/// Use this class to configure the attributes of a [MarkdownBlockquoteSpec] and pass it to +/// the [MarkdownBlockquoteSpec] constructor. +final class MarkdownBlockquoteSpecAttribute + extends SpecAttribute { + final TextStyleDto? textStyle; + final EdgeInsetsDto? padding; + final BoxDecorationDto? decoration; + final WrapAlignment? alignment; + + const MarkdownBlockquoteSpecAttribute({ + this.textStyle, + this.padding, + this.decoration, + this.alignment, + super.modifiers, + super.animated, + }); + + /// Resolves to [MarkdownBlockquoteSpec] using the provided [MixData]. + /// + /// If a property is null in the [MixData], it falls back to the + /// default value defined in the `defaultValue` for that property. + /// + /// ```dart + /// final markdownBlockquoteSpec = MarkdownBlockquoteSpecAttribute(...).resolve(mix); + /// ``` + @override + MarkdownBlockquoteSpec resolve(MixData mix) { + return MarkdownBlockquoteSpec( + textStyle: textStyle?.resolve(mix), + padding: padding?.resolve(mix), + decoration: decoration?.resolve(mix), + alignment: alignment, + modifiers: modifiers?.resolve(mix), + animated: animated?.resolve(mix) ?? mix.animation, + ); + } + + /// Merges the properties of this [MarkdownBlockquoteSpecAttribute] with the properties of [other]. + /// + /// If [other] is null, returns this instance unchanged. Otherwise, returns a new + /// [MarkdownBlockquoteSpecAttribute] with the properties of [other] taking precedence over + /// the corresponding properties of this instance. + /// + /// Properties from [other] that are null will fall back + /// to the values from this instance. + @override + MarkdownBlockquoteSpecAttribute merge( + MarkdownBlockquoteSpecAttribute? other) { + if (other == null) return this; + + return MarkdownBlockquoteSpecAttribute( + textStyle: textStyle?.merge(other.textStyle) ?? other.textStyle, + padding: padding?.merge(other.padding) ?? other.padding, + decoration: decoration?.merge(other.decoration) ?? other.decoration, + alignment: other.alignment ?? alignment, + modifiers: modifiers?.merge(other.modifiers) ?? other.modifiers, + animated: animated?.merge(other.animated) ?? other.animated, + ); + } + + /// The list of properties that constitute the state of this [MarkdownBlockquoteSpecAttribute]. + /// + /// This property is used by the [==] operator and the [hashCode] getter to + /// compare two [MarkdownBlockquoteSpecAttribute] instances for equality. + @override + List get props => [ + textStyle, + padding, + decoration, + alignment, + modifiers, + animated, + ]; +} + +/// Utility class for configuring [MarkdownBlockquoteSpec] properties. +/// +/// This class provides methods to set individual properties of a [MarkdownBlockquoteSpec]. +/// Use the methods of this class to configure specific properties of a [MarkdownBlockquoteSpec]. +class MarkdownBlockquoteSpecUtility + extends SpecUtility { + /// Utility for defining [MarkdownBlockquoteSpecAttribute.textStyle] + late final textStyle = TextStyleUtility((v) => only(textStyle: v)); + + /// Utility for defining [MarkdownBlockquoteSpecAttribute.padding] + late final padding = EdgeInsetsUtility((v) => only(padding: v)); + + /// Utility for defining [MarkdownBlockquoteSpecAttribute.decoration] + late final decoration = BoxDecorationUtility((v) => only(decoration: v)); + + /// Utility for defining [MarkdownBlockquoteSpecAttribute.alignment] + late final alignment = WrapAlignmentUtility((v) => only(alignment: v)); + + /// Utility for defining [MarkdownBlockquoteSpecAttribute.modifiers] + late final wrap = SpecModifierUtility((v) => only(modifiers: v)); + + /// Utility for defining [MarkdownBlockquoteSpecAttribute.animated] + late final animated = AnimatedUtility((v) => only(animated: v)); + + MarkdownBlockquoteSpecUtility(super.builder, {super.mutable}); + + MarkdownBlockquoteSpecUtility get chain => + MarkdownBlockquoteSpecUtility(attributeBuilder, mutable: true); + + static MarkdownBlockquoteSpecUtility + get self => MarkdownBlockquoteSpecUtility((v) => v); + + /// Returns a new [MarkdownBlockquoteSpecAttribute] with the specified properties. + @override + T only({ + TextStyleDto? textStyle, + EdgeInsetsDto? padding, + BoxDecorationDto? decoration, + WrapAlignment? alignment, + WidgetModifiersDataDto? modifiers, + AnimatedDataDto? animated, + }) { + return builder(MarkdownBlockquoteSpecAttribute( + textStyle: textStyle, + padding: padding, + decoration: decoration, + alignment: alignment, + modifiers: modifiers, + animated: animated, + )); + } +} + +/// A tween that interpolates between two [MarkdownBlockquoteSpec] instances. +/// +/// This class can be used in animations to smoothly transition between +/// different [MarkdownBlockquoteSpec] specifications. +class MarkdownBlockquoteSpecTween extends Tween { + MarkdownBlockquoteSpecTween({ + super.begin, + super.end, + }); + + @override + MarkdownBlockquoteSpec lerp(double t) { + if (begin == null && end == null) { + return const MarkdownBlockquoteSpec(); + } + + if (begin == null) { + return end!; + } + + return begin!.lerp(end!, t); + } +} + +mixin _$MarkdownCodeblockSpec on Spec { + static MarkdownCodeblockSpec from(MixData mix) { + return mix.attributeOf()?.resolve(mix) ?? + const MarkdownCodeblockSpec(); + } + + /// {@template markdown_codeblock_spec_of} + /// Retrieves the [MarkdownCodeblockSpec] from the nearest [Mix] ancestor in the widget tree. + /// + /// This method uses [Mix.of] to obtain the [Mix] instance associated with the + /// given [BuildContext], and then retrieves the [MarkdownCodeblockSpec] from that [Mix]. + /// If no ancestor [Mix] is found, this method returns an empty [MarkdownCodeblockSpec]. + /// + /// Example: + /// + /// ```dart + /// final markdownCodeblockSpec = MarkdownCodeblockSpec.of(context); + /// ``` + /// {@endtemplate} + static MarkdownCodeblockSpec of(BuildContext context) { + return _$MarkdownCodeblockSpec.from(Mix.of(context)); + } + + /// Creates a copy of this [MarkdownCodeblockSpec] but with the given fields + /// replaced with the new values. + @override + MarkdownCodeblockSpec copyWith({ + TextStyle? textStyle, + BoxSpec? container, + WrapAlignment? alignment, + WidgetModifiersData? modifiers, + AnimatedData? animated, + }) { + return MarkdownCodeblockSpec( + textStyle: textStyle ?? _$this.textStyle, + container: container ?? _$this.container, + alignment: alignment ?? _$this.alignment, + modifiers: modifiers ?? _$this.modifiers, + animated: animated ?? _$this.animated, + ); + } + + /// Linearly interpolates between this [MarkdownCodeblockSpec] and another [MarkdownCodeblockSpec] based on the given parameter [t]. + /// + /// The parameter [t] represents the interpolation factor, typically ranging from 0.0 to 1.0. + /// When [t] is 0.0, the current [MarkdownCodeblockSpec] is returned. When [t] is 1.0, the [other] [MarkdownCodeblockSpec] is returned. + /// For values of [t] between 0.0 and 1.0, an interpolated [MarkdownCodeblockSpec] is returned. + /// + /// If [other] is null, this method returns the current [MarkdownCodeblockSpec] instance. + /// + /// The interpolation is performed on each property of the [MarkdownCodeblockSpec] using the appropriate + /// interpolation method: + /// + /// - [MixHelpers.lerpTextStyle] for [textStyle]. + /// - [BoxSpec.lerp] for [container]. + + /// For [alignment] and [modifiers] and [animated], the interpolation is performed using a step function. + /// If [t] is less than 0.5, the value from the current [MarkdownCodeblockSpec] is used. Otherwise, the value + /// from the [other] [MarkdownCodeblockSpec] is used. + /// + /// This method is typically used in animations to smoothly transition between + /// different [MarkdownCodeblockSpec] configurations. + @override + MarkdownCodeblockSpec lerp(MarkdownCodeblockSpec? other, double t) { + if (other == null) return _$this; + + return MarkdownCodeblockSpec( + textStyle: MixHelpers.lerpTextStyle(_$this.textStyle, other.textStyle, t), + container: _$this.container?.lerp(other.container, t) ?? other.container, + alignment: t < 0.5 ? _$this.alignment : other.alignment, + modifiers: other.modifiers, + animated: t < 0.5 ? _$this.animated : other.animated, + ); + } + + /// The list of properties that constitute the state of this [MarkdownCodeblockSpec]. + /// + /// This property is used by the [==] operator and the [hashCode] getter to + /// compare two [MarkdownCodeblockSpec] instances for equality. + @override + List get props => [ + _$this.textStyle, + _$this.container, + _$this.alignment, + _$this.modifiers, + _$this.animated, + ]; + + MarkdownCodeblockSpec get _$this => this as MarkdownCodeblockSpec; +} + +/// Represents the attributes of a [MarkdownCodeblockSpec]. +/// +/// This class encapsulates properties defining the layout and +/// appearance of a [MarkdownCodeblockSpec]. +/// +/// Use this class to configure the attributes of a [MarkdownCodeblockSpec] and pass it to +/// the [MarkdownCodeblockSpec] constructor. +final class MarkdownCodeblockSpecAttribute + extends SpecAttribute { + final TextStyleDto? textStyle; + final BoxSpecAttribute? container; + final WrapAlignment? alignment; + + const MarkdownCodeblockSpecAttribute({ + this.textStyle, + this.container, + this.alignment, + super.modifiers, + super.animated, + }); + + /// Resolves to [MarkdownCodeblockSpec] using the provided [MixData]. + /// + /// If a property is null in the [MixData], it falls back to the + /// default value defined in the `defaultValue` for that property. + /// + /// ```dart + /// final markdownCodeblockSpec = MarkdownCodeblockSpecAttribute(...).resolve(mix); + /// ``` + @override + MarkdownCodeblockSpec resolve(MixData mix) { + return MarkdownCodeblockSpec( + textStyle: textStyle?.resolve(mix), + container: container?.resolve(mix), + alignment: alignment, + modifiers: modifiers?.resolve(mix), + animated: animated?.resolve(mix) ?? mix.animation, + ); + } + + /// Merges the properties of this [MarkdownCodeblockSpecAttribute] with the properties of [other]. + /// + /// If [other] is null, returns this instance unchanged. Otherwise, returns a new + /// [MarkdownCodeblockSpecAttribute] with the properties of [other] taking precedence over + /// the corresponding properties of this instance. + /// + /// Properties from [other] that are null will fall back + /// to the values from this instance. + @override + MarkdownCodeblockSpecAttribute merge(MarkdownCodeblockSpecAttribute? other) { + if (other == null) return this; + + return MarkdownCodeblockSpecAttribute( + textStyle: textStyle?.merge(other.textStyle) ?? other.textStyle, + container: container?.merge(other.container) ?? other.container, + alignment: other.alignment ?? alignment, + modifiers: modifiers?.merge(other.modifiers) ?? other.modifiers, + animated: animated?.merge(other.animated) ?? other.animated, + ); + } + + /// The list of properties that constitute the state of this [MarkdownCodeblockSpecAttribute]. + /// + /// This property is used by the [==] operator and the [hashCode] getter to + /// compare two [MarkdownCodeblockSpecAttribute] instances for equality. + @override + List get props => [ + textStyle, + container, + alignment, + modifiers, + animated, + ]; +} + +/// Utility class for configuring [MarkdownCodeblockSpec] properties. +/// +/// This class provides methods to set individual properties of a [MarkdownCodeblockSpec]. +/// Use the methods of this class to configure specific properties of a [MarkdownCodeblockSpec]. +class MarkdownCodeblockSpecUtility + extends SpecUtility { + /// Utility for defining [MarkdownCodeblockSpecAttribute.textStyle] + late final textStyle = TextStyleUtility((v) => only(textStyle: v)); + + /// Utility for defining [MarkdownCodeblockSpecAttribute.container] + late final container = BoxSpecUtility((v) => only(container: v)); + + /// Utility for defining [MarkdownCodeblockSpecAttribute.alignment] + late final alignment = WrapAlignmentUtility((v) => only(alignment: v)); + + /// Utility for defining [MarkdownCodeblockSpecAttribute.modifiers] + late final wrap = SpecModifierUtility((v) => only(modifiers: v)); + + /// Utility for defining [MarkdownCodeblockSpecAttribute.animated] + late final animated = AnimatedUtility((v) => only(animated: v)); + + MarkdownCodeblockSpecUtility(super.builder, {super.mutable}); + + MarkdownCodeblockSpecUtility get chain => + MarkdownCodeblockSpecUtility(attributeBuilder, mutable: true); + + static MarkdownCodeblockSpecUtility + get self => MarkdownCodeblockSpecUtility((v) => v); + + /// Returns a new [MarkdownCodeblockSpecAttribute] with the specified properties. + @override + T only({ + TextStyleDto? textStyle, + BoxSpecAttribute? container, + WrapAlignment? alignment, + WidgetModifiersDataDto? modifiers, + AnimatedDataDto? animated, + }) { + return builder(MarkdownCodeblockSpecAttribute( + textStyle: textStyle, + container: container, + alignment: alignment, + modifiers: modifiers, + animated: animated, + )); + } +} + +/// A tween that interpolates between two [MarkdownCodeblockSpec] instances. +/// +/// This class can be used in animations to smoothly transition between +/// different [MarkdownCodeblockSpec] specifications. +class MarkdownCodeblockSpecTween extends Tween { + MarkdownCodeblockSpecTween({ + super.begin, + super.end, + }); + + @override + MarkdownCodeblockSpec lerp(double t) { + if (begin == null && end == null) { + return const MarkdownCodeblockSpec(); + } + + if (begin == null) { + return end!; + } + + return begin!.lerp(end!, t); + } +} + +mixin _$MarkdownCheckboxSpec on Spec { + static MarkdownCheckboxSpec from(MixData mix) { + return mix.attributeOf()?.resolve(mix) ?? + const MarkdownCheckboxSpec(); + } + + /// {@template markdown_checkbox_spec_of} + /// Retrieves the [MarkdownCheckboxSpec] from the nearest [Mix] ancestor in the widget tree. + /// + /// This method uses [Mix.of] to obtain the [Mix] instance associated with the + /// given [BuildContext], and then retrieves the [MarkdownCheckboxSpec] from that [Mix]. + /// If no ancestor [Mix] is found, this method returns an empty [MarkdownCheckboxSpec]. + /// + /// Example: + /// + /// ```dart + /// final markdownCheckboxSpec = MarkdownCheckboxSpec.of(context); + /// ``` + /// {@endtemplate} + static MarkdownCheckboxSpec of(BuildContext context) { + return _$MarkdownCheckboxSpec.from(Mix.of(context)); + } + + /// Creates a copy of this [MarkdownCheckboxSpec] but with the given fields + /// replaced with the new values. + @override + MarkdownCheckboxSpec copyWith({ + TextStyle? textStyle, + IconSpec? icon, + WidgetModifiersData? modifiers, + AnimatedData? animated, + }) { + return MarkdownCheckboxSpec( + textStyle: textStyle ?? _$this.textStyle, + icon: icon ?? _$this.icon, + modifiers: modifiers ?? _$this.modifiers, + animated: animated ?? _$this.animated, + ); + } + + /// Linearly interpolates between this [MarkdownCheckboxSpec] and another [MarkdownCheckboxSpec] based on the given parameter [t]. + /// + /// The parameter [t] represents the interpolation factor, typically ranging from 0.0 to 1.0. + /// When [t] is 0.0, the current [MarkdownCheckboxSpec] is returned. When [t] is 1.0, the [other] [MarkdownCheckboxSpec] is returned. + /// For values of [t] between 0.0 and 1.0, an interpolated [MarkdownCheckboxSpec] is returned. + /// + /// If [other] is null, this method returns the current [MarkdownCheckboxSpec] instance. + /// + /// The interpolation is performed on each property of the [MarkdownCheckboxSpec] using the appropriate + /// interpolation method: + /// + /// - [MixHelpers.lerpTextStyle] for [textStyle]. + /// - [IconSpec.lerp] for [icon]. + + /// For [modifiers] and [animated], the interpolation is performed using a step function. + /// If [t] is less than 0.5, the value from the current [MarkdownCheckboxSpec] is used. Otherwise, the value + /// from the [other] [MarkdownCheckboxSpec] is used. + /// + /// This method is typically used in animations to smoothly transition between + /// different [MarkdownCheckboxSpec] configurations. + @override + MarkdownCheckboxSpec lerp(MarkdownCheckboxSpec? other, double t) { + if (other == null) return _$this; + + return MarkdownCheckboxSpec( + textStyle: MixHelpers.lerpTextStyle(_$this.textStyle, other.textStyle, t), + icon: _$this.icon?.lerp(other.icon, t) ?? other.icon, + modifiers: other.modifiers, + animated: t < 0.5 ? _$this.animated : other.animated, + ); + } + + /// The list of properties that constitute the state of this [MarkdownCheckboxSpec]. + /// + /// This property is used by the [==] operator and the [hashCode] getter to + /// compare two [MarkdownCheckboxSpec] instances for equality. + @override + List get props => [ + _$this.textStyle, + _$this.icon, + _$this.modifiers, + _$this.animated, + ]; + + MarkdownCheckboxSpec get _$this => this as MarkdownCheckboxSpec; +} + +/// Represents the attributes of a [MarkdownCheckboxSpec]. +/// +/// This class encapsulates properties defining the layout and +/// appearance of a [MarkdownCheckboxSpec]. +/// +/// Use this class to configure the attributes of a [MarkdownCheckboxSpec] and pass it to +/// the [MarkdownCheckboxSpec] constructor. +class MarkdownCheckboxSpecAttribute + extends SpecAttribute { + final TextStyleDto? textStyle; + final IconSpecAttribute? icon; + + const MarkdownCheckboxSpecAttribute({ + this.textStyle, + this.icon, + super.modifiers, + super.animated, + }); + + /// Resolves to [MarkdownCheckboxSpec] using the provided [MixData]. + /// + /// If a property is null in the [MixData], it falls back to the + /// default value defined in the `defaultValue` for that property. + /// + /// ```dart + /// final markdownCheckboxSpec = MarkdownCheckboxSpecAttribute(...).resolve(mix); + /// ``` + @override + MarkdownCheckboxSpec resolve(MixData mix) { + return MarkdownCheckboxSpec( + textStyle: textStyle?.resolve(mix), + icon: icon?.resolve(mix), + modifiers: modifiers?.resolve(mix), + animated: animated?.resolve(mix) ?? mix.animation, + ); + } + + /// Merges the properties of this [MarkdownCheckboxSpecAttribute] with the properties of [other]. + /// + /// If [other] is null, returns this instance unchanged. Otherwise, returns a new + /// [MarkdownCheckboxSpecAttribute] with the properties of [other] taking precedence over + /// the corresponding properties of this instance. + /// + /// Properties from [other] that are null will fall back + /// to the values from this instance. + @override + MarkdownCheckboxSpecAttribute merge( + covariant MarkdownCheckboxSpecAttribute? other) { + if (other == null) return this; + + return MarkdownCheckboxSpecAttribute( + textStyle: textStyle?.merge(other.textStyle) ?? other.textStyle, + icon: icon?.merge(other.icon) ?? other.icon, + modifiers: modifiers?.merge(other.modifiers) ?? other.modifiers, + animated: animated?.merge(other.animated) ?? other.animated, + ); + } + + /// The list of properties that constitute the state of this [MarkdownCheckboxSpecAttribute]. + /// + /// This property is used by the [==] operator and the [hashCode] getter to + /// compare two [MarkdownCheckboxSpecAttribute] instances for equality. + @override + List get props => [ + textStyle, + icon, + modifiers, + animated, + ]; +} + +/// Utility class for configuring [MarkdownCheckboxSpec] properties. +/// +/// This class provides methods to set individual properties of a [MarkdownCheckboxSpec]. +/// Use the methods of this class to configure specific properties of a [MarkdownCheckboxSpec]. +class MarkdownCheckboxSpecUtility + extends SpecUtility { + /// Utility for defining [MarkdownCheckboxSpecAttribute.textStyle] + late final textStyle = TextStyleUtility((v) => only(textStyle: v)); + + /// Utility for defining [MarkdownCheckboxSpecAttribute.icon] + late final icon = IconSpecUtility((v) => only(icon: v)); + + /// Utility for defining [MarkdownCheckboxSpecAttribute.modifiers] + late final wrap = SpecModifierUtility((v) => only(modifiers: v)); + + /// Utility for defining [MarkdownCheckboxSpecAttribute.animated] + late final animated = AnimatedUtility((v) => only(animated: v)); + + MarkdownCheckboxSpecUtility(super.builder, {super.mutable}); + + MarkdownCheckboxSpecUtility get chain => + MarkdownCheckboxSpecUtility(attributeBuilder, mutable: true); + + static MarkdownCheckboxSpecUtility get self => + MarkdownCheckboxSpecUtility((v) => v); + + /// Returns a new [MarkdownCheckboxSpecAttribute] with the specified properties. + @override + T only({ + TextStyleDto? textStyle, + IconSpecAttribute? icon, + WidgetModifiersDataDto? modifiers, + AnimatedDataDto? animated, + }) { + return builder(MarkdownCheckboxSpecAttribute( + textStyle: textStyle, + icon: icon, + modifiers: modifiers, + animated: animated, + )); + } +} + +/// A tween that interpolates between two [MarkdownCheckboxSpec] instances. +/// +/// This class can be used in animations to smoothly transition between +/// different [MarkdownCheckboxSpec] specifications. +class MarkdownCheckboxSpecTween extends Tween { + MarkdownCheckboxSpecTween({ + super.begin, + super.end, + }); + + @override + MarkdownCheckboxSpec lerp(double t) { + if (begin == null && end == null) { + return const MarkdownCheckboxSpec(); + } + + if (begin == null) { + return end!; + } + + return begin!.lerp(end!, t); + } +} + +mixin _$SlideSpec on Spec { + static SlideSpec from(MixData mix) { + return mix.attributeOf()?.resolve(mix) ?? + const SlideSpec(); + } + + /// {@template slide_spec_of} + /// Retrieves the [SlideSpec] from the nearest [Mix] ancestor in the widget tree. + /// + /// This method uses [Mix.of] to obtain the [Mix] instance associated with the + /// given [BuildContext], and then retrieves the [SlideSpec] from that [Mix]. + /// If no ancestor [Mix] is found, this method returns an empty [SlideSpec]. + /// + /// Example: + /// + /// ```dart + /// final slideSpec = SlideSpec.of(context); + /// ``` + /// {@endtemplate} + static SlideSpec of(BuildContext context) { + return _$SlideSpec.from(Mix.of(context)); + } + + /// Creates a copy of this [SlideSpec] but with the given fields + /// replaced with the new values. + @override + SlideSpec copyWith({ + TextSpec? h1, + TextSpec? h2, + TextSpec? h3, + TextSpec? h4, + TextSpec? h5, + TextSpec? h6, + TextSpec? p, + TextStyle? link, + MarkdownBlockquoteSpec? blockquote, + MarkdownListSpec? list, + MarkdownTableSpec? table, + MarkdownCheckboxSpec? checkbox, + MarkdownCodeblockSpec? code, + TextStyle? a, + TextStyle? em, + TextStyle? strong, + TextStyle? del, + TextStyle? img, + BoxDecoration? horizontalRuleDecoration, + TextScaler? textScaleFactor, + BoxSpec? blockContainer, + ImageSpec? image, + MarkdownAlertSpec? alert, + WidgetModifiersData? modifiers, + AnimatedData? animated, + }) { + return SlideSpec( + h1: h1 ?? _$this.h1, + h2: h2 ?? _$this.h2, + h3: h3 ?? _$this.h3, + h4: h4 ?? _$this.h4, + h5: h5 ?? _$this.h5, + h6: h6 ?? _$this.h6, + p: p ?? _$this.p, + link: link ?? _$this.link, + blockquote: blockquote ?? _$this.blockquote, + list: list ?? _$this.list, + table: table ?? _$this.table, + checkbox: checkbox ?? _$this.checkbox, + code: code ?? _$this.code, + a: a ?? _$this.a, + em: em ?? _$this.em, + strong: strong ?? _$this.strong, + del: del ?? _$this.del, + img: img ?? _$this.img, + horizontalRuleDecoration: + horizontalRuleDecoration ?? _$this.horizontalRuleDecoration, + textScaleFactor: textScaleFactor ?? _$this.textScaleFactor, + blockContainer: blockContainer ?? _$this.blockContainer, + image: image ?? _$this.image, + alert: alert ?? _$this.alert, + modifiers: modifiers ?? _$this.modifiers, + animated: animated ?? _$this.animated, + ); + } + + /// Linearly interpolates between this [SlideSpec] and another [SlideSpec] based on the given parameter [t]. + /// + /// The parameter [t] represents the interpolation factor, typically ranging from 0.0 to 1.0. + /// When [t] is 0.0, the current [SlideSpec] is returned. When [t] is 1.0, the [other] [SlideSpec] is returned. + /// For values of [t] between 0.0 and 1.0, an interpolated [SlideSpec] is returned. + /// + /// If [other] is null, this method returns the current [SlideSpec] instance. + /// + /// The interpolation is performed on each property of the [SlideSpec] using the appropriate + /// interpolation method: + /// + /// - [TextSpec.lerp] for [h1] and [h2] and [h3] and [h4] and [h5] and [h6] and [p]. + /// - [MixHelpers.lerpTextStyle] for [link] and [a] and [em] and [strong] and [del] and [img]. + /// - [BoxDecoration.lerp] for [horizontalRuleDecoration]. + /// - [BoxSpec.lerp] for [blockContainer]. + /// - [ImageSpec.lerp] for [image]. + + /// For [blockquote] and [list] and [table] and [checkbox] and [code] and [textScaleFactor] and [alert] and [modifiers] and [animated], the interpolation is performed using a step function. + /// If [t] is less than 0.5, the value from the current [SlideSpec] is used. Otherwise, the value + /// from the [other] [SlideSpec] is used. + /// + /// This method is typically used in animations to smoothly transition between + /// different [SlideSpec] configurations. + @override + SlideSpec lerp(SlideSpec? other, double t) { + if (other == null) return _$this; + + return SlideSpec( + h1: _$this.h1?.lerp(other.h1, t) ?? other.h1, + h2: _$this.h2?.lerp(other.h2, t) ?? other.h2, + h3: _$this.h3?.lerp(other.h3, t) ?? other.h3, + h4: _$this.h4?.lerp(other.h4, t) ?? other.h4, + h5: _$this.h5?.lerp(other.h5, t) ?? other.h5, + h6: _$this.h6?.lerp(other.h6, t) ?? other.h6, + p: _$this.p?.lerp(other.p, t) ?? other.p, + link: MixHelpers.lerpTextStyle(_$this.link, other.link, t), + blockquote: + _$this.blockquote?.lerp(other.blockquote, t) ?? other.blockquote, + list: _$this.list?.lerp(other.list, t) ?? other.list, + table: _$this.table?.lerp(other.table, t) ?? other.table, + checkbox: _$this.checkbox?.lerp(other.checkbox, t) ?? other.checkbox, + code: _$this.code?.lerp(other.code, t) ?? other.code, + a: MixHelpers.lerpTextStyle(_$this.a, other.a, t), + em: MixHelpers.lerpTextStyle(_$this.em, other.em, t), + strong: MixHelpers.lerpTextStyle(_$this.strong, other.strong, t), + del: MixHelpers.lerpTextStyle(_$this.del, other.del, t), + img: MixHelpers.lerpTextStyle(_$this.img, other.img, t), + horizontalRuleDecoration: BoxDecoration.lerp( + _$this.horizontalRuleDecoration, other.horizontalRuleDecoration, t), + textScaleFactor: t < 0.5 ? _$this.textScaleFactor : other.textScaleFactor, + blockContainer: _$this.blockContainer.lerp(other.blockContainer, t), + image: _$this.image.lerp(other.image, t), + alert: _$this.alert.lerp(other.alert, t), + modifiers: other.modifiers, + animated: t < 0.5 ? _$this.animated : other.animated, + ); + } + + /// The list of properties that constitute the state of this [SlideSpec]. + /// + /// This property is used by the [==] operator and the [hashCode] getter to + /// compare two [SlideSpec] instances for equality. + @override + List get props => [ + _$this.h1, + _$this.h2, + _$this.h3, + _$this.h4, + _$this.h5, + _$this.h6, + _$this.p, + _$this.link, + _$this.blockquote, + _$this.list, + _$this.table, + _$this.checkbox, + _$this.code, + _$this.a, + _$this.em, + _$this.strong, + _$this.del, + _$this.img, + _$this.horizontalRuleDecoration, + _$this.textScaleFactor, + _$this.blockContainer, + _$this.image, + _$this.alert, + _$this.modifiers, + _$this.animated, + ]; + + SlideSpec get _$this => this as SlideSpec; +} + +/// Represents the attributes of a [SlideSpec]. +/// +/// This class encapsulates properties defining the layout and +/// appearance of a [SlideSpec]. +/// +/// Use this class to configure the attributes of a [SlideSpec] and pass it to +/// the [SlideSpec] constructor. +final class SlideSpecAttribute extends SpecAttribute { + final TextSpecAttribute? h1; + final TextSpecAttribute? h2; + final TextSpecAttribute? h3; + final TextSpecAttribute? h4; + final TextSpecAttribute? h5; + final TextSpecAttribute? h6; + final TextSpecAttribute? p; + final TextStyleDto? link; + final MarkdownBlockquoteSpecAttribute? blockquote; + final MarkdownListSpecAttribute? list; + final MarkdownTableSpecAttribute? table; + final MarkdownCheckboxSpecAttribute? checkbox; + final MarkdownCodeblockSpecAttribute? code; + final TextStyleDto? a; + final TextStyleDto? em; + final TextStyleDto? strong; + final TextStyleDto? del; + final TextStyleDto? img; + final BoxDecorationDto? horizontalRuleDecoration; + final TextScaler? textScaleFactor; + final BoxSpecAttribute? blockContainer; + final ImageSpecAttribute? image; + final MarkdownAlertSpecAttribute? alert; + + const SlideSpecAttribute({ + this.h1, + this.h2, + this.h3, + this.h4, + this.h5, + this.h6, + this.p, + this.link, + this.blockquote, + this.list, + this.table, + this.checkbox, + this.code, + this.a, + this.em, + this.strong, + this.del, + this.img, + this.horizontalRuleDecoration, + this.textScaleFactor, + this.blockContainer, + this.image, + this.alert, + super.modifiers, + super.animated, + }); + + /// Resolves to [SlideSpec] using the provided [MixData]. + /// + /// If a property is null in the [MixData], it falls back to the + /// default value defined in the `defaultValue` for that property. + /// + /// ```dart + /// final slideSpec = SlideSpecAttribute(...).resolve(mix); + /// ``` + @override + SlideSpec resolve(MixData mix) { + return SlideSpec( + h1: h1?.resolve(mix), + h2: h2?.resolve(mix), + h3: h3?.resolve(mix), + h4: h4?.resolve(mix), + h5: h5?.resolve(mix), + h6: h6?.resolve(mix), + p: p?.resolve(mix), + link: link?.resolve(mix), + blockquote: blockquote?.resolve(mix), + list: list?.resolve(mix), + table: table?.resolve(mix), + checkbox: checkbox?.resolve(mix), + code: code?.resolve(mix), + a: a?.resolve(mix), + em: em?.resolve(mix), + strong: strong?.resolve(mix), + del: del?.resolve(mix), + img: img?.resolve(mix), + horizontalRuleDecoration: horizontalRuleDecoration?.resolve(mix), + textScaleFactor: textScaleFactor, + blockContainer: blockContainer?.resolve(mix), + image: image?.resolve(mix), + alert: alert?.resolve(mix), + modifiers: modifiers?.resolve(mix), + animated: animated?.resolve(mix) ?? mix.animation, + ); + } + + /// Merges the properties of this [SlideSpecAttribute] with the properties of [other]. + /// + /// If [other] is null, returns this instance unchanged. Otherwise, returns a new + /// [SlideSpecAttribute] with the properties of [other] taking precedence over + /// the corresponding properties of this instance. + /// + /// Properties from [other] that are null will fall back + /// to the values from this instance. + @override + SlideSpecAttribute merge(SlideSpecAttribute? other) { + if (other == null) return this; + + return SlideSpecAttribute( + h1: h1?.merge(other.h1) ?? other.h1, + h2: h2?.merge(other.h2) ?? other.h2, + h3: h3?.merge(other.h3) ?? other.h3, + h4: h4?.merge(other.h4) ?? other.h4, + h5: h5?.merge(other.h5) ?? other.h5, + h6: h6?.merge(other.h6) ?? other.h6, + p: p?.merge(other.p) ?? other.p, + link: link?.merge(other.link) ?? other.link, + blockquote: blockquote?.merge(other.blockquote) ?? other.blockquote, + list: list?.merge(other.list) ?? other.list, + table: table?.merge(other.table) ?? other.table, + checkbox: checkbox?.merge(other.checkbox) ?? other.checkbox, + code: code?.merge(other.code) ?? other.code, + a: a?.merge(other.a) ?? other.a, + em: em?.merge(other.em) ?? other.em, + strong: strong?.merge(other.strong) ?? other.strong, + del: del?.merge(other.del) ?? other.del, + img: img?.merge(other.img) ?? other.img, + horizontalRuleDecoration: + horizontalRuleDecoration?.merge(other.horizontalRuleDecoration) ?? + other.horizontalRuleDecoration, + textScaleFactor: other.textScaleFactor ?? textScaleFactor, + blockContainer: + blockContainer?.merge(other.blockContainer) ?? other.blockContainer, + image: image?.merge(other.image) ?? other.image, + alert: alert?.merge(other.alert) ?? other.alert, + modifiers: modifiers?.merge(other.modifiers) ?? other.modifiers, + animated: animated?.merge(other.animated) ?? other.animated, + ); + } + + /// The list of properties that constitute the state of this [SlideSpecAttribute]. + /// + /// This property is used by the [==] operator and the [hashCode] getter to + /// compare two [SlideSpecAttribute] instances for equality. + @override + List get props => [ + h1, + h2, + h3, + h4, + h5, + h6, + p, + link, + blockquote, + list, + table, + checkbox, + code, + a, + em, + strong, + del, + img, + horizontalRuleDecoration, + textScaleFactor, + blockContainer, + image, + alert, + modifiers, + animated, + ]; +} + +/// Utility class for configuring [SlideSpec] properties. +/// +/// This class provides methods to set individual properties of a [SlideSpec]. +/// Use the methods of this class to configure specific properties of a [SlideSpec]. +class SlideSpecUtility + extends SpecUtility { + /// Utility for defining [SlideSpecAttribute.h1] + late final h1 = TextSpecUtility((v) => only(h1: v)); + + /// Utility for defining [SlideSpecAttribute.h2] + late final h2 = TextSpecUtility((v) => only(h2: v)); + + /// Utility for defining [SlideSpecAttribute.h3] + late final h3 = TextSpecUtility((v) => only(h3: v)); + + /// Utility for defining [SlideSpecAttribute.h4] + late final h4 = TextSpecUtility((v) => only(h4: v)); + + /// Utility for defining [SlideSpecAttribute.h5] + late final h5 = TextSpecUtility((v) => only(h5: v)); + + /// Utility for defining [SlideSpecAttribute.h6] + late final h6 = TextSpecUtility((v) => only(h6: v)); + + /// Utility for defining [SlideSpecAttribute.p] + late final p = TextSpecUtility((v) => only(p: v)); + + /// Utility for defining [SlideSpecAttribute.link] + late final link = TextStyleUtility((v) => only(link: v)); + + /// Utility for defining [SlideSpecAttribute.blockquote] + late final blockquote = + MarkdownBlockquoteSpecUtility((v) => only(blockquote: v)); + + /// Utility for defining [SlideSpecAttribute.list] + late final list = MarkdownListSpecUtility((v) => only(list: v)); + + /// Utility for defining [SlideSpecAttribute.table] + late final table = MarkdownTableSpecUtility((v) => only(table: v)); + + /// Utility for defining [SlideSpecAttribute.checkbox] + late final checkbox = MarkdownCheckboxSpecUtility((v) => only(checkbox: v)); + + /// Utility for defining [SlideSpecAttribute.code] + late final code = MarkdownCodeblockSpecUtility((v) => only(code: v)); + + /// Utility for defining [SlideSpecAttribute.a] + late final a = TextStyleUtility((v) => only(a: v)); + + /// Utility for defining [SlideSpecAttribute.em] + late final em = TextStyleUtility((v) => only(em: v)); + + /// Utility for defining [SlideSpecAttribute.strong] + late final strong = TextStyleUtility((v) => only(strong: v)); + + /// Utility for defining [SlideSpecAttribute.del] + late final del = TextStyleUtility((v) => only(del: v)); + + /// Utility for defining [SlideSpecAttribute.img] + late final img = TextStyleUtility((v) => only(img: v)); + + /// Utility for defining [SlideSpecAttribute.horizontalRuleDecoration] + late final divider = + BoxDecorationUtility((v) => only(horizontalRuleDecoration: v)); + + /// Utility for defining [SlideSpecAttribute.textScaleFactor] + late final textScaleFactor = + TextScalerUtility((v) => only(textScaleFactor: v)); + + /// Utility for defining [SlideSpecAttribute.blockContainer] + late final blockContainer = BoxSpecUtility((v) => only(blockContainer: v)); + + /// Utility for defining [SlideSpecAttribute.image] + late final image = ImageSpecUtility((v) => only(image: v)); + + /// Utility for defining [SlideSpecAttribute.alert] + late final alert = MarkdownAlertSpecUtility((v) => only(alert: v)); + + /// Utility for defining [SlideSpecAttribute.modifiers] + late final wrap = SpecModifierUtility((v) => only(modifiers: v)); + + /// Utility for defining [SlideSpecAttribute.animated] + late final animated = AnimatedUtility((v) => only(animated: v)); + + SlideSpecUtility(super.builder, {super.mutable}); + + SlideSpecUtility get chain => + SlideSpecUtility(attributeBuilder, mutable: true); + + static SlideSpecUtility get self => + SlideSpecUtility((v) => v); + + /// Returns a new [SlideSpecAttribute] with the specified properties. + @override + T only({ + TextSpecAttribute? h1, + TextSpecAttribute? h2, + TextSpecAttribute? h3, + TextSpecAttribute? h4, + TextSpecAttribute? h5, + TextSpecAttribute? h6, + TextSpecAttribute? p, + TextStyleDto? link, + MarkdownBlockquoteSpecAttribute? blockquote, + MarkdownListSpecAttribute? list, + MarkdownTableSpecAttribute? table, + MarkdownCheckboxSpecAttribute? checkbox, + MarkdownCodeblockSpecAttribute? code, + TextStyleDto? a, + TextStyleDto? em, + TextStyleDto? strong, + TextStyleDto? del, + TextStyleDto? img, + BoxDecorationDto? horizontalRuleDecoration, + TextScaler? textScaleFactor, + BoxSpecAttribute? blockContainer, + ImageSpecAttribute? image, + MarkdownAlertSpecAttribute? alert, + WidgetModifiersDataDto? modifiers, + AnimatedDataDto? animated, + }) { + return builder(SlideSpecAttribute( + h1: h1, + h2: h2, + h3: h3, + h4: h4, + h5: h5, + h6: h6, + p: p, + link: link, + blockquote: blockquote, + list: list, + table: table, + checkbox: checkbox, + code: code, + a: a, + em: em, + strong: strong, + del: del, + img: img, + horizontalRuleDecoration: horizontalRuleDecoration, + textScaleFactor: textScaleFactor, + blockContainer: blockContainer, + image: image, + alert: alert, + modifiers: modifiers, + animated: animated, + )); + } +} + +/// A tween that interpolates between two [SlideSpec] instances. +/// +/// This class can be used in animations to smoothly transition between +/// different [SlideSpec] specifications. +class SlideSpecTween extends Tween { + SlideSpecTween({ + super.begin, + super.end, + }); + + @override + SlideSpec lerp(double t) { + if (begin == null && end == null) { + return const SlideSpec(); + } + + if (begin == null) { + return end!; + } + + return begin!.lerp(end!, t); + } +} diff --git a/packages/superdeck/lib/src/modules/deck/deck_controller.dart b/packages/superdeck/lib/src/modules/deck/deck_controller.dart new file mode 100644 index 00000000..f3fbe3f3 --- /dev/null +++ b/packages/superdeck/lib/src/modules/deck/deck_controller.dart @@ -0,0 +1,131 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/widgets.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +import '../common/helpers/provider.dart'; +import 'deck_options.dart'; +import 'slide_configuration.dart'; + +class DeckController with ChangeNotifier { + DeckOptions options; + List slides; + IDataStore _dataStore; + + DeckController({ + required this.options, + required this.slides, + required IDataStore dataStore, + }) : _dataStore = dataStore; + + void update({ + List? slides, + DeckOptions? options, + }) { + if (slides != null || options != null) { + this.options = options ?? this.options; + final newSlides = + slides ?? this.slides.map((slide) => slide.data).toList(); + this.slides = _buildSlides( + slides: newSlides, + options: this.options, + dataStore: _dataStore, + ); + + notifyListeners(); + } + } + + factory DeckController.build({ + required List slides, + required DeckOptions options, + required IDataStore dataStore, + }) { + return DeckController( + options: options, + slides: _buildSlides( + slides: slides, + options: options, + dataStore: dataStore, + ), + dataStore: dataStore, + ); + } + + static DeckController of(BuildContext context) { + return InheritedNotifierData.of(context); + } +} + +List _buildSlides({ + required List slides, + required DeckOptions options, + required IDataStore dataStore, +}) { + if (slides.isEmpty) { + return [ + _convertSlide( + slideIndex: 0, + slide: _emptySlide, + options: options, + dataStore: dataStore, + ) + ]; + } + return slides.mapIndexed((index, slide) { + return _convertSlide( + slideIndex: index, + slide: slide, + options: options, + dataStore: dataStore, + ); + }).toList(); +} + +SlideConfiguration _convertSlide({ + required int slideIndex, + required Slide slide, + required DeckOptions options, + required IDataStore dataStore, +}) { + final widgetBlocks = slide.sections + .expand((section) => section.blocks) + .whereType(); + + final slideWidgets = {}; + + for (final block in widgetBlocks) { + final widgetBuilder = options.widgets[block.name]; + if (widgetBuilder != null) { + slideWidgets[block.name] = widgetBuilder; + } + } + + final styles = options.styles; + final styleName = slide.options?.style; + final baseStyle = options.baseStyle; + final style = baseStyle.build().merge(styles[styleName]?.build()); + final thumbnailFile = dataStore.getGeneratedAssetPath( + GeneratedAsset.thumbnail(slide.key), + ); + return SlideConfiguration( + slideIndex: slideIndex, + style: style, + slide: slide, + debug: options.debug, + parts: options.parts, + widgets: slideWidgets, + thumbnailFile: thumbnailFile, + ); +} + +final _emptySlide = Slide( + key: 'empty', + sections: [ + SectionBlock([ + '## No slides found'.column().alignCenter(), + 'Update the slides.md file to add slides to your deck.' + .column() + .alignBottomRight(), + ]), + ], +); diff --git a/packages/superdeck/lib/src/modules/deck/deck_options.dart b/packages/superdeck/lib/src/modules/deck/deck_options.dart new file mode 100644 index 00000000..d1d4883e --- /dev/null +++ b/packages/superdeck/lib/src/modules/deck/deck_options.dart @@ -0,0 +1,81 @@ +import 'package:dart_mappable/dart_mappable.dart'; +import 'package:flutter/widgets.dart'; + +import '../../components/parts/slide_parts.dart'; +import '../common/styles/style.dart'; + +part 'deck_options.mapper.dart'; + +@MappableClass() +class DeckOptions with DeckOptionsMappable { + final DeckStyle baseStyle; + final Map styles; + final Map widgets; + final SlideParts parts; + final bool debug; + + const DeckOptions({ + this.baseStyle = const DeckStyle(), + this.styles = const {}, + this.widgets = const {}, + this.parts = SlideParts.defaultParts, + this.debug = false, + }); +} + +typedef WidgetBlockBuilder = Widget Function( + Map args, +); + +extension MapExt on Map { + String? getStringOrNull(String key) => _getMaybeAs(key); + + int? getIntOrNull(String key) => _getMaybeAs(key); + + double? getDoubleOrNull(String key) => _getMaybeAs(key); + + bool? getBoolOrNull(String key) => _getMaybeAs(key); + + bool getBool(String key) => _getAs(key); + + int getInt(String key) => _getAs(key); + + double getDouble(String key) => _getAs(key); + + String getString(String key) => _getAs(key); + + /// Returns the value for [key] converted to type [T], or `null` if the conversion fails. + + /// Returns the value for [key] converted to type [T], or `null` if the conversion fails. + T? _getMaybeAs(String key) { + final value = this[key]; + if (value == null) return null; + if (value is T) return value; + + if (T == int) { + if (value is num) return value.toInt() as T; + if (value is String) return int.tryParse(value) as T?; + } else if (T == double) { + if (value is num) return value.toDouble() as T; + if (value is String) return double.tryParse(value) as T?; + } else if (T == bool) { + if (value is String) { + final lower = value.toLowerCase(); + if (lower == 'true') return true as T; + if (lower == 'false') return false as T; + } + } else if (T == String) { + return value.toString() as T; + } + + return null; + } + + T _getAs(String key) { + final value = _getMaybeAs(key); + if (value == null) { + throw ArgumentError('Key "$key" not found in the map.'); + } + return value; + } +} diff --git a/packages/superdeck/lib/src/modules/deck/deck_options.mapper.dart b/packages/superdeck/lib/src/modules/deck/deck_options.mapper.dart new file mode 100644 index 00000000..494bf4d7 --- /dev/null +++ b/packages/superdeck/lib/src/modules/deck/deck_options.mapper.dart @@ -0,0 +1,177 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member +// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter + +part of 'deck_options.dart'; + +class DeckOptionsMapper extends ClassMapperBase { + DeckOptionsMapper._(); + + static DeckOptionsMapper? _instance; + static DeckOptionsMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = DeckOptionsMapper._()); + } + return _instance!; + } + + @override + final String id = 'DeckOptions'; + + static DeckStyle _$baseStyle(DeckOptions v) => v.baseStyle; + static const Field _f$baseStyle = + Field('baseStyle', _$baseStyle, opt: true, def: const DeckStyle()); + static Map _$styles(DeckOptions v) => v.styles; + static const Field> _f$styles = + Field('styles', _$styles, opt: true, def: const {}); + static Map)> _$widgets( + DeckOptions v) => + v.widgets; + static const Field)>> _f$widgets = + Field('widgets', _$widgets, + opt: true, def: const {}); + static SlideParts _$parts(DeckOptions v) => v.parts; + static const Field _f$parts = + Field('parts', _$parts, opt: true, def: SlideParts.defaultParts); + static bool _$debug(DeckOptions v) => v.debug; + static const Field _f$debug = + Field('debug', _$debug, opt: true, def: false); + + @override + final MappableFields fields = const { + #baseStyle: _f$baseStyle, + #styles: _f$styles, + #widgets: _f$widgets, + #parts: _f$parts, + #debug: _f$debug, + }; + + static DeckOptions _instantiate(DecodingData data) { + return DeckOptions( + baseStyle: data.dec(_f$baseStyle), + styles: data.dec(_f$styles), + widgets: data.dec(_f$widgets), + parts: data.dec(_f$parts), + debug: data.dec(_f$debug)); + } + + @override + final Function instantiate = _instantiate; + + static DeckOptions fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static DeckOptions fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin DeckOptionsMappable { + String toJson() { + return DeckOptionsMapper.ensureInitialized() + .encodeJson(this as DeckOptions); + } + + Map toMap() { + return DeckOptionsMapper.ensureInitialized() + .encodeMap(this as DeckOptions); + } + + DeckOptionsCopyWith get copyWith => + _DeckOptionsCopyWithImpl(this as DeckOptions, $identity, $identity); + @override + String toString() { + return DeckOptionsMapper.ensureInitialized() + .stringifyValue(this as DeckOptions); + } + + @override + bool operator ==(Object other) { + return DeckOptionsMapper.ensureInitialized() + .equalsValue(this as DeckOptions, other); + } + + @override + int get hashCode { + return DeckOptionsMapper.ensureInitialized().hashValue(this as DeckOptions); + } +} + +extension DeckOptionsValueCopy<$R, $Out> + on ObjectCopyWith<$R, DeckOptions, $Out> { + DeckOptionsCopyWith<$R, DeckOptions, $Out> get $asDeckOptions => + $base.as((v, t, t2) => _DeckOptionsCopyWithImpl(v, t, t2)); +} + +abstract class DeckOptionsCopyWith<$R, $In extends DeckOptions, $Out> + implements ClassCopyWith<$R, $In, $Out> { + MapCopyWith<$R, String, DeckStyle, ObjectCopyWith<$R, DeckStyle, DeckStyle>> + get styles; + MapCopyWith< + $R, + String, + Widget Function(Map), + ObjectCopyWith<$R, Widget Function(Map), + Widget Function(Map)>> get widgets; + $R call( + {DeckStyle? baseStyle, + Map? styles, + Map)>? widgets, + SlideParts? parts, + bool? debug}); + DeckOptionsCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class _DeckOptionsCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, DeckOptions, $Out> + implements DeckOptionsCopyWith<$R, DeckOptions, $Out> { + _DeckOptionsCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + DeckOptionsMapper.ensureInitialized(); + @override + MapCopyWith<$R, String, DeckStyle, ObjectCopyWith<$R, DeckStyle, DeckStyle>> + get styles => MapCopyWith($value.styles, + (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(styles: v)); + @override + MapCopyWith< + $R, + String, + Widget Function(Map), + ObjectCopyWith<$R, Widget Function(Map), + Widget Function(Map)>> get widgets => MapCopyWith( + $value.widgets, + (v, t) => ObjectCopyWith(v, $identity, t), + (v) => call(widgets: v)); + @override + $R call( + {DeckStyle? baseStyle, + Map? styles, + Map)>? widgets, + SlideParts? parts, + bool? debug}) => + $apply(FieldCopyWithData({ + if (baseStyle != null) #baseStyle: baseStyle, + if (styles != null) #styles: styles, + if (widgets != null) #widgets: widgets, + if (parts != null) #parts: parts, + if (debug != null) #debug: debug + })); + @override + DeckOptions $make(CopyWithData data) => DeckOptions( + baseStyle: data.get(#baseStyle, or: $value.baseStyle), + styles: data.get(#styles, or: $value.styles), + widgets: data.get(#widgets, or: $value.widgets), + parts: data.get(#parts, or: $value.parts), + debug: data.get(#debug, or: $value.debug)); + + @override + DeckOptionsCopyWith<$R2, DeckOptions, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _DeckOptionsCopyWithImpl($value, $cast, t); +} diff --git a/packages/superdeck/lib/src/modules/deck/deck_provider.dart b/packages/superdeck/lib/src/modules/deck/deck_provider.dart new file mode 100644 index 00000000..ea39da95 --- /dev/null +++ b/packages/superdeck/lib/src/modules/deck/deck_provider.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:superdeck/src/components/atoms/async_snapshot_widget.dart'; +import 'package:superdeck/src/modules/common/helpers/root_bundle_data_store.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +import '../common/helpers/constants.dart'; +import '../common/helpers/provider.dart'; +import '../slide_capture/thumbnail_controller.dart'; +import 'deck_controller.dart'; +import 'deck_options.dart'; + +class DeckControllerBuilder extends StatelessWidget { + final DeckOptions options; + + const DeckControllerBuilder({ + super.key, + required this.builder, + required this.options, + }); + + final Widget Function(DeckController controller) builder; + + @override + Widget build(BuildContext context) { + final configuration = DeckConfiguration(); + final dataStore = kCanRunProcess + ? FileSystemDataStore(configuration) + : AssetBundleDataStore(configuration); + return AsyncStreamWidget( + stream: dataStore.loadDeckReferenceStream(), + builder: (snapshot) { + return _DeckControllerProvider( + options: options, + reference: snapshot, + builder: builder, + dataStore: dataStore, + ); + }, + ); + } +} + +class _DeckControllerProvider extends StatefulWidget { + const _DeckControllerProvider({ + required this.options, + required this.reference, + required this.builder, + required this.dataStore, + }); + + final DeckOptions options; + final DeckReference reference; + final Widget Function(DeckController controller) builder; + final IDataStore dataStore; + @override + State<_DeckControllerProvider> createState() => + _DeckControllerProviderState(); +} + +class _DeckControllerProviderState extends State<_DeckControllerProvider> { + late final DeckController _deckController; + late final ThumbnailController _thumbnailController; + + @override + void initState() { + super.initState(); + _deckController = DeckController.build( + slides: widget.reference.slides, + options: widget.options, + dataStore: widget.dataStore, + ); + _thumbnailController = ThumbnailController(); + } + + @override + void didUpdateWidget(_DeckControllerProvider oldWidget) { + super.didUpdateWidget(oldWidget); + final referenceChanged = widget.reference != oldWidget.reference; + final optionsChanged = widget.options != oldWidget.options; + + if (referenceChanged || optionsChanged) { + _deckController.update( + slides: widget.reference.slides, + options: widget.options, + ); + } + } + + @override + void dispose() { + super.dispose(); + _deckController.dispose(); + _thumbnailController.dispose(); + } + + @override + Widget build(BuildContext context) { + return InheritedNotifierData( + data: _deckController, + child: InheritedNotifierData( + data: _thumbnailController, + child: widget.builder(_deckController), + ), + ); + } +} diff --git a/packages/superdeck/lib/src/modules/deck/slide_configuration.dart b/packages/superdeck/lib/src/modules/deck/slide_configuration.dart new file mode 100644 index 00000000..f18ab278 --- /dev/null +++ b/packages/superdeck/lib/src/modules/deck/slide_configuration.dart @@ -0,0 +1,50 @@ +import 'package:dart_mappable/dart_mappable.dart'; +import 'package:flutter/widgets.dart'; +import 'package:mix/mix.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +import '../../components/parts/slide_parts.dart'; +import '../common/helpers/provider.dart'; +import 'deck_options.dart'; + +part 'slide_configuration.mapper.dart'; + +@MappableClass() +class SlideConfiguration with SlideConfigurationMappable { + final int slideIndex; + final Style style; + final Slide _slide; + final bool debug; + final SlideParts? parts; + final Map _widgets; + final String thumbnailFile; + + final bool isExporting; + SlideConfiguration({ + required this.slideIndex, + required this.style, + required Slide slide, + this.debug = false, + this.parts, + required this.thumbnailFile, + Map widgets = const {}, + this.isExporting = false, + }) : _slide = slide, + _widgets = widgets; + + SlideOptions get options => _slide.options ?? const SlideOptions(); + + String get key => _slide.key; + + Slide get data => _slide; + + List get sections => _slide.sections; + + List get comments => _slide.comments; + + WidgetBlockBuilder? getWidget(String name) => _widgets[name]; + + static SlideConfiguration of(BuildContext context) { + return InheritedData.of(context); + } +} diff --git a/packages/superdeck/lib/src/modules/deck/slide_configuration.mapper.dart b/packages/superdeck/lib/src/modules/deck/slide_configuration.mapper.dart new file mode 100644 index 00000000..10eb5dcc --- /dev/null +++ b/packages/superdeck/lib/src/modules/deck/slide_configuration.mapper.dart @@ -0,0 +1,207 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member +// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter + +part of 'slide_configuration.dart'; + +class SlideConfigurationMapper extends ClassMapperBase { + SlideConfigurationMapper._(); + + static SlideConfigurationMapper? _instance; + static SlideConfigurationMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = SlideConfigurationMapper._()); + SlideMapper.ensureInitialized(); + } + return _instance!; + } + + @override + final String id = 'SlideConfiguration'; + + static int _$slideIndex(SlideConfiguration v) => v.slideIndex; + static const Field _f$slideIndex = + Field('slideIndex', _$slideIndex); + static Style _$style(SlideConfiguration v) => v.style; + static const Field _f$style = + Field('style', _$style); + static Slide _$_slide(SlideConfiguration v) => v._slide; + static const Field _f$_slide = + Field('_slide', _$_slide, key: 'slide'); + static bool _$debug(SlideConfiguration v) => v.debug; + static const Field _f$debug = + Field('debug', _$debug, opt: true, def: false); + static SlideParts? _$parts(SlideConfiguration v) => v.parts; + static const Field _f$parts = + Field('parts', _$parts, opt: true); + static String _$thumbnailFile(SlideConfiguration v) => v.thumbnailFile; + static const Field _f$thumbnailFile = + Field('thumbnailFile', _$thumbnailFile); + static Map)> _$_widgets( + SlideConfiguration v) => + v._widgets; + static const Field)>> _f$_widgets = + Field('_widgets', _$_widgets, key: 'widgets', opt: true, def: const {}); + static bool _$isExporting(SlideConfiguration v) => v.isExporting; + static const Field _f$isExporting = + Field('isExporting', _$isExporting, opt: true, def: false); + + @override + final MappableFields fields = const { + #slideIndex: _f$slideIndex, + #style: _f$style, + #_slide: _f$_slide, + #debug: _f$debug, + #parts: _f$parts, + #thumbnailFile: _f$thumbnailFile, + #_widgets: _f$_widgets, + #isExporting: _f$isExporting, + }; + + static SlideConfiguration _instantiate(DecodingData data) { + return SlideConfiguration( + slideIndex: data.dec(_f$slideIndex), + style: data.dec(_f$style), + slide: data.dec(_f$_slide), + debug: data.dec(_f$debug), + parts: data.dec(_f$parts), + thumbnailFile: data.dec(_f$thumbnailFile), + widgets: data.dec(_f$_widgets), + isExporting: data.dec(_f$isExporting)); + } + + @override + final Function instantiate = _instantiate; + + static SlideConfiguration fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static SlideConfiguration fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin SlideConfigurationMappable { + String toJson() { + return SlideConfigurationMapper.ensureInitialized() + .encodeJson(this as SlideConfiguration); + } + + Map toMap() { + return SlideConfigurationMapper.ensureInitialized() + .encodeMap(this as SlideConfiguration); + } + + SlideConfigurationCopyWith + get copyWith => _SlideConfigurationCopyWithImpl( + this as SlideConfiguration, $identity, $identity); + @override + String toString() { + return SlideConfigurationMapper.ensureInitialized() + .stringifyValue(this as SlideConfiguration); + } + + @override + bool operator ==(Object other) { + return SlideConfigurationMapper.ensureInitialized() + .equalsValue(this as SlideConfiguration, other); + } + + @override + int get hashCode { + return SlideConfigurationMapper.ensureInitialized() + .hashValue(this as SlideConfiguration); + } +} + +extension SlideConfigurationValueCopy<$R, $Out> + on ObjectCopyWith<$R, SlideConfiguration, $Out> { + SlideConfigurationCopyWith<$R, SlideConfiguration, $Out> + get $asSlideConfiguration => + $base.as((v, t, t2) => _SlideConfigurationCopyWithImpl(v, t, t2)); +} + +abstract class SlideConfigurationCopyWith<$R, $In extends SlideConfiguration, + $Out> implements ClassCopyWith<$R, $In, $Out> { + SlideCopyWith<$R, Slide, Slide> get _slide; + MapCopyWith< + $R, + String, + Widget Function(Map), + ObjectCopyWith<$R, Widget Function(Map), + Widget Function(Map)>> get _widgets; + $R call( + {int? slideIndex, + Style? style, + Slide? slide, + bool? debug, + SlideParts? parts, + String? thumbnailFile, + Map)>? widgets, + bool? isExporting}); + SlideConfigurationCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); +} + +class _SlideConfigurationCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, SlideConfiguration, $Out> + implements SlideConfigurationCopyWith<$R, SlideConfiguration, $Out> { + _SlideConfigurationCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + SlideConfigurationMapper.ensureInitialized(); + @override + SlideCopyWith<$R, Slide, Slide> get _slide => + $value._slide.copyWith.$chain((v) => call(slide: v)); + @override + MapCopyWith< + $R, + String, + Widget Function(Map), + ObjectCopyWith<$R, Widget Function(Map), + Widget Function(Map)>> get _widgets => MapCopyWith( + $value._widgets, + (v, t) => ObjectCopyWith(v, $identity, t), + (v) => call(widgets: v)); + @override + $R call( + {int? slideIndex, + Style? style, + Slide? slide, + bool? debug, + Object? parts = $none, + String? thumbnailFile, + Map)>? widgets, + bool? isExporting}) => + $apply(FieldCopyWithData({ + if (slideIndex != null) #slideIndex: slideIndex, + if (style != null) #style: style, + if (slide != null) #slide: slide, + if (debug != null) #debug: debug, + if (parts != $none) #parts: parts, + if (thumbnailFile != null) #thumbnailFile: thumbnailFile, + if (widgets != null) #widgets: widgets, + if (isExporting != null) #isExporting: isExporting + })); + @override + SlideConfiguration $make(CopyWithData data) => SlideConfiguration( + slideIndex: data.get(#slideIndex, or: $value.slideIndex), + style: data.get(#style, or: $value.style), + slide: data.get(#slide, or: $value._slide), + debug: data.get(#debug, or: $value.debug), + parts: data.get(#parts, or: $value.parts), + thumbnailFile: data.get(#thumbnailFile, or: $value.thumbnailFile), + widgets: data.get(#widgets, or: $value._widgets), + isExporting: data.get(#isExporting, or: $value.isExporting)); + + @override + SlideConfigurationCopyWith<$R2, SlideConfiguration, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _SlideConfigurationCopyWithImpl($value, $cast, t); +} diff --git a/packages/superdeck/lib/src/modules/navigation/navigation_controller.dart b/packages/superdeck/lib/src/modules/navigation/navigation_controller.dart new file mode 100644 index 00000000..f8f9bee2 --- /dev/null +++ b/packages/superdeck/lib/src/modules/navigation/navigation_controller.dart @@ -0,0 +1,171 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../../components/molecules/slide_screen.dart'; +import '../common/helpers/provider.dart'; +import '../deck/slide_configuration.dart'; +import '../navigation/routes.dart'; + +class NavigationController extends ChangeNotifier { + late GoRouter _router; + bool _isMenuOpen = false; + late SlideConfiguration _currentSlide; + late List _slides; + bool _isNotesVisible = false; + NavigationController({ + required List slides, + }) { + _router = _buildRouter(slides); + _currentSlide = slides[0]; + _slides = slides; + } + + GoRouter get router => _router; + + bool get isMenuOpen => _isMenuOpen; + bool get isNotesOpen => _isNotesVisible; + + SlideConfiguration get currentSlide => _currentSlide; + + int get totalSlides => _slides.length; + + void updateSlides(List slides) { + _router = _buildRouter(slides); + _currentSlide = currentSlide.slideIndex < slides.length + ? slides[currentSlide.slideIndex] + : slides.last; + _slides = slides; + + goToSlide(_currentSlide.slideIndex); + } + + VoidCallback _routingListenerCallback() { + return () { + final uri = _router.routeInformationProvider.value.uri; + + final pathParam = uri.toString().startsWith(SDPaths.slides.path) + ? uri.pathSegments.last + : '0'; + final slideIndex = int.tryParse(pathParam) ?? 0; + + if (slideIndex != _currentSlide.slideIndex) { + _currentSlide = _slides[slideIndex]; + notifyListeners(); + } + }; + } + + @override + void dispose() { + _router.dispose(); + super.dispose(); + } + + static NavigationController of(BuildContext context) { + return InheritedNotifierData.of(context); + } + + GoRouter _buildRouter(List slides) { + return GoRouter( + initialLocation: SDPaths.slides.goRoute, + redirect: (context, state) => + state.path == SDPaths.root.path ? SDPaths.slides.goRoute : null, + navigatorKey: _kRootNavigatorKey, + restorationScopeId: 'root', + routes: [ + GoRoute( + parentNavigatorKey: _kRootNavigatorKey, + path: SDPaths.slides.goRoute, + builder: (context, state) => SlideScreen(slides[0]), + routes: slides.mapIndexed((index, slide) { + return GoRoute( + path: index.toString(), + pageBuilder: (context, state) { + final slideIndex = int.parse(state.path ?? '0'); + return _getPageTransition( + SlideScreen(slides[slideIndex]), + state, + ); + }, + ); + }).toList(), + ), + ], + )..routeInformationProvider.addListener(_routingListenerCallback()); + } + + void goToSlide(int index) { + if (index < 0 || index >= _slides.length) { + return; + } + _router.go(SDPaths.slides.slide.define(index.toString()).path); + } + + void nextSlide() => goToSlide(_currentSlide.slideIndex + 1); + + void previousSlide() => goToSlide(_currentSlide.slideIndex - 1); + + void toggleMenu() { + _isMenuOpen = !_isMenuOpen; + notifyListeners(); + } + + void goToExport() { + _router.go(SDPaths.export.path); + } + + void closeMenu() { + _isMenuOpen = false; + notifyListeners(); + } + + void openMenu() { + _isMenuOpen = true; + notifyListeners(); + } + + void toggleNotes() { + _isNotesVisible = !_isNotesVisible; + notifyListeners(); + } + + void hideNotes() { + _isNotesVisible = false; + notifyListeners(); + } + + void showNotes() { + _isNotesVisible = true; + notifyListeners(); + } +} + +final _kRootNavigatorKey = GlobalKey(debugLabel: 'root'); + +CustomTransitionPage _getPageTransition( + Widget child, + GoRouterState state, +) { + return CustomTransitionPage( + key: state.pageKey, + maintainState: true, + transitionDuration: const Duration(milliseconds: 1000), + child: child, + transitionsBuilder: (context, animation, secondaryAnimation, child) { + return FadeTransition( + opacity: Tween( + begin: 0.0, + end: 1.0, + ).animate(animation), + child: FadeTransition( + opacity: Tween( + begin: 1.0, + end: 0.0, + ).animate(secondaryAnimation), + child: child, + ), + ); + }, + ); +} diff --git a/packages/superdeck/lib/src/modules/navigation/navigation_provider.dart b/packages/superdeck/lib/src/modules/navigation/navigation_provider.dart new file mode 100644 index 00000000..87a4b713 --- /dev/null +++ b/packages/superdeck/lib/src/modules/navigation/navigation_provider.dart @@ -0,0 +1,66 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:go_router/go_router.dart'; + +import '../common/helpers/provider.dart'; +import '../deck/deck_controller.dart'; +import 'navigation_controller.dart'; + +class NavigationProviderBuilder extends StatefulWidget { + final Widget Function(GoRouter router) builder; + const NavigationProviderBuilder({ + super.key, + required DeckController deckController, + required this.builder, + }) : _deckController = deckController; + + final DeckController _deckController; + + @override + State createState() => + _NavigationProviderBuilderState(); +} + +class _NavigationProviderBuilderState extends State { + late final NavigationController _navigationController; + + @override + void initState() { + super.initState(); + + _navigationController = NavigationController( + slides: widget._deckController.slides, + ); + } + + @override + void dispose() { + super.dispose(); + + _navigationController.dispose(); + } + + @override + void didUpdateWidget(NavigationProviderBuilder oldWidget) { + super.didUpdateWidget(oldWidget); + + final slidesChanged = listEquals( + widget._deckController.slides, + oldWidget._deckController.slides, + ); + + if (slidesChanged) { + _navigationController.updateSlides( + widget._deckController.slides, + ); + } + } + + @override + Widget build(BuildContext context) { + return InheritedNotifierData( + data: _navigationController, + child: widget.builder(_navigationController.router), + ); + } +} diff --git a/packages/superdeck/lib/src/modules/navigation/routes.dart b/packages/superdeck/lib/src/modules/navigation/routes.dart new file mode 100644 index 00000000..b9a8dfb6 --- /dev/null +++ b/packages/superdeck/lib/src/modules/navigation/routes.dart @@ -0,0 +1,27 @@ +import 'package:go_router_paths/go_router_paths.dart'; + +class SDPaths { + static Path get root => Path('/'); + static ExportPath get export => ExportPath(); + // static Param get slides => Param('slides', 'slideId'); + static SlidesPath get slides => SlidesPath(); + + static ChatPath get chat => ChatPath(); +} + +class ExportPath extends Path { + ExportPath() : super('export'); +} + +class SlidesPath extends Path { + SlidesPath() : super('slides'); + SlidePath get slide => SlidePath(this); +} + +class SlidePath extends Param { + SlidePath(SlidesPath slidesPath) : super.only('slideId', parent: slidesPath); +} + +class ChatPath extends Path { + ChatPath() : super('chat'); +} diff --git a/packages/superdeck/lib/src/modules/slide_capture/pdf_controller.dart b/packages/superdeck/lib/src/modules/slide_capture/pdf_controller.dart new file mode 100644 index 00000000..bebb034d --- /dev/null +++ b/packages/superdeck/lib/src/modules/slide_capture/pdf_controller.dart @@ -0,0 +1,289 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:file_saver/file_saver.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:pdf/pdf.dart'; +import 'package:pdf/widgets.dart' as pw; +import 'package:superdeck/src/modules/common/helpers/constants.dart'; +import 'package:superdeck/src/modules/deck/slide_configuration.dart'; +import 'package:universal_html/html.dart' as html; + +import 'slide_capture_service.dart'; + +/// Status of the export process +enum PdfExportStatus { + /// Initial state, no export in progress + idle, + + /// Currently capturing slide images + capturing, + + /// Building PDF from captured images + building, + + /// Export completed successfully + complete, + + /// Preparing slides for export + preparing +} + +/// Controller for exporting slides to PDF +/// +/// Handles capturing slides as images and combining them into a PDF document. +/// Supports both web and native platforms. +class PdfController extends ChangeNotifier { + /// Creates a new [PdfController] + /// + /// [slides] - List of slides to export + /// [slideCaptureService] - Service used to capture slide images + /// [waitDuration] - Duration to wait between operations + PdfController({ + required this.slides, + required this.slideCaptureService, + Duration waitDuration = const Duration(milliseconds: 100), + }) : _waitDuration = waitDuration { + _pageController = PageController(initialPage: 0); + _slideKeys = { + for (var slide in slides) slide.key: GlobalKey(), + }; + } + + late final PageController _pageController; + late final Map _slideKeys; + PdfExportStatus _exportStatus = PdfExportStatus.idle; + final List _images = []; + bool _disposed = false; + bool _cancelled = false; + + void _checkExportAllowed() { + if (_disposed) throw _ExportCancelledException('Controller disposed'); + if (_cancelled) throw _ExportCancelledException(); + } + + /// The list of slides to export + final List slides; + + /// Service used to capture slides + final SlideCaptureService slideCaptureService; + + /// Duration used to wait between operations + final Duration _waitDuration; + + /// Whether this controller has been disposed + bool get disposed => _disposed; + + /// Page controller for navigating between slides + PageController get pageController => _pageController; + + /// Gets the [GlobalKey] for a specific slide + GlobalKey getSlideKey(SlideConfiguration slide) => _slideKeys[slide.key]!; + + /// Current status of the export process + PdfExportStatus get exportStatus => _exportStatus; + + /// Export progress from 0.0 to 1.0 + double get progress => + _slideKeys.isNotEmpty ? _images.length / _slideKeys.length : 0.0; + + (int current, int total) get progressTuple => + (_images.length, _slideKeys.length); + + /// Waits for a render boundary widget to be painted + Future _waitForRenderBoundaryPaint(GlobalKey key) async { + while (key.currentContext == null) { + await Future.delayed(const Duration(milliseconds: 10)); + } + + final repaintBoundary = key.currentContext!.findRenderObject()!; + final isAttached = repaintBoundary.attached; + + while (!isAttached) { + await Future.delayed(const Duration(milliseconds: 10)); + } + + await WidgetsBinding.instance.endOfFrame; + } + + /// Captures an image from a [GlobalKey] with retry logic + Future _captureImageWithRetry(GlobalKey key) async { + const maxAttempts = 3; + for (int attempt = 1; attempt <= maxAttempts; attempt++) { + try { + return await slideCaptureService.captureFromKey( + quality: + kIsWeb ? SlideCaptureQuality.thumbnail : SlideCaptureQuality.good, + key: key, + ); + } catch (error) { + if (attempt == maxAttempts) rethrow; + await Future.delayed(const Duration(milliseconds: 100)); + } + } + throw Exception('Failed to capture image after $maxAttempts attempts.'); + } + + /// Prepares slides for export by ensuring they are properly rendered + Future _prepare() async { + for (var i = 0; i < _slideKeys.length; i++) { + // just return if cancelled + if (_cancelled) return; + final slide = slides[i]; + final key = _slideKeys[slide.key]!; + + await _pageController.animateToPage( + i, + duration: const Duration(milliseconds: 50), + curve: Curves.linear, + ); + + await _waitForRenderBoundaryPaint(key); + } + } + + /// Starts the export process + /// + /// Captures slides as images and combines them into a PDF document. + Future export() async { + _cancelled = false; + _exportStatus = PdfExportStatus.preparing; + notifyListeners(); + + try { + await _prepare(); + + _exportStatus = PdfExportStatus.capturing; + notifyListeners(); + + for (var i = 0; i < _slideKeys.length; i++) { + _checkExportAllowed(); // check before starting this iteration + + final slide = slides[i]; + final key = _slideKeys[slide.key]!; + + await _pageController.animateToPage( + i, + duration: const Duration(milliseconds: 1), + curve: Curves.linear, + ); + + _checkExportAllowed(); + await _waitForRenderBoundaryPaint(key); + _checkExportAllowed(); + + final image = await _captureImageWithRetry(key); + _checkExportAllowed(); + + _images.add(image); + notifyListeners(); + } + + _exportStatus = PdfExportStatus.building; + notifyListeners(); + + await Future.delayed(_waitDuration); + _checkExportAllowed(); + + final pdf = await compute(_buildPdf, [..._images]); + _checkExportAllowed(); + + _savePdf(pdf); + + _exportStatus = PdfExportStatus.complete; + _images.clear(); + notifyListeners(); + } on _ExportCancelledException catch (e) { + // Handle cancellation: update status and notify listeners. + _exportStatus = PdfExportStatus.idle; + + // Optionally, log the cancellation. + log(e.toString()); + } finally { + if (!_disposed) { + notifyListeners(); + } + } + } + + /// Saves the generated PDF file + /// + /// Uses different approaches for web and native platforms + Future _savePdf(Uint8List pdf) async { + try { + if (kIsWeb) { + final blob = html.Blob([pdf], 'application/pdf'); + final url = html.Url.createObjectUrlFromBlob(blob); + + html.AnchorElement(href: url) + ..setAttribute('download', 'superdeck.pdf') + ..click(); + + return; + } + + log('Saving pdf'); + final result = await FileSaver.instance.saveAs( + name: 'superdeck', + bytes: pdf, + ext: 'pdf', + mimeType: MimeType.pdf, + ); + log('Save result: $result'); + } catch (e) { + log('Error saving pdf: $e'); + } + } + + void cancel() { + _cancelled = true; + } + + @override + void dispose() { + _disposed = true; + _pageController.dispose(); + super.dispose(); + } +} + +/// Builds a PDF document from a list of images +/// +/// This runs on a separate isolate via [compute] +Future _buildPdf(List images) async { + final pdf = pw.Document(); + + for (final imageData in images) { + final image = pw.MemoryImage(imageData); + + final pdfImage = pw.Image( + image, + width: kResolution.width, + height: kResolution.height, + ); + + pdf.addPage( + pw.Page( + pageFormat: PdfPageFormat( + kResolution.width, + kResolution.height, + ), + build: (pw.Context context) { + return pw.Center( + child: pdfImage, + ); + }, + ), + ); + } + + return await pdf.save(); +} + +class _ExportCancelledException implements Exception { + final String message; + _ExportCancelledException([this.message = 'Export cancelled']); + @override + String toString() => message; +} diff --git a/packages/superdeck/lib/src/modules/slide_capture/pdf_export_screen.dart b/packages/superdeck/lib/src/modules/slide_capture/pdf_export_screen.dart new file mode 100644 index 00000000..dbdcbf2d --- /dev/null +++ b/packages/superdeck/lib/src/modules/slide_capture/pdf_export_screen.dart @@ -0,0 +1,192 @@ +import 'package:flutter/material.dart'; +import 'package:superdeck/src/modules/common/helpers/constants.dart'; +import 'package:superdeck/src/modules/common/helpers/provider.dart'; +import 'package:superdeck/src/modules/deck/deck_controller.dart'; +import 'package:superdeck/src/modules/slide_capture/slide_capture_service.dart'; + +import '../../components/atoms/slide_view.dart'; +import '../deck/slide_configuration.dart'; +import 'pdf_controller.dart'; + +class PdfExportDialogScreen extends StatefulWidget { + const PdfExportDialogScreen({super.key, required this.slides}); + + final List slides; + + @override + State createState() => _PdfExportDialogScreenState(); + + static void show(BuildContext context) { + final deckController = DeckController.of(context); + showDialog( + context: context, + builder: (context) => + PdfExportDialogScreen(slides: deckController.slides), + ); + } +} + +class _PdfExportDialogScreenState extends State { + late PdfController _exportController; + + @override + void initState() { + super.initState(); + _setupExportController(); + } + + void _setupExportController() { + _exportController = PdfController( + slides: widget.slides, + slideCaptureService: SlideCaptureService(), + ); + + WidgetsBinding.instance.addPostFrameCallback((_) => _handleExport()); + } + + Future _handleExport() async { + try { + await _exportController.export(); + } finally { + if (context.mounted) { + // ignore: use_build_context_synchronously + Navigator.of(context).pop(); + } + } + } + + @override + void didUpdateWidget(PdfExportDialogScreen oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.slides != widget.slides) { + _exportController.dispose(); + _setupExportController(); + } + } + + @override + void dispose() { + _exportController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Dialog( + insetPadding: const EdgeInsets.all(0), + child: SizedBox.fromSize( + size: kResolution, + child: ListenableBuilder( + listenable: _exportController, + builder: (context, _) { + return Stack( + children: [ + PageView.builder( + controller: _exportController.pageController, + itemCount: _exportController.slides.length, + itemBuilder: (context, index) { + // Set to exporting true + final slide = _exportController.slides[index].copyWith( + isExporting: true, + debug: false, + ); + + return RepaintBoundary( + key: _exportController.getSlideKey(slide), + child: InheritedData( + data: slide, + child: SlideView(slide), + ), + ); + }, + ), + Positioned.fill( + child: Container( + color: Colors.black, + child: Align( + alignment: Alignment.center, + child: _PdfExportBar( + exportController: _exportController, + ), + ), + ), + ) + ], + ); + }), + ), + ); + } +} + +class _PdfExportBar extends StatelessWidget { + const _PdfExportBar({ + required this.exportController, + }); + + final PdfController exportController; + + /// Human readable progress text + String get _progressText { + final current = exportController.progressTuple.$1; + final total = exportController.progressTuple.$2; + return switch (exportController.exportStatus) { + PdfExportStatus.building => 'Building PDF...', + PdfExportStatus.complete => 'Done', + PdfExportStatus.capturing => 'Exporting $current / $total', + PdfExportStatus.idle => 'Exporting $current / $total', + PdfExportStatus.preparing => 'Preparing...', + }; + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 16.0, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + exportController.exportStatus == PdfExportStatus.complete + ? Icon( + Icons.check_circle, + color: Theme.of(context).colorScheme.primary, + size: 32, + ) + : SizedBox( + height: 32, + width: 32, + child: CircularProgressIndicator( + color: Theme.of(context).colorScheme.primary, + value: exportController.exportStatus == + PdfExportStatus.building + ? null + : exportController.progress, + ), + ), + const SizedBox(height: 16.0), + Text( + _progressText, + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: Colors.white, + ), + ), + const SizedBox(height: 16.0), + TextButton.icon( + onPressed: () { + exportController.cancel(); + Navigator.of(context).pop(); + }, + icon: const Icon(Icons.cancel, color: Colors.white), + label: const Text( + 'Cancel', + style: TextStyle(color: Colors.white), + ), + ), + ], + ), + ); + } +} diff --git a/packages/superdeck/lib/services/snapshot_service.dart b/packages/superdeck/lib/src/modules/slide_capture/slide_capture_service.dart similarity index 73% rename from packages/superdeck/lib/services/snapshot_service.dart rename to packages/superdeck/lib/src/modules/slide_capture/slide_capture_service.dart index 6e8570c8..7a444ea6 100644 --- a/packages/superdeck/lib/services/snapshot_service.dart +++ b/packages/superdeck/lib/src/modules/slide_capture/slide_capture_service.dart @@ -6,51 +6,56 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; +import 'package:superdeck/src/modules/common/helpers/provider.dart'; -import '../components/atoms/slide_view.dart'; -import '../helpers/constants.dart'; -import '../providers/assets_provider.dart'; -import '../providers/examples_provider.dart'; -import '../providers/snapshot_provider.dart'; -import '../providers/style_provider.dart'; -import '../superdeck.dart'; +import '../../components/atoms/slide_view.dart'; +import '../common/helpers/constants.dart'; +import '../deck/slide_configuration.dart'; -enum SnapshotQuality { - low('Low', pixelRatio: 0.4), - good('Good', pixelRatio: 1), - better('Better', pixelRatio: 2), - best('Best', pixelRatio: 3); +enum SlideCaptureQuality { + thumbnail(0.3), + good(1), + better(2), + best(3); - const SnapshotQuality(this.label, {required this.pixelRatio}); + const SlideCaptureQuality( + this.pixelRatio, + ); - final String label; final double pixelRatio; } -// -class SnapshotService { - SnapshotService._(); - - static final SnapshotService instance = SnapshotService._(); +class SlideCaptureService { + SlideCaptureService(); static final _generationQueue = {}; static const _maxConcurrentGenerations = 3; - Future generate({ - required SnapshotQuality quality, - required Slide slide, + Future capture({ + SlideCaptureQuality quality = SlideCaptureQuality.thumbnail, + required SlideConfiguration slide, + required BuildContext context, }) async { final queueKey = shortHash(slide.key + quality.name); try { while (_generationQueue.length > _maxConcurrentGenerations) { - await Future.delayed(const Duration(milliseconds: 100)); + await Future.delayed(const Duration(milliseconds: 50)); } _generationQueue.add(queueKey); + final exportingSlide = slide.copyWith( + debug: false, + isExporting: true, + ); + final image = await _fromWidgetToImage( - SnapshotProvider(isCapturing: true, child: SlideView(slide)), - context: kScaffoldKey.currentContext!, + InheritedData( + data: exportingSlide, + child: SlideView(exportingSlide), + ), + // ignore: use_build_context_synchronously + context: context, pixelRatio: quality.pixelRatio, targetSize: kResolution, ); @@ -64,9 +69,9 @@ class SnapshotService { } } - Future generateWithKey({ + Future captureFromKey({ required GlobalKey key, - required SnapshotQuality quality, + required SlideCaptureQuality quality, }) async { final boundary = key.currentContext!.findRenderObject() as RenderRepaintBoundary; @@ -97,21 +102,12 @@ class SnapshotService { try { final child = InheritedTheme.captureAll( context, - StyleProvider.inherit( - context: context, - child: AssetsProvider.inherit( - context: context, - child: ExamplesProvider.inherit( - context: context, - child: MediaQuery( - data: MediaQuery.of(context), - child: MaterialApp( - theme: Theme.of(context), - debugShowCheckedModeBanner: false, - home: Scaffold(body: widget), - ), - ), - ), + MediaQuery( + data: MediaQuery.of(context), + child: MaterialApp( + theme: Theme.of(context), + debugShowCheckedModeBanner: false, + home: Scaffold(body: widget), ), ), ); @@ -180,7 +176,7 @@ class SnapshotService { ..flushCompositingBits() ..flushPaint(); - await Future.delayed(const Duration(milliseconds: 250)); + await Future.delayed(const Duration(milliseconds: 100)); if (!isDirty) { log('Image generation completed.'); diff --git a/packages/superdeck/lib/src/modules/slide_capture/thumbnail_controller.dart b/packages/superdeck/lib/src/modules/slide_capture/thumbnail_controller.dart new file mode 100644 index 00000000..e72f51fe --- /dev/null +++ b/packages/superdeck/lib/src/modules/slide_capture/thumbnail_controller.dart @@ -0,0 +1,214 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:superdeck/src/components/atoms/cache_image_widget.dart'; +import 'package:superdeck/src/modules/deck/slide_configuration.dart'; + +import '../../components/atoms/loading_indicator.dart'; +import '../common/helpers/constants.dart'; +import '../common/helpers/provider.dart'; +import 'slide_capture_service.dart'; + +typedef AsyncFileGenerator = Future Function( + BuildContext context, + bool force, +); + +enum AsyncFileStatus { + idle, + loading, + done, + error, +} + +/// A model that asynchronously loads an image and notifies listeners of changes. +class AsyncThumbnail extends ChangeNotifier { + AsyncFileStatus _status = AsyncFileStatus.idle; + File? _imageFile; + Object? _error; + bool _disposed = false; + + /// The generator function that asynchronously returns an Image. + final AsyncFileGenerator _generator; + + AsyncThumbnail({required AsyncFileGenerator generator}) + : _generator = generator; + + Future _generate(BuildContext context, [bool force = false]) async { + if (_disposed) return; + + _status = AsyncFileStatus.loading; + _error = null; + if (_imageFile != null) { + FileImage(_imageFile!).evict(); + } + _imageFile = null; + notifyListeners(); + + try { + _imageFile = await _generator(context, force); + } catch (e) { + _error = e; + _status = AsyncFileStatus.error; + _imageFile = null; + } finally { + _status = AsyncFileStatus.done; + if (!_disposed) { + notifyListeners(); + } + } + } + + @override + void dispose() { + super.dispose(); + _disposed = true; + } + + Future load(BuildContext context, [bool force = false]) async { + if (force) { + return _generate(context, true); + } + return switch (_status) { + AsyncFileStatus.idle => _generate(context), + AsyncFileStatus.done => Future.value(), + AsyncFileStatus.loading => Future.value(), + AsyncFileStatus.error => _generate(context), + }; + } + + Widget _errorWidget(BuildContext context, AsyncThumbnail thumbnail) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.error, + color: Colors.red, + size: 40, + ), + const SizedBox(height: 10), + ElevatedButton.icon( + onPressed: () => thumbnail.load(context, true), + icon: const Icon(Icons.refresh), + label: const Text('Refresh'), + ), + ], + ), + ); + } + + /// Synchronously returns the loaded image. + /// + /// Should be called only when the image has finished loading. + /// Throws an exception if the image is still loading or if loading failed. + File get requireData { + return switch (_status) { + AsyncFileStatus.idle => throw Exception("Image has not been loaded yet."), + AsyncFileStatus.loading => throw Exception("Image is still loading."), + AsyncFileStatus.error => throw Exception("Image failed to load: $_error"), + AsyncFileStatus.done => _imageFile!, + }; + } + + Widget build(BuildContext context) { + return ListenableBuilder( + listenable: this, + builder: (context, child) { + return switch (_status) { + AsyncFileStatus.idle => const IsometricLoading(), + AsyncFileStatus.loading => const IsometricLoading(), + AsyncFileStatus.done => Image( + gaplessPlayback: true, + image: getImageProvider(requireData.uri), + errorBuilder: (context, error, _) { + return _errorWidget(context, this); + }, + ), + AsyncFileStatus.error => _errorWidget(context, this), + }; + }, + ); + } +} + +/// A controller that manages thumbnail images for slides. +class ThumbnailController extends ChangeNotifier { + ThumbnailController(); + + final Map _thumbnails = {}; + final _slideCaptureService = SlideCaptureService(); + + void generateThumbnails( + List slides, + BuildContext context, { + bool force = false, + }) { + for (final slide in slides) { + _getAsyncThumbnail(slide, context).load(context, force); + } + } + + @override + void dispose() { + super.dispose(); + + for (final thumbnail in _thumbnails.values) { + thumbnail.dispose(); + } + _thumbnails.clear(); + } + + AsyncThumbnail get( + SlideConfiguration slide, + BuildContext context, + ) { + return _getAsyncThumbnail(slide, context)..load(context); + } + + AsyncThumbnail _getAsyncThumbnail( + SlideConfiguration slide, BuildContext context) { + return _thumbnails.putIfAbsent(slide.key, () { + return AsyncThumbnail(generator: (context, force) async { + return _generateThumbnail( + slide: slide, + context: context, + force: force, + ); + }); + }); + } + + Future _generateThumbnail({ + required SlideConfiguration slide, + required BuildContext context, + required bool force, + }) async { + final thumbnailFile = File(slide.thumbnailFile); + + if (!kCanRunProcess) { + return thumbnailFile; + } + + final isValid = + await thumbnailFile.exists() && (await thumbnailFile.length()) > 0; + + if (isValid && !force) { + return thumbnailFile; + } + + final imageData = await _slideCaptureService.capture( + slide: slide, + // ignore: use_build_context_synchronously + context: context, + ); + + await thumbnailFile.writeAsBytes(imageData, flush: false); + + return thumbnailFile; + } + + static ThumbnailController of(BuildContext context) { + return InheritedNotifierData.of(context); + } +} diff --git a/packages/superdeck/lib/styles/style_spec.dart b/packages/superdeck/lib/styles/style_spec.dart deleted file mode 100644 index 8f2ac914..00000000 --- a/packages/superdeck/lib/styles/style_spec.dart +++ /dev/null @@ -1,305 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:mix/mix.dart'; -import 'package:mix_annotations/mix_annotations.dart'; - -part 'style_spec.g.dart'; - -const _mdHeading = MixableFieldDto(type: 'MdHeadingSpecAttribute'); -const _mdParagraph = MixableFieldDto(type: 'MdParagraphSpecAttribute'); -const _mdCode = MixableFieldDto(type: 'MdCodeblockSpecAttribute'); -const _mdList = MixableFieldDto(type: 'MdListSpecAttribute'); -const _mdTable = MixableFieldDto(type: 'MdTableSpecAttribute'); - -const _mdBlockQuote = MixableFieldDto(type: 'MdBlockquoteSpecAttribute'); - -@MixableSpec() -final class MdTextSpec extends Spec with _$MdTextSpec { - final TextStyle? textStyle; - final EdgeInsets? padding; - final WrapAlignment? alignment; - - const MdTextSpec({ - this.textStyle, - this.padding, - this.alignment, - }); -} - -@MixableSpec() -final class MdHeadingSpec extends Spec with _$MdHeadingSpec { - final TextStyle? textStyle; - final EdgeInsets? padding; - final WrapAlignment? align; - - const MdHeadingSpec({ - this.textStyle, - this.padding, - this.align, - }); -} - -@MixableSpec() -final class MdParagraphSpec extends Spec - with _$MdParagraphSpec { - final TextStyle? textStyle; - final EdgeInsets? padding; - - const MdParagraphSpec({ - this.textStyle, - this.padding, - }); -} - -@MixableSpec() -final class MdListSpec extends Spec with _$MdListSpec { - final double? indent; - final TextStyle? bulletStyle; - final EdgeInsets? bulletPadding; - final WrapAlignment? orderedAlignment; - final WrapAlignment? unorderedAlignment; - - const MdListSpec({ - this.indent, - this.bulletStyle, - this.bulletPadding, - this.orderedAlignment, - this.unorderedAlignment, - }); -} - -@MixableSpec() -final class MdTableSpec extends Spec with _$MdTableSpec { - final TextStyle? headStyle; - final TextStyle? bodyStyle; - final TextAlign? headAlignment; - final EdgeInsets? padding; - final TableBorder? border; - final TableColumnWidth? columnWidth; - final EdgeInsets? cellPadding; - - final BoxDecoration? cellDecoration; - final TableCellVerticalAlignment? verticalAlignment; - - const MdTableSpec({ - this.headStyle, - this.bodyStyle, - this.headAlignment, - this.padding, - this.border, - this.columnWidth, - this.cellPadding, - this.cellDecoration, - this.verticalAlignment, - }); -} - -@MixableSpec() -final class MdBlockquoteSpec extends Spec - with _$MdBlockquoteSpec { - final TextStyle? textStyle; - final EdgeInsets? padding; - final BoxDecoration? decoration; - final WrapAlignment? alignment; - - const MdBlockquoteSpec({ - this.textStyle, - this.padding, - this.decoration, - this.alignment, - }); -} - -@MixableSpec() -final class MdCodeblockSpec extends Spec - with _$MdCodeblockSpec { - final TextStyle? textStyle; - final EdgeInsets? padding; - final BoxDecoration? decoration; - final WrapAlignment? alignment; - - const MdCodeblockSpec({ - this.textStyle, - this.padding, - this.decoration, - this.alignment, - }); -} - -@MixableSpec() -final class SlideSpec extends Spec with _$SlideSpec { - final WrapAlignment? textAlign; - @MixableProperty(dto: _mdHeading) - final MdHeadingSpec? h1; - - @MixableProperty(dto: _mdHeading) - final MdHeadingSpec? h2; - - final TextStyle? a; - final TextStyle? em; - final TextStyle? strong; - final TextStyle? del; - final TextStyle? img; - @MixableProperty(utilities: [MixableUtility(alias: 'divider')]) - final BoxDecoration? horizontalRuleDecoration; - final TextScaler? textScaleFactor; - - @MixableProperty(dto: _mdHeading) - final MdHeadingSpec? h3; - - @MixableProperty(dto: _mdHeading) - final MdHeadingSpec? h4; - - @MixableProperty(dto: _mdHeading) - final MdHeadingSpec? h5; - - @MixableProperty(dto: _mdHeading) - final MdHeadingSpec? h6; - - @MixableProperty(dto: _mdParagraph) - final MdParagraphSpec? paragraph; - - final TextStyle? link; - final double? blockSpacing; - - @MixableProperty(dto: _mdBlockQuote) - final MdBlockquoteSpec? blockquote; - @MixableProperty(dto: _mdList) - final MdListSpec? list; - @MixableProperty(dto: _mdTable) - final MdTableSpec? table; - @MixableProperty(dto: _mdCode) - final MdCodeblockSpec? code; - final BoxSpec innerContainer; - final BoxSpec outerContainer; - final BoxSpec contentContainer; - final ImageSpec image; - final TextStyle? checkbox; - - static const of = _$SlideSpec.of; - static const from = _$SlideSpec.from; - - const SlideSpec({ - this.h1, - this.h2, - this.h3, - this.h4, - this.h5, - this.h6, - this.paragraph, - this.link, - this.blockSpacing, - this.blockquote, - this.list, - this.table, - this.checkbox, - this.code, - this.a, - this.em, - this.strong, - this.del, - this.textAlign, - this.img, - this.horizontalRuleDecoration, - this.textScaleFactor, - BoxSpec? innerContainer, - BoxSpec? outerContainer, - BoxSpec? contentContainer, - ImageSpec? image, - super.animated, - }) : outerContainer = outerContainer ?? const BoxSpec(), - innerContainer = innerContainer ?? const BoxSpec(), - contentContainer = contentContainer ?? const BoxSpec(), - image = image ?? const ImageSpec(); - - MarkdownStyleSheet toStyle() { - return MarkdownStyleSheet( - a: link, - h1: h1?.textStyle, - h1Padding: h1?.padding, - h1Align: h1?.align ?? WrapAlignment.start, - h2: h2?.textStyle, - h2Padding: h2?.padding, - h2Align: h2?.align ?? WrapAlignment.start, - h3: h3?.textStyle, - h3Padding: h3?.padding, - h3Align: h3?.align ?? WrapAlignment.start, - h4: h4?.textStyle, - h4Padding: h4?.padding, - h4Align: h4?.align ?? WrapAlignment.start, - h5: h5?.textStyle, - h5Padding: h5?.padding, - h5Align: h5?.align ?? WrapAlignment.start, - h6: h6?.textStyle, - h6Padding: h6?.padding, - h6Align: h6?.align ?? WrapAlignment.start, - p: paragraph?.textStyle, - pPadding: paragraph?.padding, - textAlign: textAlign ?? WrapAlignment.start, - blockSpacing: blockSpacing, - blockquote: blockquote?.textStyle, - blockquotePadding: blockquote?.padding, - blockquoteDecoration: blockquote?.decoration, - blockquoteAlign: blockquote?.alignment ?? WrapAlignment.start, - listBullet: list?.bulletStyle, - listBulletPadding: list?.bulletPadding, - listIndent: list?.indent, - orderedListAlign: list?.orderedAlignment ?? WrapAlignment.start, - unorderedListAlign: list?.unorderedAlignment ?? WrapAlignment.start, - tableHead: table?.headStyle, - tableBody: table?.bodyStyle, - tableHeadAlign: table?.headAlignment, - tablePadding: table?.padding, - tableBorder: table?.border, - tableColumnWidth: table?.columnWidth, - tableCellsPadding: table?.cellPadding, - tableCellsDecoration: table?.cellDecoration, - tableVerticalAlignment: - table?.verticalAlignment ?? TableCellVerticalAlignment.middle, - code: code?.textStyle, - codeblockAlign: code?.alignment ?? WrapAlignment.start, - codeblockPadding: code?.padding, - codeblockDecoration: code?.decoration, - checkbox: checkbox, - ); - } -} - -extension on MixData { - Spec specOf>(Spec fallback) { - return resolvableOf() ?? fallback; - } -} - -extension SlideSpecUtilityX on SlideSpecUtility { - TextStyleUtility get textStyle { - return TextStyleUtility( - (value) => only( - paragraph: MdParagraphSpecAttribute(textStyle: value), - h1: MdHeadingSpecAttribute(textStyle: value), - h2: MdHeadingSpecAttribute(textStyle: value), - h3: MdHeadingSpecAttribute(textStyle: value), - h4: MdHeadingSpecAttribute(textStyle: value), - h5: MdHeadingSpecAttribute(textStyle: value), - h6: MdHeadingSpecAttribute(textStyle: value), - list: MdListSpecAttribute(bulletStyle: value), - table: MdTableSpecAttribute(bodyStyle: value, headStyle: value), - code: MdCodeblockSpecAttribute(textStyle: value), - blockquote: MdBlockquoteSpecAttribute(textStyle: value), - ), - ); - } - - MdHeadingSpecUtility get headings { - return MdHeadingSpecUtility( - (value) => only( - h1: value, - h2: value, - h3: value, - h4: value, - h5: value, - h6: value, - ), - ); - } -} diff --git a/packages/superdeck/lib/styles/style_spec.g.dart b/packages/superdeck/lib/styles/style_spec.g.dart deleted file mode 100644 index a340aeb5..00000000 --- a/packages/superdeck/lib/styles/style_spec.g.dart +++ /dev/null @@ -1,2156 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'style_spec.dart'; - -// ************************************************************************** -// MixableSpecGenerator -// ************************************************************************** - -mixin _$MdTextSpec on Spec { - static MdTextSpec from(MixData mix) { - return mix.attributeOf()?.resolve(mix) ?? - const MdTextSpec(); - } - - /// {@template md_text_spec_of} - /// Retrieves the [MdTextSpec] from the nearest [Mix] ancestor in the widget tree. - /// - /// This method uses [Mix.of] to obtain the [Mix] instance associated with the - /// given [BuildContext], and then retrieves the [MdTextSpec] from that [Mix]. - /// If no ancestor [Mix] is found, this method returns an empty [MdTextSpec]. - /// - /// Example: - /// - /// ```dart - /// final mdTextSpec = MdTextSpec.of(context); - /// ``` - /// {@endtemplate} - static MdTextSpec of(BuildContext context) { - return _$MdTextSpec.from(Mix.of(context)); - } - - /// Creates a copy of this [MdTextSpec] but with the given fields - /// replaced with the new values. - @override - MdTextSpec copyWith({ - TextStyle? textStyle, - EdgeInsets? padding, - WrapAlignment? alignment, - }) { - return MdTextSpec( - textStyle: textStyle ?? _$this.textStyle, - padding: padding ?? _$this.padding, - alignment: alignment ?? _$this.alignment, - ); - } - - /// Linearly interpolates between this [MdTextSpec] and another [MdTextSpec] based on the given parameter [t]. - /// - /// The parameter [t] represents the interpolation factor, typically ranging from 0.0 to 1.0. - /// When [t] is 0.0, the current [MdTextSpec] is returned. When [t] is 1.0, the [other] [MdTextSpec] is returned. - /// For values of [t] between 0.0 and 1.0, an interpolated [MdTextSpec] is returned. - /// - /// If [other] is null, this method returns the current [MdTextSpec] instance. - /// - /// The interpolation is performed on each property of the [MdTextSpec] using the appropriate - /// interpolation method: - /// - /// - [MixHelpers.lerpTextStyle] for [textStyle]. - /// - [EdgeInsets.lerp] for [padding]. - - /// For [alignment], the interpolation is performed using a step function. - /// If [t] is less than 0.5, the value from the current [MdTextSpec] is used. Otherwise, the value - /// from the [other] [MdTextSpec] is used. - /// - /// This method is typically used in animations to smoothly transition between - /// different [MdTextSpec] configurations. - @override - MdTextSpec lerp(MdTextSpec? other, double t) { - if (other == null) return _$this; - - return MdTextSpec( - textStyle: MixHelpers.lerpTextStyle(_$this.textStyle, other.textStyle, t), - padding: EdgeInsets.lerp(_$this.padding, other.padding, t), - alignment: t < 0.5 ? _$this.alignment : other.alignment, - ); - } - - /// The list of properties that constitute the state of this [MdTextSpec]. - /// - /// This property is used by the [==] operator and the [hashCode] getter to - /// compare two [MdTextSpec] instances for equality. - @override - List get props => [ - _$this.textStyle, - _$this.padding, - _$this.alignment, - ]; - - MdTextSpec get _$this => this as MdTextSpec; -} - -/// Represents the attributes of a [MdTextSpec]. -/// -/// This class encapsulates properties defining the layout and -/// appearance of a [MdTextSpec]. -/// -/// Use this class to configure the attributes of a [MdTextSpec] and pass it to -/// the [MdTextSpec] constructor. -final class MdTextSpecAttribute extends SpecAttribute { - final TextStyleDto? textStyle; - final EdgeInsetsDto? padding; - final WrapAlignment? alignment; - - const MdTextSpecAttribute({ - this.textStyle, - this.padding, - this.alignment, - }); - - /// Resolves to [MdTextSpec] using the provided [MixData]. - /// - /// If a property is null in the [MixData], it falls back to the - /// default value defined in the `defaultValue` for that property. - /// - /// ```dart - /// final mdTextSpec = MdTextSpecAttribute(...).resolve(mix); - /// ``` - @override - MdTextSpec resolve(MixData mix) { - return MdTextSpec( - textStyle: textStyle?.resolve(mix), - padding: padding?.resolve(mix), - alignment: alignment, - ); - } - - /// Merges the properties of this [MdTextSpecAttribute] with the properties of [other]. - /// - /// If [other] is null, returns this instance unchanged. Otherwise, returns a new - /// [MdTextSpecAttribute] with the properties of [other] taking precedence over - /// the corresponding properties of this instance. - /// - /// Properties from [other] that are null will fall back - /// to the values from this instance. - @override - MdTextSpecAttribute merge(MdTextSpecAttribute? other) { - if (other == null) return this; - - return MdTextSpecAttribute( - textStyle: textStyle?.merge(other.textStyle) ?? other.textStyle, - padding: padding?.merge(other.padding) ?? other.padding, - alignment: other.alignment ?? alignment, - ); - } - - /// The list of properties that constitute the state of this [MdTextSpecAttribute]. - /// - /// This property is used by the [==] operator and the [hashCode] getter to - /// compare two [MdTextSpecAttribute] instances for equality. - @override - List get props => [ - textStyle, - padding, - alignment, - ]; -} - -/// Utility class for configuring [MdTextSpecAttribute] properties. -/// -/// This class provides methods to set individual properties of a [MdTextSpecAttribute]. -/// Use the methods of this class to configure specific properties of a [MdTextSpecAttribute]. -class MdTextSpecUtility - extends SpecUtility { - /// Utility for defining [MdTextSpecAttribute.textStyle] - late final textStyle = TextStyleUtility((v) => only(textStyle: v)); - - /// Utility for defining [MdTextSpecAttribute.padding] - late final padding = EdgeInsetsUtility((v) => only(padding: v)); - - /// Utility for defining [MdTextSpecAttribute.alignment] - late final alignment = WrapAlignmentUtility((v) => only(alignment: v)); - - MdTextSpecUtility(super.builder); - - static final self = MdTextSpecUtility((v) => v); - - /// Returns a new [MdTextSpecAttribute] with the specified properties. - @override - T only({ - TextStyleDto? textStyle, - EdgeInsetsDto? padding, - WrapAlignment? alignment, - }) { - return builder(MdTextSpecAttribute( - textStyle: textStyle, - padding: padding, - alignment: alignment, - )); - } -} - -/// A tween that interpolates between two [MdTextSpec] instances. -/// -/// This class can be used in animations to smoothly transition between -/// different [MdTextSpec] specifications. -class MdTextSpecTween extends Tween { - MdTextSpecTween({ - super.begin, - super.end, - }); - - @override - MdTextSpec lerp(double t) { - if (begin == null && end == null) { - return const MdTextSpec(); - } - - if (begin == null) { - return end!; - } - - return begin!.lerp(end!, t); - } -} - -mixin _$MdHeadingSpec on Spec { - static MdHeadingSpec from(MixData mix) { - return mix.attributeOf()?.resolve(mix) ?? - const MdHeadingSpec(); - } - - /// {@template md_heading_spec_of} - /// Retrieves the [MdHeadingSpec] from the nearest [Mix] ancestor in the widget tree. - /// - /// This method uses [Mix.of] to obtain the [Mix] instance associated with the - /// given [BuildContext], and then retrieves the [MdHeadingSpec] from that [Mix]. - /// If no ancestor [Mix] is found, this method returns an empty [MdHeadingSpec]. - /// - /// Example: - /// - /// ```dart - /// final mdHeadingSpec = MdHeadingSpec.of(context); - /// ``` - /// {@endtemplate} - static MdHeadingSpec of(BuildContext context) { - return _$MdHeadingSpec.from(Mix.of(context)); - } - - /// Creates a copy of this [MdHeadingSpec] but with the given fields - /// replaced with the new values. - @override - MdHeadingSpec copyWith({ - TextStyle? textStyle, - EdgeInsets? padding, - WrapAlignment? align, - }) { - return MdHeadingSpec( - textStyle: textStyle ?? _$this.textStyle, - padding: padding ?? _$this.padding, - align: align ?? _$this.align, - ); - } - - /// Linearly interpolates between this [MdHeadingSpec] and another [MdHeadingSpec] based on the given parameter [t]. - /// - /// The parameter [t] represents the interpolation factor, typically ranging from 0.0 to 1.0. - /// When [t] is 0.0, the current [MdHeadingSpec] is returned. When [t] is 1.0, the [other] [MdHeadingSpec] is returned. - /// For values of [t] between 0.0 and 1.0, an interpolated [MdHeadingSpec] is returned. - /// - /// If [other] is null, this method returns the current [MdHeadingSpec] instance. - /// - /// The interpolation is performed on each property of the [MdHeadingSpec] using the appropriate - /// interpolation method: - /// - /// - [MixHelpers.lerpTextStyle] for [textStyle]. - /// - [EdgeInsets.lerp] for [padding]. - - /// For [align], the interpolation is performed using a step function. - /// If [t] is less than 0.5, the value from the current [MdHeadingSpec] is used. Otherwise, the value - /// from the [other] [MdHeadingSpec] is used. - /// - /// This method is typically used in animations to smoothly transition between - /// different [MdHeadingSpec] configurations. - @override - MdHeadingSpec lerp(MdHeadingSpec? other, double t) { - if (other == null) return _$this; - - return MdHeadingSpec( - textStyle: MixHelpers.lerpTextStyle(_$this.textStyle, other.textStyle, t), - padding: EdgeInsets.lerp(_$this.padding, other.padding, t), - align: t < 0.5 ? _$this.align : other.align, - ); - } - - /// The list of properties that constitute the state of this [MdHeadingSpec]. - /// - /// This property is used by the [==] operator and the [hashCode] getter to - /// compare two [MdHeadingSpec] instances for equality. - @override - List get props => [ - _$this.textStyle, - _$this.padding, - _$this.align, - ]; - - MdHeadingSpec get _$this => this as MdHeadingSpec; -} - -/// Represents the attributes of a [MdHeadingSpec]. -/// -/// This class encapsulates properties defining the layout and -/// appearance of a [MdHeadingSpec]. -/// -/// Use this class to configure the attributes of a [MdHeadingSpec] and pass it to -/// the [MdHeadingSpec] constructor. -final class MdHeadingSpecAttribute extends SpecAttribute { - final TextStyleDto? textStyle; - final EdgeInsetsDto? padding; - final WrapAlignment? align; - - const MdHeadingSpecAttribute({ - this.textStyle, - this.padding, - this.align, - }); - - /// Resolves to [MdHeadingSpec] using the provided [MixData]. - /// - /// If a property is null in the [MixData], it falls back to the - /// default value defined in the `defaultValue` for that property. - /// - /// ```dart - /// final mdHeadingSpec = MdHeadingSpecAttribute(...).resolve(mix); - /// ``` - @override - MdHeadingSpec resolve(MixData mix) { - return MdHeadingSpec( - textStyle: textStyle?.resolve(mix), - padding: padding?.resolve(mix), - align: align, - ); - } - - /// Merges the properties of this [MdHeadingSpecAttribute] with the properties of [other]. - /// - /// If [other] is null, returns this instance unchanged. Otherwise, returns a new - /// [MdHeadingSpecAttribute] with the properties of [other] taking precedence over - /// the corresponding properties of this instance. - /// - /// Properties from [other] that are null will fall back - /// to the values from this instance. - @override - MdHeadingSpecAttribute merge(MdHeadingSpecAttribute? other) { - if (other == null) return this; - - return MdHeadingSpecAttribute( - textStyle: textStyle?.merge(other.textStyle) ?? other.textStyle, - padding: padding?.merge(other.padding) ?? other.padding, - align: other.align ?? align, - ); - } - - /// The list of properties that constitute the state of this [MdHeadingSpecAttribute]. - /// - /// This property is used by the [==] operator and the [hashCode] getter to - /// compare two [MdHeadingSpecAttribute] instances for equality. - @override - List get props => [ - textStyle, - padding, - align, - ]; -} - -/// Utility class for configuring [MdHeadingSpecAttribute] properties. -/// -/// This class provides methods to set individual properties of a [MdHeadingSpecAttribute]. -/// Use the methods of this class to configure specific properties of a [MdHeadingSpecAttribute]. -class MdHeadingSpecUtility - extends SpecUtility { - /// Utility for defining [MdHeadingSpecAttribute.textStyle] - late final textStyle = TextStyleUtility((v) => only(textStyle: v)); - - /// Utility for defining [MdHeadingSpecAttribute.padding] - late final padding = EdgeInsetsUtility((v) => only(padding: v)); - - /// Utility for defining [MdHeadingSpecAttribute.align] - late final align = WrapAlignmentUtility((v) => only(align: v)); - - MdHeadingSpecUtility(super.builder); - - static final self = MdHeadingSpecUtility((v) => v); - - /// Returns a new [MdHeadingSpecAttribute] with the specified properties. - @override - T only({ - TextStyleDto? textStyle, - EdgeInsetsDto? padding, - WrapAlignment? align, - }) { - return builder(MdHeadingSpecAttribute( - textStyle: textStyle, - padding: padding, - align: align, - )); - } -} - -/// A tween that interpolates between two [MdHeadingSpec] instances. -/// -/// This class can be used in animations to smoothly transition between -/// different [MdHeadingSpec] specifications. -class MdHeadingSpecTween extends Tween { - MdHeadingSpecTween({ - super.begin, - super.end, - }); - - @override - MdHeadingSpec lerp(double t) { - if (begin == null && end == null) { - return const MdHeadingSpec(); - } - - if (begin == null) { - return end!; - } - - return begin!.lerp(end!, t); - } -} - -mixin _$MdParagraphSpec on Spec { - static MdParagraphSpec from(MixData mix) { - return mix.attributeOf()?.resolve(mix) ?? - const MdParagraphSpec(); - } - - /// {@template md_paragraph_spec_of} - /// Retrieves the [MdParagraphSpec] from the nearest [Mix] ancestor in the widget tree. - /// - /// This method uses [Mix.of] to obtain the [Mix] instance associated with the - /// given [BuildContext], and then retrieves the [MdParagraphSpec] from that [Mix]. - /// If no ancestor [Mix] is found, this method returns an empty [MdParagraphSpec]. - /// - /// Example: - /// - /// ```dart - /// final mdParagraphSpec = MdParagraphSpec.of(context); - /// ``` - /// {@endtemplate} - static MdParagraphSpec of(BuildContext context) { - return _$MdParagraphSpec.from(Mix.of(context)); - } - - /// Creates a copy of this [MdParagraphSpec] but with the given fields - /// replaced with the new values. - @override - MdParagraphSpec copyWith({ - TextStyle? textStyle, - EdgeInsets? padding, - }) { - return MdParagraphSpec( - textStyle: textStyle ?? _$this.textStyle, - padding: padding ?? _$this.padding, - ); - } - - /// Linearly interpolates between this [MdParagraphSpec] and another [MdParagraphSpec] based on the given parameter [t]. - /// - /// The parameter [t] represents the interpolation factor, typically ranging from 0.0 to 1.0. - /// When [t] is 0.0, the current [MdParagraphSpec] is returned. When [t] is 1.0, the [other] [MdParagraphSpec] is returned. - /// For values of [t] between 0.0 and 1.0, an interpolated [MdParagraphSpec] is returned. - /// - /// If [other] is null, this method returns the current [MdParagraphSpec] instance. - /// - /// The interpolation is performed on each property of the [MdParagraphSpec] using the appropriate - /// interpolation method: - /// - /// - [MixHelpers.lerpTextStyle] for [textStyle]. - /// - [EdgeInsets.lerp] for [padding]. - - /// For , the interpolation is performed using a step function. - /// If [t] is less than 0.5, the value from the current [MdParagraphSpec] is used. Otherwise, the value - /// from the [other] [MdParagraphSpec] is used. - /// - /// This method is typically used in animations to smoothly transition between - /// different [MdParagraphSpec] configurations. - @override - MdParagraphSpec lerp(MdParagraphSpec? other, double t) { - if (other == null) return _$this; - - return MdParagraphSpec( - textStyle: MixHelpers.lerpTextStyle(_$this.textStyle, other.textStyle, t), - padding: EdgeInsets.lerp(_$this.padding, other.padding, t), - ); - } - - /// The list of properties that constitute the state of this [MdParagraphSpec]. - /// - /// This property is used by the [==] operator and the [hashCode] getter to - /// compare two [MdParagraphSpec] instances for equality. - @override - List get props => [ - _$this.textStyle, - _$this.padding, - ]; - - MdParagraphSpec get _$this => this as MdParagraphSpec; -} - -/// Represents the attributes of a [MdParagraphSpec]. -/// -/// This class encapsulates properties defining the layout and -/// appearance of a [MdParagraphSpec]. -/// -/// Use this class to configure the attributes of a [MdParagraphSpec] and pass it to -/// the [MdParagraphSpec] constructor. -final class MdParagraphSpecAttribute extends SpecAttribute { - final TextStyleDto? textStyle; - final EdgeInsetsDto? padding; - - const MdParagraphSpecAttribute({ - this.textStyle, - this.padding, - }); - - /// Resolves to [MdParagraphSpec] using the provided [MixData]. - /// - /// If a property is null in the [MixData], it falls back to the - /// default value defined in the `defaultValue` for that property. - /// - /// ```dart - /// final mdParagraphSpec = MdParagraphSpecAttribute(...).resolve(mix); - /// ``` - @override - MdParagraphSpec resolve(MixData mix) { - return MdParagraphSpec( - textStyle: textStyle?.resolve(mix), - padding: padding?.resolve(mix), - ); - } - - /// Merges the properties of this [MdParagraphSpecAttribute] with the properties of [other]. - /// - /// If [other] is null, returns this instance unchanged. Otherwise, returns a new - /// [MdParagraphSpecAttribute] with the properties of [other] taking precedence over - /// the corresponding properties of this instance. - /// - /// Properties from [other] that are null will fall back - /// to the values from this instance. - @override - MdParagraphSpecAttribute merge(MdParagraphSpecAttribute? other) { - if (other == null) return this; - - return MdParagraphSpecAttribute( - textStyle: textStyle?.merge(other.textStyle) ?? other.textStyle, - padding: padding?.merge(other.padding) ?? other.padding, - ); - } - - /// The list of properties that constitute the state of this [MdParagraphSpecAttribute]. - /// - /// This property is used by the [==] operator and the [hashCode] getter to - /// compare two [MdParagraphSpecAttribute] instances for equality. - @override - List get props => [ - textStyle, - padding, - ]; -} - -/// Utility class for configuring [MdParagraphSpecAttribute] properties. -/// -/// This class provides methods to set individual properties of a [MdParagraphSpecAttribute]. -/// Use the methods of this class to configure specific properties of a [MdParagraphSpecAttribute]. -class MdParagraphSpecUtility - extends SpecUtility { - /// Utility for defining [MdParagraphSpecAttribute.textStyle] - late final textStyle = TextStyleUtility((v) => only(textStyle: v)); - - /// Utility for defining [MdParagraphSpecAttribute.padding] - late final padding = EdgeInsetsUtility((v) => only(padding: v)); - - MdParagraphSpecUtility(super.builder); - - static final self = MdParagraphSpecUtility((v) => v); - - /// Returns a new [MdParagraphSpecAttribute] with the specified properties. - @override - T only({ - TextStyleDto? textStyle, - EdgeInsetsDto? padding, - }) { - return builder(MdParagraphSpecAttribute( - textStyle: textStyle, - padding: padding, - )); - } -} - -/// A tween that interpolates between two [MdParagraphSpec] instances. -/// -/// This class can be used in animations to smoothly transition between -/// different [MdParagraphSpec] specifications. -class MdParagraphSpecTween extends Tween { - MdParagraphSpecTween({ - super.begin, - super.end, - }); - - @override - MdParagraphSpec lerp(double t) { - if (begin == null && end == null) { - return const MdParagraphSpec(); - } - - if (begin == null) { - return end!; - } - - return begin!.lerp(end!, t); - } -} - -mixin _$MdListSpec on Spec { - static MdListSpec from(MixData mix) { - return mix.attributeOf()?.resolve(mix) ?? - const MdListSpec(); - } - - /// {@template md_list_spec_of} - /// Retrieves the [MdListSpec] from the nearest [Mix] ancestor in the widget tree. - /// - /// This method uses [Mix.of] to obtain the [Mix] instance associated with the - /// given [BuildContext], and then retrieves the [MdListSpec] from that [Mix]. - /// If no ancestor [Mix] is found, this method returns an empty [MdListSpec]. - /// - /// Example: - /// - /// ```dart - /// final mdListSpec = MdListSpec.of(context); - /// ``` - /// {@endtemplate} - static MdListSpec of(BuildContext context) { - return _$MdListSpec.from(Mix.of(context)); - } - - /// Creates a copy of this [MdListSpec] but with the given fields - /// replaced with the new values. - @override - MdListSpec copyWith({ - double? indent, - TextStyle? bulletStyle, - EdgeInsets? bulletPadding, - WrapAlignment? orderedAlignment, - WrapAlignment? unorderedAlignment, - }) { - return MdListSpec( - indent: indent ?? _$this.indent, - bulletStyle: bulletStyle ?? _$this.bulletStyle, - bulletPadding: bulletPadding ?? _$this.bulletPadding, - orderedAlignment: orderedAlignment ?? _$this.orderedAlignment, - unorderedAlignment: unorderedAlignment ?? _$this.unorderedAlignment, - ); - } - - /// Linearly interpolates between this [MdListSpec] and another [MdListSpec] based on the given parameter [t]. - /// - /// The parameter [t] represents the interpolation factor, typically ranging from 0.0 to 1.0. - /// When [t] is 0.0, the current [MdListSpec] is returned. When [t] is 1.0, the [other] [MdListSpec] is returned. - /// For values of [t] between 0.0 and 1.0, an interpolated [MdListSpec] is returned. - /// - /// If [other] is null, this method returns the current [MdListSpec] instance. - /// - /// The interpolation is performed on each property of the [MdListSpec] using the appropriate - /// interpolation method: - /// - /// - [MixHelpers.lerpDouble] for [indent]. - /// - [MixHelpers.lerpTextStyle] for [bulletStyle]. - /// - [EdgeInsets.lerp] for [bulletPadding]. - - /// For [orderedAlignment] and [unorderedAlignment], the interpolation is performed using a step function. - /// If [t] is less than 0.5, the value from the current [MdListSpec] is used. Otherwise, the value - /// from the [other] [MdListSpec] is used. - /// - /// This method is typically used in animations to smoothly transition between - /// different [MdListSpec] configurations. - @override - MdListSpec lerp(MdListSpec? other, double t) { - if (other == null) return _$this; - - return MdListSpec( - indent: MixHelpers.lerpDouble(_$this.indent, other.indent, t), - bulletStyle: - MixHelpers.lerpTextStyle(_$this.bulletStyle, other.bulletStyle, t), - bulletPadding: - EdgeInsets.lerp(_$this.bulletPadding, other.bulletPadding, t), - orderedAlignment: - t < 0.5 ? _$this.orderedAlignment : other.orderedAlignment, - unorderedAlignment: - t < 0.5 ? _$this.unorderedAlignment : other.unorderedAlignment, - ); - } - - /// The list of properties that constitute the state of this [MdListSpec]. - /// - /// This property is used by the [==] operator and the [hashCode] getter to - /// compare two [MdListSpec] instances for equality. - @override - List get props => [ - _$this.indent, - _$this.bulletStyle, - _$this.bulletPadding, - _$this.orderedAlignment, - _$this.unorderedAlignment, - ]; - - MdListSpec get _$this => this as MdListSpec; -} - -/// Represents the attributes of a [MdListSpec]. -/// -/// This class encapsulates properties defining the layout and -/// appearance of a [MdListSpec]. -/// -/// Use this class to configure the attributes of a [MdListSpec] and pass it to -/// the [MdListSpec] constructor. -final class MdListSpecAttribute extends SpecAttribute { - final double? indent; - final TextStyleDto? bulletStyle; - final EdgeInsetsDto? bulletPadding; - final WrapAlignment? orderedAlignment; - final WrapAlignment? unorderedAlignment; - - const MdListSpecAttribute({ - this.indent, - this.bulletStyle, - this.bulletPadding, - this.orderedAlignment, - this.unorderedAlignment, - }); - - /// Resolves to [MdListSpec] using the provided [MixData]. - /// - /// If a property is null in the [MixData], it falls back to the - /// default value defined in the `defaultValue` for that property. - /// - /// ```dart - /// final mdListSpec = MdListSpecAttribute(...).resolve(mix); - /// ``` - @override - MdListSpec resolve(MixData mix) { - return MdListSpec( - indent: indent, - bulletStyle: bulletStyle?.resolve(mix), - bulletPadding: bulletPadding?.resolve(mix), - orderedAlignment: orderedAlignment, - unorderedAlignment: unorderedAlignment, - ); - } - - /// Merges the properties of this [MdListSpecAttribute] with the properties of [other]. - /// - /// If [other] is null, returns this instance unchanged. Otherwise, returns a new - /// [MdListSpecAttribute] with the properties of [other] taking precedence over - /// the corresponding properties of this instance. - /// - /// Properties from [other] that are null will fall back - /// to the values from this instance. - @override - MdListSpecAttribute merge(MdListSpecAttribute? other) { - if (other == null) return this; - - return MdListSpecAttribute( - indent: other.indent ?? indent, - bulletStyle: bulletStyle?.merge(other.bulletStyle) ?? other.bulletStyle, - bulletPadding: - bulletPadding?.merge(other.bulletPadding) ?? other.bulletPadding, - orderedAlignment: other.orderedAlignment ?? orderedAlignment, - unorderedAlignment: other.unorderedAlignment ?? unorderedAlignment, - ); - } - - /// The list of properties that constitute the state of this [MdListSpecAttribute]. - /// - /// This property is used by the [==] operator and the [hashCode] getter to - /// compare two [MdListSpecAttribute] instances for equality. - @override - List get props => [ - indent, - bulletStyle, - bulletPadding, - orderedAlignment, - unorderedAlignment, - ]; -} - -/// Utility class for configuring [MdListSpecAttribute] properties. -/// -/// This class provides methods to set individual properties of a [MdListSpecAttribute]. -/// Use the methods of this class to configure specific properties of a [MdListSpecAttribute]. -class MdListSpecUtility - extends SpecUtility { - /// Utility for defining [MdListSpecAttribute.indent] - late final indent = DoubleUtility((v) => only(indent: v)); - - /// Utility for defining [MdListSpecAttribute.bulletStyle] - late final bulletStyle = TextStyleUtility((v) => only(bulletStyle: v)); - - /// Utility for defining [MdListSpecAttribute.bulletPadding] - late final bulletPadding = EdgeInsetsUtility((v) => only(bulletPadding: v)); - - /// Utility for defining [MdListSpecAttribute.orderedAlignment] - late final orderedAlignment = - WrapAlignmentUtility((v) => only(orderedAlignment: v)); - - /// Utility for defining [MdListSpecAttribute.unorderedAlignment] - late final unorderedAlignment = - WrapAlignmentUtility((v) => only(unorderedAlignment: v)); - - MdListSpecUtility(super.builder); - - static final self = MdListSpecUtility((v) => v); - - /// Returns a new [MdListSpecAttribute] with the specified properties. - @override - T only({ - double? indent, - TextStyleDto? bulletStyle, - EdgeInsetsDto? bulletPadding, - WrapAlignment? orderedAlignment, - WrapAlignment? unorderedAlignment, - }) { - return builder(MdListSpecAttribute( - indent: indent, - bulletStyle: bulletStyle, - bulletPadding: bulletPadding, - orderedAlignment: orderedAlignment, - unorderedAlignment: unorderedAlignment, - )); - } -} - -/// A tween that interpolates between two [MdListSpec] instances. -/// -/// This class can be used in animations to smoothly transition between -/// different [MdListSpec] specifications. -class MdListSpecTween extends Tween { - MdListSpecTween({ - super.begin, - super.end, - }); - - @override - MdListSpec lerp(double t) { - if (begin == null && end == null) { - return const MdListSpec(); - } - - if (begin == null) { - return end!; - } - - return begin!.lerp(end!, t); - } -} - -mixin _$MdTableSpec on Spec { - static MdTableSpec from(MixData mix) { - return mix.attributeOf()?.resolve(mix) ?? - const MdTableSpec(); - } - - /// {@template md_table_spec_of} - /// Retrieves the [MdTableSpec] from the nearest [Mix] ancestor in the widget tree. - /// - /// This method uses [Mix.of] to obtain the [Mix] instance associated with the - /// given [BuildContext], and then retrieves the [MdTableSpec] from that [Mix]. - /// If no ancestor [Mix] is found, this method returns an empty [MdTableSpec]. - /// - /// Example: - /// - /// ```dart - /// final mdTableSpec = MdTableSpec.of(context); - /// ``` - /// {@endtemplate} - static MdTableSpec of(BuildContext context) { - return _$MdTableSpec.from(Mix.of(context)); - } - - /// Creates a copy of this [MdTableSpec] but with the given fields - /// replaced with the new values. - @override - MdTableSpec copyWith({ - TextStyle? headStyle, - TextStyle? bodyStyle, - TextAlign? headAlignment, - EdgeInsets? padding, - TableBorder? border, - TableColumnWidth? columnWidth, - EdgeInsets? cellPadding, - BoxDecoration? cellDecoration, - TableCellVerticalAlignment? verticalAlignment, - }) { - return MdTableSpec( - headStyle: headStyle ?? _$this.headStyle, - bodyStyle: bodyStyle ?? _$this.bodyStyle, - headAlignment: headAlignment ?? _$this.headAlignment, - padding: padding ?? _$this.padding, - border: border ?? _$this.border, - columnWidth: columnWidth ?? _$this.columnWidth, - cellPadding: cellPadding ?? _$this.cellPadding, - cellDecoration: cellDecoration ?? _$this.cellDecoration, - verticalAlignment: verticalAlignment ?? _$this.verticalAlignment, - ); - } - - /// Linearly interpolates between this [MdTableSpec] and another [MdTableSpec] based on the given parameter [t]. - /// - /// The parameter [t] represents the interpolation factor, typically ranging from 0.0 to 1.0. - /// When [t] is 0.0, the current [MdTableSpec] is returned. When [t] is 1.0, the [other] [MdTableSpec] is returned. - /// For values of [t] between 0.0 and 1.0, an interpolated [MdTableSpec] is returned. - /// - /// If [other] is null, this method returns the current [MdTableSpec] instance. - /// - /// The interpolation is performed on each property of the [MdTableSpec] using the appropriate - /// interpolation method: - /// - /// - [MixHelpers.lerpTextStyle] for [headStyle] and [bodyStyle]. - /// - [EdgeInsets.lerp] for [padding] and [cellPadding]. - /// - [TableBorder.lerp] for [border]. - /// - [BoxDecoration.lerp] for [cellDecoration]. - - /// For [headAlignment] and [columnWidth] and [verticalAlignment], the interpolation is performed using a step function. - /// If [t] is less than 0.5, the value from the current [MdTableSpec] is used. Otherwise, the value - /// from the [other] [MdTableSpec] is used. - /// - /// This method is typically used in animations to smoothly transition between - /// different [MdTableSpec] configurations. - @override - MdTableSpec lerp(MdTableSpec? other, double t) { - if (other == null) return _$this; - - return MdTableSpec( - headStyle: MixHelpers.lerpTextStyle(_$this.headStyle, other.headStyle, t), - bodyStyle: MixHelpers.lerpTextStyle(_$this.bodyStyle, other.bodyStyle, t), - headAlignment: t < 0.5 ? _$this.headAlignment : other.headAlignment, - padding: EdgeInsets.lerp(_$this.padding, other.padding, t), - border: TableBorder.lerp(_$this.border, other.border, t), - columnWidth: t < 0.5 ? _$this.columnWidth : other.columnWidth, - cellPadding: EdgeInsets.lerp(_$this.cellPadding, other.cellPadding, t), - cellDecoration: - BoxDecoration.lerp(_$this.cellDecoration, other.cellDecoration, t), - verticalAlignment: - t < 0.5 ? _$this.verticalAlignment : other.verticalAlignment, - ); - } - - /// The list of properties that constitute the state of this [MdTableSpec]. - /// - /// This property is used by the [==] operator and the [hashCode] getter to - /// compare two [MdTableSpec] instances for equality. - @override - List get props => [ - _$this.headStyle, - _$this.bodyStyle, - _$this.headAlignment, - _$this.padding, - _$this.border, - _$this.columnWidth, - _$this.cellPadding, - _$this.cellDecoration, - _$this.verticalAlignment, - ]; - - MdTableSpec get _$this => this as MdTableSpec; -} - -/// Represents the attributes of a [MdTableSpec]. -/// -/// This class encapsulates properties defining the layout and -/// appearance of a [MdTableSpec]. -/// -/// Use this class to configure the attributes of a [MdTableSpec] and pass it to -/// the [MdTableSpec] constructor. -final class MdTableSpecAttribute extends SpecAttribute { - final TextStyleDto? headStyle; - final TextStyleDto? bodyStyle; - final TextAlign? headAlignment; - final EdgeInsetsDto? padding; - final TableBorder? border; - final TableColumnWidth? columnWidth; - final EdgeInsetsDto? cellPadding; - final BoxDecorationDto? cellDecoration; - final TableCellVerticalAlignment? verticalAlignment; - - const MdTableSpecAttribute({ - this.headStyle, - this.bodyStyle, - this.headAlignment, - this.padding, - this.border, - this.columnWidth, - this.cellPadding, - this.cellDecoration, - this.verticalAlignment, - }); - - /// Resolves to [MdTableSpec] using the provided [MixData]. - /// - /// If a property is null in the [MixData], it falls back to the - /// default value defined in the `defaultValue` for that property. - /// - /// ```dart - /// final mdTableSpec = MdTableSpecAttribute(...).resolve(mix); - /// ``` - @override - MdTableSpec resolve(MixData mix) { - return MdTableSpec( - headStyle: headStyle?.resolve(mix), - bodyStyle: bodyStyle?.resolve(mix), - headAlignment: headAlignment, - padding: padding?.resolve(mix), - border: border, - columnWidth: columnWidth, - cellPadding: cellPadding?.resolve(mix), - cellDecoration: cellDecoration?.resolve(mix), - verticalAlignment: verticalAlignment, - ); - } - - /// Merges the properties of this [MdTableSpecAttribute] with the properties of [other]. - /// - /// If [other] is null, returns this instance unchanged. Otherwise, returns a new - /// [MdTableSpecAttribute] with the properties of [other] taking precedence over - /// the corresponding properties of this instance. - /// - /// Properties from [other] that are null will fall back - /// to the values from this instance. - @override - MdTableSpecAttribute merge(MdTableSpecAttribute? other) { - if (other == null) return this; - - return MdTableSpecAttribute( - headStyle: headStyle?.merge(other.headStyle) ?? other.headStyle, - bodyStyle: bodyStyle?.merge(other.bodyStyle) ?? other.bodyStyle, - headAlignment: other.headAlignment ?? headAlignment, - padding: padding?.merge(other.padding) ?? other.padding, - border: other.border ?? border, - columnWidth: other.columnWidth ?? columnWidth, - cellPadding: cellPadding?.merge(other.cellPadding) ?? other.cellPadding, - cellDecoration: - cellDecoration?.merge(other.cellDecoration) ?? other.cellDecoration, - verticalAlignment: other.verticalAlignment ?? verticalAlignment, - ); - } - - /// The list of properties that constitute the state of this [MdTableSpecAttribute]. - /// - /// This property is used by the [==] operator and the [hashCode] getter to - /// compare two [MdTableSpecAttribute] instances for equality. - @override - List get props => [ - headStyle, - bodyStyle, - headAlignment, - padding, - border, - columnWidth, - cellPadding, - cellDecoration, - verticalAlignment, - ]; -} - -/// Utility class for configuring [MdTableSpecAttribute] properties. -/// -/// This class provides methods to set individual properties of a [MdTableSpecAttribute]. -/// Use the methods of this class to configure specific properties of a [MdTableSpecAttribute]. -class MdTableSpecUtility - extends SpecUtility { - /// Utility for defining [MdTableSpecAttribute.headStyle] - late final headStyle = TextStyleUtility((v) => only(headStyle: v)); - - /// Utility for defining [MdTableSpecAttribute.bodyStyle] - late final bodyStyle = TextStyleUtility((v) => only(bodyStyle: v)); - - /// Utility for defining [MdTableSpecAttribute.headAlignment] - late final headAlignment = TextAlignUtility((v) => only(headAlignment: v)); - - /// Utility for defining [MdTableSpecAttribute.padding] - late final padding = EdgeInsetsUtility((v) => only(padding: v)); - - /// Utility for defining [MdTableSpecAttribute.border] - late final border = TableBorderUtility((v) => only(border: v)); - - /// Utility for defining [MdTableSpecAttribute.columnWidth] - late final columnWidth = TableColumnWidthUtility((v) => only(columnWidth: v)); - - /// Utility for defining [MdTableSpecAttribute.cellPadding] - late final cellPadding = EdgeInsetsUtility((v) => only(cellPadding: v)); - - /// Utility for defining [MdTableSpecAttribute.cellDecoration] - late final cellDecoration = - BoxDecorationUtility((v) => only(cellDecoration: v)); - - /// Utility for defining [MdTableSpecAttribute.verticalAlignment] - late final verticalAlignment = - TableCellVerticalAlignmentUtility((v) => only(verticalAlignment: v)); - - MdTableSpecUtility(super.builder); - - static final self = MdTableSpecUtility((v) => v); - - /// Returns a new [MdTableSpecAttribute] with the specified properties. - @override - T only({ - TextStyleDto? headStyle, - TextStyleDto? bodyStyle, - TextAlign? headAlignment, - EdgeInsetsDto? padding, - TableBorder? border, - TableColumnWidth? columnWidth, - EdgeInsetsDto? cellPadding, - BoxDecorationDto? cellDecoration, - TableCellVerticalAlignment? verticalAlignment, - }) { - return builder(MdTableSpecAttribute( - headStyle: headStyle, - bodyStyle: bodyStyle, - headAlignment: headAlignment, - padding: padding, - border: border, - columnWidth: columnWidth, - cellPadding: cellPadding, - cellDecoration: cellDecoration, - verticalAlignment: verticalAlignment, - )); - } -} - -/// A tween that interpolates between two [MdTableSpec] instances. -/// -/// This class can be used in animations to smoothly transition between -/// different [MdTableSpec] specifications. -class MdTableSpecTween extends Tween { - MdTableSpecTween({ - super.begin, - super.end, - }); - - @override - MdTableSpec lerp(double t) { - if (begin == null && end == null) { - return const MdTableSpec(); - } - - if (begin == null) { - return end!; - } - - return begin!.lerp(end!, t); - } -} - -mixin _$MdBlockquoteSpec on Spec { - static MdBlockquoteSpec from(MixData mix) { - return mix.attributeOf()?.resolve(mix) ?? - const MdBlockquoteSpec(); - } - - /// {@template md_blockquote_spec_of} - /// Retrieves the [MdBlockquoteSpec] from the nearest [Mix] ancestor in the widget tree. - /// - /// This method uses [Mix.of] to obtain the [Mix] instance associated with the - /// given [BuildContext], and then retrieves the [MdBlockquoteSpec] from that [Mix]. - /// If no ancestor [Mix] is found, this method returns an empty [MdBlockquoteSpec]. - /// - /// Example: - /// - /// ```dart - /// final mdBlockquoteSpec = MdBlockquoteSpec.of(context); - /// ``` - /// {@endtemplate} - static MdBlockquoteSpec of(BuildContext context) { - return _$MdBlockquoteSpec.from(Mix.of(context)); - } - - /// Creates a copy of this [MdBlockquoteSpec] but with the given fields - /// replaced with the new values. - @override - MdBlockquoteSpec copyWith({ - TextStyle? textStyle, - EdgeInsets? padding, - BoxDecoration? decoration, - WrapAlignment? alignment, - }) { - return MdBlockquoteSpec( - textStyle: textStyle ?? _$this.textStyle, - padding: padding ?? _$this.padding, - decoration: decoration ?? _$this.decoration, - alignment: alignment ?? _$this.alignment, - ); - } - - /// Linearly interpolates between this [MdBlockquoteSpec] and another [MdBlockquoteSpec] based on the given parameter [t]. - /// - /// The parameter [t] represents the interpolation factor, typically ranging from 0.0 to 1.0. - /// When [t] is 0.0, the current [MdBlockquoteSpec] is returned. When [t] is 1.0, the [other] [MdBlockquoteSpec] is returned. - /// For values of [t] between 0.0 and 1.0, an interpolated [MdBlockquoteSpec] is returned. - /// - /// If [other] is null, this method returns the current [MdBlockquoteSpec] instance. - /// - /// The interpolation is performed on each property of the [MdBlockquoteSpec] using the appropriate - /// interpolation method: - /// - /// - [MixHelpers.lerpTextStyle] for [textStyle]. - /// - [EdgeInsets.lerp] for [padding]. - /// - [BoxDecoration.lerp] for [decoration]. - - /// For [alignment], the interpolation is performed using a step function. - /// If [t] is less than 0.5, the value from the current [MdBlockquoteSpec] is used. Otherwise, the value - /// from the [other] [MdBlockquoteSpec] is used. - /// - /// This method is typically used in animations to smoothly transition between - /// different [MdBlockquoteSpec] configurations. - @override - MdBlockquoteSpec lerp(MdBlockquoteSpec? other, double t) { - if (other == null) return _$this; - - return MdBlockquoteSpec( - textStyle: MixHelpers.lerpTextStyle(_$this.textStyle, other.textStyle, t), - padding: EdgeInsets.lerp(_$this.padding, other.padding, t), - decoration: BoxDecoration.lerp(_$this.decoration, other.decoration, t), - alignment: t < 0.5 ? _$this.alignment : other.alignment, - ); - } - - /// The list of properties that constitute the state of this [MdBlockquoteSpec]. - /// - /// This property is used by the [==] operator and the [hashCode] getter to - /// compare two [MdBlockquoteSpec] instances for equality. - @override - List get props => [ - _$this.textStyle, - _$this.padding, - _$this.decoration, - _$this.alignment, - ]; - - MdBlockquoteSpec get _$this => this as MdBlockquoteSpec; -} - -/// Represents the attributes of a [MdBlockquoteSpec]. -/// -/// This class encapsulates properties defining the layout and -/// appearance of a [MdBlockquoteSpec]. -/// -/// Use this class to configure the attributes of a [MdBlockquoteSpec] and pass it to -/// the [MdBlockquoteSpec] constructor. -final class MdBlockquoteSpecAttribute extends SpecAttribute { - final TextStyleDto? textStyle; - final EdgeInsetsDto? padding; - final BoxDecorationDto? decoration; - final WrapAlignment? alignment; - - const MdBlockquoteSpecAttribute({ - this.textStyle, - this.padding, - this.decoration, - this.alignment, - }); - - /// Resolves to [MdBlockquoteSpec] using the provided [MixData]. - /// - /// If a property is null in the [MixData], it falls back to the - /// default value defined in the `defaultValue` for that property. - /// - /// ```dart - /// final mdBlockquoteSpec = MdBlockquoteSpecAttribute(...).resolve(mix); - /// ``` - @override - MdBlockquoteSpec resolve(MixData mix) { - return MdBlockquoteSpec( - textStyle: textStyle?.resolve(mix), - padding: padding?.resolve(mix), - decoration: decoration?.resolve(mix), - alignment: alignment, - ); - } - - /// Merges the properties of this [MdBlockquoteSpecAttribute] with the properties of [other]. - /// - /// If [other] is null, returns this instance unchanged. Otherwise, returns a new - /// [MdBlockquoteSpecAttribute] with the properties of [other] taking precedence over - /// the corresponding properties of this instance. - /// - /// Properties from [other] that are null will fall back - /// to the values from this instance. - @override - MdBlockquoteSpecAttribute merge(MdBlockquoteSpecAttribute? other) { - if (other == null) return this; - - return MdBlockquoteSpecAttribute( - textStyle: textStyle?.merge(other.textStyle) ?? other.textStyle, - padding: padding?.merge(other.padding) ?? other.padding, - decoration: decoration?.merge(other.decoration) ?? other.decoration, - alignment: other.alignment ?? alignment, - ); - } - - /// The list of properties that constitute the state of this [MdBlockquoteSpecAttribute]. - /// - /// This property is used by the [==] operator and the [hashCode] getter to - /// compare two [MdBlockquoteSpecAttribute] instances for equality. - @override - List get props => [ - textStyle, - padding, - decoration, - alignment, - ]; -} - -/// Utility class for configuring [MdBlockquoteSpecAttribute] properties. -/// -/// This class provides methods to set individual properties of a [MdBlockquoteSpecAttribute]. -/// Use the methods of this class to configure specific properties of a [MdBlockquoteSpecAttribute]. -class MdBlockquoteSpecUtility - extends SpecUtility { - /// Utility for defining [MdBlockquoteSpecAttribute.textStyle] - late final textStyle = TextStyleUtility((v) => only(textStyle: v)); - - /// Utility for defining [MdBlockquoteSpecAttribute.padding] - late final padding = EdgeInsetsUtility((v) => only(padding: v)); - - /// Utility for defining [MdBlockquoteSpecAttribute.decoration] - late final decoration = BoxDecorationUtility((v) => only(decoration: v)); - - /// Utility for defining [MdBlockquoteSpecAttribute.alignment] - late final alignment = WrapAlignmentUtility((v) => only(alignment: v)); - - MdBlockquoteSpecUtility(super.builder); - - static final self = MdBlockquoteSpecUtility((v) => v); - - /// Returns a new [MdBlockquoteSpecAttribute] with the specified properties. - @override - T only({ - TextStyleDto? textStyle, - EdgeInsetsDto? padding, - BoxDecorationDto? decoration, - WrapAlignment? alignment, - }) { - return builder(MdBlockquoteSpecAttribute( - textStyle: textStyle, - padding: padding, - decoration: decoration, - alignment: alignment, - )); - } -} - -/// A tween that interpolates between two [MdBlockquoteSpec] instances. -/// -/// This class can be used in animations to smoothly transition between -/// different [MdBlockquoteSpec] specifications. -class MdBlockquoteSpecTween extends Tween { - MdBlockquoteSpecTween({ - super.begin, - super.end, - }); - - @override - MdBlockquoteSpec lerp(double t) { - if (begin == null && end == null) { - return const MdBlockquoteSpec(); - } - - if (begin == null) { - return end!; - } - - return begin!.lerp(end!, t); - } -} - -mixin _$MdCodeblockSpec on Spec { - static MdCodeblockSpec from(MixData mix) { - return mix.attributeOf()?.resolve(mix) ?? - const MdCodeblockSpec(); - } - - /// {@template md_codeblock_spec_of} - /// Retrieves the [MdCodeblockSpec] from the nearest [Mix] ancestor in the widget tree. - /// - /// This method uses [Mix.of] to obtain the [Mix] instance associated with the - /// given [BuildContext], and then retrieves the [MdCodeblockSpec] from that [Mix]. - /// If no ancestor [Mix] is found, this method returns an empty [MdCodeblockSpec]. - /// - /// Example: - /// - /// ```dart - /// final mdCodeblockSpec = MdCodeblockSpec.of(context); - /// ``` - /// {@endtemplate} - static MdCodeblockSpec of(BuildContext context) { - return _$MdCodeblockSpec.from(Mix.of(context)); - } - - /// Creates a copy of this [MdCodeblockSpec] but with the given fields - /// replaced with the new values. - @override - MdCodeblockSpec copyWith({ - TextStyle? textStyle, - EdgeInsets? padding, - BoxDecoration? decoration, - WrapAlignment? alignment, - }) { - return MdCodeblockSpec( - textStyle: textStyle ?? _$this.textStyle, - padding: padding ?? _$this.padding, - decoration: decoration ?? _$this.decoration, - alignment: alignment ?? _$this.alignment, - ); - } - - /// Linearly interpolates between this [MdCodeblockSpec] and another [MdCodeblockSpec] based on the given parameter [t]. - /// - /// The parameter [t] represents the interpolation factor, typically ranging from 0.0 to 1.0. - /// When [t] is 0.0, the current [MdCodeblockSpec] is returned. When [t] is 1.0, the [other] [MdCodeblockSpec] is returned. - /// For values of [t] between 0.0 and 1.0, an interpolated [MdCodeblockSpec] is returned. - /// - /// If [other] is null, this method returns the current [MdCodeblockSpec] instance. - /// - /// The interpolation is performed on each property of the [MdCodeblockSpec] using the appropriate - /// interpolation method: - /// - /// - [MixHelpers.lerpTextStyle] for [textStyle]. - /// - [EdgeInsets.lerp] for [padding]. - /// - [BoxDecoration.lerp] for [decoration]. - - /// For [alignment], the interpolation is performed using a step function. - /// If [t] is less than 0.5, the value from the current [MdCodeblockSpec] is used. Otherwise, the value - /// from the [other] [MdCodeblockSpec] is used. - /// - /// This method is typically used in animations to smoothly transition between - /// different [MdCodeblockSpec] configurations. - @override - MdCodeblockSpec lerp(MdCodeblockSpec? other, double t) { - if (other == null) return _$this; - - return MdCodeblockSpec( - textStyle: MixHelpers.lerpTextStyle(_$this.textStyle, other.textStyle, t), - padding: EdgeInsets.lerp(_$this.padding, other.padding, t), - decoration: BoxDecoration.lerp(_$this.decoration, other.decoration, t), - alignment: t < 0.5 ? _$this.alignment : other.alignment, - ); - } - - /// The list of properties that constitute the state of this [MdCodeblockSpec]. - /// - /// This property is used by the [==] operator and the [hashCode] getter to - /// compare two [MdCodeblockSpec] instances for equality. - @override - List get props => [ - _$this.textStyle, - _$this.padding, - _$this.decoration, - _$this.alignment, - ]; - - MdCodeblockSpec get _$this => this as MdCodeblockSpec; -} - -/// Represents the attributes of a [MdCodeblockSpec]. -/// -/// This class encapsulates properties defining the layout and -/// appearance of a [MdCodeblockSpec]. -/// -/// Use this class to configure the attributes of a [MdCodeblockSpec] and pass it to -/// the [MdCodeblockSpec] constructor. -final class MdCodeblockSpecAttribute extends SpecAttribute { - final TextStyleDto? textStyle; - final EdgeInsetsDto? padding; - final BoxDecorationDto? decoration; - final WrapAlignment? alignment; - - const MdCodeblockSpecAttribute({ - this.textStyle, - this.padding, - this.decoration, - this.alignment, - }); - - /// Resolves to [MdCodeblockSpec] using the provided [MixData]. - /// - /// If a property is null in the [MixData], it falls back to the - /// default value defined in the `defaultValue` for that property. - /// - /// ```dart - /// final mdCodeblockSpec = MdCodeblockSpecAttribute(...).resolve(mix); - /// ``` - @override - MdCodeblockSpec resolve(MixData mix) { - return MdCodeblockSpec( - textStyle: textStyle?.resolve(mix), - padding: padding?.resolve(mix), - decoration: decoration?.resolve(mix), - alignment: alignment, - ); - } - - /// Merges the properties of this [MdCodeblockSpecAttribute] with the properties of [other]. - /// - /// If [other] is null, returns this instance unchanged. Otherwise, returns a new - /// [MdCodeblockSpecAttribute] with the properties of [other] taking precedence over - /// the corresponding properties of this instance. - /// - /// Properties from [other] that are null will fall back - /// to the values from this instance. - @override - MdCodeblockSpecAttribute merge(MdCodeblockSpecAttribute? other) { - if (other == null) return this; - - return MdCodeblockSpecAttribute( - textStyle: textStyle?.merge(other.textStyle) ?? other.textStyle, - padding: padding?.merge(other.padding) ?? other.padding, - decoration: decoration?.merge(other.decoration) ?? other.decoration, - alignment: other.alignment ?? alignment, - ); - } - - /// The list of properties that constitute the state of this [MdCodeblockSpecAttribute]. - /// - /// This property is used by the [==] operator and the [hashCode] getter to - /// compare two [MdCodeblockSpecAttribute] instances for equality. - @override - List get props => [ - textStyle, - padding, - decoration, - alignment, - ]; -} - -/// Utility class for configuring [MdCodeblockSpecAttribute] properties. -/// -/// This class provides methods to set individual properties of a [MdCodeblockSpecAttribute]. -/// Use the methods of this class to configure specific properties of a [MdCodeblockSpecAttribute]. -class MdCodeblockSpecUtility - extends SpecUtility { - /// Utility for defining [MdCodeblockSpecAttribute.textStyle] - late final textStyle = TextStyleUtility((v) => only(textStyle: v)); - - /// Utility for defining [MdCodeblockSpecAttribute.padding] - late final padding = EdgeInsetsUtility((v) => only(padding: v)); - - /// Utility for defining [MdCodeblockSpecAttribute.decoration] - late final decoration = BoxDecorationUtility((v) => only(decoration: v)); - - /// Utility for defining [MdCodeblockSpecAttribute.alignment] - late final alignment = WrapAlignmentUtility((v) => only(alignment: v)); - - MdCodeblockSpecUtility(super.builder); - - static final self = MdCodeblockSpecUtility((v) => v); - - /// Returns a new [MdCodeblockSpecAttribute] with the specified properties. - @override - T only({ - TextStyleDto? textStyle, - EdgeInsetsDto? padding, - BoxDecorationDto? decoration, - WrapAlignment? alignment, - }) { - return builder(MdCodeblockSpecAttribute( - textStyle: textStyle, - padding: padding, - decoration: decoration, - alignment: alignment, - )); - } -} - -/// A tween that interpolates between two [MdCodeblockSpec] instances. -/// -/// This class can be used in animations to smoothly transition between -/// different [MdCodeblockSpec] specifications. -class MdCodeblockSpecTween extends Tween { - MdCodeblockSpecTween({ - super.begin, - super.end, - }); - - @override - MdCodeblockSpec lerp(double t) { - if (begin == null && end == null) { - return const MdCodeblockSpec(); - } - - if (begin == null) { - return end!; - } - - return begin!.lerp(end!, t); - } -} - -mixin _$SlideSpec on Spec { - static SlideSpec from(MixData mix) { - return mix.attributeOf()?.resolve(mix) ?? - const SlideSpec(); - } - - /// {@template slide_spec_of} - /// Retrieves the [SlideSpec] from the nearest [Mix] ancestor in the widget tree. - /// - /// This method uses [Mix.of] to obtain the [Mix] instance associated with the - /// given [BuildContext], and then retrieves the [SlideSpec] from that [Mix]. - /// If no ancestor [Mix] is found, this method returns an empty [SlideSpec]. - /// - /// Example: - /// - /// ```dart - /// final slideSpec = SlideSpec.of(context); - /// ``` - /// {@endtemplate} - static SlideSpec of(BuildContext context) { - return _$SlideSpec.from(Mix.of(context)); - } - - /// Creates a copy of this [SlideSpec] but with the given fields - /// replaced with the new values. - @override - SlideSpec copyWith({ - MdHeadingSpec? h1, - MdHeadingSpec? h2, - MdHeadingSpec? h3, - MdHeadingSpec? h4, - MdHeadingSpec? h5, - MdHeadingSpec? h6, - MdParagraphSpec? paragraph, - TextStyle? link, - double? blockSpacing, - MdBlockquoteSpec? blockquote, - MdListSpec? list, - MdTableSpec? table, - TextStyle? checkbox, - MdCodeblockSpec? code, - TextStyle? a, - TextStyle? em, - TextStyle? strong, - TextStyle? del, - WrapAlignment? textAlign, - TextStyle? img, - BoxDecoration? horizontalRuleDecoration, - TextScaler? textScaleFactor, - BoxSpec? innerContainer, - BoxSpec? outerContainer, - BoxSpec? contentContainer, - ImageSpec? image, - AnimatedData? animated, - }) { - return SlideSpec( - h1: h1 ?? _$this.h1, - h2: h2 ?? _$this.h2, - h3: h3 ?? _$this.h3, - h4: h4 ?? _$this.h4, - h5: h5 ?? _$this.h5, - h6: h6 ?? _$this.h6, - paragraph: paragraph ?? _$this.paragraph, - link: link ?? _$this.link, - blockSpacing: blockSpacing ?? _$this.blockSpacing, - blockquote: blockquote ?? _$this.blockquote, - list: list ?? _$this.list, - table: table ?? _$this.table, - checkbox: checkbox ?? _$this.checkbox, - code: code ?? _$this.code, - a: a ?? _$this.a, - em: em ?? _$this.em, - strong: strong ?? _$this.strong, - del: del ?? _$this.del, - textAlign: textAlign ?? _$this.textAlign, - img: img ?? _$this.img, - horizontalRuleDecoration: - horizontalRuleDecoration ?? _$this.horizontalRuleDecoration, - textScaleFactor: textScaleFactor ?? _$this.textScaleFactor, - innerContainer: innerContainer ?? _$this.innerContainer, - outerContainer: outerContainer ?? _$this.outerContainer, - contentContainer: contentContainer ?? _$this.contentContainer, - image: image ?? _$this.image, - animated: animated ?? _$this.animated, - ); - } - - /// Linearly interpolates between this [SlideSpec] and another [SlideSpec] based on the given parameter [t]. - /// - /// The parameter [t] represents the interpolation factor, typically ranging from 0.0 to 1.0. - /// When [t] is 0.0, the current [SlideSpec] is returned. When [t] is 1.0, the [other] [SlideSpec] is returned. - /// For values of [t] between 0.0 and 1.0, an interpolated [SlideSpec] is returned. - /// - /// If [other] is null, this method returns the current [SlideSpec] instance. - /// - /// The interpolation is performed on each property of the [SlideSpec] using the appropriate - /// interpolation method: - /// - /// - [MixHelpers.lerpTextStyle] for [link] and [checkbox] and [a] and [em] and [strong] and [del] and [img]. - /// - [MixHelpers.lerpDouble] for [blockSpacing]. - /// - [BoxDecoration.lerp] for [horizontalRuleDecoration]. - /// - [BoxSpec.lerp] for [innerContainer] and [outerContainer] and [contentContainer]. - /// - [ImageSpec.lerp] for [image]. - - /// For [h1] and [h2] and [h3] and [h4] and [h5] and [h6] and [paragraph] and [blockquote] and [list] and [table] and [code] and [textAlign] and [textScaleFactor] and [animated], the interpolation is performed using a step function. - /// If [t] is less than 0.5, the value from the current [SlideSpec] is used. Otherwise, the value - /// from the [other] [SlideSpec] is used. - /// - /// This method is typically used in animations to smoothly transition between - /// different [SlideSpec] configurations. - @override - SlideSpec lerp(SlideSpec? other, double t) { - if (other == null) return _$this; - - return SlideSpec( - h1: _$this.h1?.lerp(other.h1, t) ?? other.h1, - h2: _$this.h2?.lerp(other.h2, t) ?? other.h2, - h3: _$this.h3?.lerp(other.h3, t) ?? other.h3, - h4: _$this.h4?.lerp(other.h4, t) ?? other.h4, - h5: _$this.h5?.lerp(other.h5, t) ?? other.h5, - h6: _$this.h6?.lerp(other.h6, t) ?? other.h6, - paragraph: _$this.paragraph?.lerp(other.paragraph, t) ?? other.paragraph, - link: MixHelpers.lerpTextStyle(_$this.link, other.link, t), - blockSpacing: - MixHelpers.lerpDouble(_$this.blockSpacing, other.blockSpacing, t), - blockquote: - _$this.blockquote?.lerp(other.blockquote, t) ?? other.blockquote, - list: _$this.list?.lerp(other.list, t) ?? other.list, - table: _$this.table?.lerp(other.table, t) ?? other.table, - checkbox: MixHelpers.lerpTextStyle(_$this.checkbox, other.checkbox, t), - code: _$this.code?.lerp(other.code, t) ?? other.code, - a: MixHelpers.lerpTextStyle(_$this.a, other.a, t), - em: MixHelpers.lerpTextStyle(_$this.em, other.em, t), - strong: MixHelpers.lerpTextStyle(_$this.strong, other.strong, t), - del: MixHelpers.lerpTextStyle(_$this.del, other.del, t), - textAlign: t < 0.5 ? _$this.textAlign : other.textAlign, - img: MixHelpers.lerpTextStyle(_$this.img, other.img, t), - horizontalRuleDecoration: BoxDecoration.lerp( - _$this.horizontalRuleDecoration, other.horizontalRuleDecoration, t), - textScaleFactor: t < 0.5 ? _$this.textScaleFactor : other.textScaleFactor, - innerContainer: _$this.innerContainer.lerp(other.innerContainer, t), - outerContainer: _$this.outerContainer.lerp(other.outerContainer, t), - contentContainer: _$this.contentContainer.lerp(other.contentContainer, t), - image: _$this.image.lerp(other.image, t), - animated: t < 0.5 ? _$this.animated : other.animated, - ); - } - - /// The list of properties that constitute the state of this [SlideSpec]. - /// - /// This property is used by the [==] operator and the [hashCode] getter to - /// compare two [SlideSpec] instances for equality. - @override - List get props => [ - _$this.h1, - _$this.h2, - _$this.h3, - _$this.h4, - _$this.h5, - _$this.h6, - _$this.paragraph, - _$this.link, - _$this.blockSpacing, - _$this.blockquote, - _$this.list, - _$this.table, - _$this.checkbox, - _$this.code, - _$this.a, - _$this.em, - _$this.strong, - _$this.del, - _$this.textAlign, - _$this.img, - _$this.horizontalRuleDecoration, - _$this.textScaleFactor, - _$this.innerContainer, - _$this.outerContainer, - _$this.contentContainer, - _$this.image, - _$this.animated, - ]; - - SlideSpec get _$this => this as SlideSpec; -} - -/// Represents the attributes of a [SlideSpec]. -/// -/// This class encapsulates properties defining the layout and -/// appearance of a [SlideSpec]. -/// -/// Use this class to configure the attributes of a [SlideSpec] and pass it to -/// the [SlideSpec] constructor. -final class SlideSpecAttribute extends SpecAttribute { - final MdHeadingSpecAttribute? h1; - final MdHeadingSpecAttribute? h2; - final MdHeadingSpecAttribute? h3; - final MdHeadingSpecAttribute? h4; - final MdHeadingSpecAttribute? h5; - final MdHeadingSpecAttribute? h6; - final MdParagraphSpecAttribute? paragraph; - final TextStyleDto? link; - final double? blockSpacing; - final MdBlockquoteSpecAttribute? blockquote; - final MdListSpecAttribute? list; - final MdTableSpecAttribute? table; - final TextStyleDto? checkbox; - final MdCodeblockSpecAttribute? code; - final TextStyleDto? a; - final TextStyleDto? em; - final TextStyleDto? strong; - final TextStyleDto? del; - final WrapAlignment? textAlign; - final TextStyleDto? img; - final BoxDecorationDto? horizontalRuleDecoration; - final TextScaler? textScaleFactor; - final BoxSpecAttribute? innerContainer; - final BoxSpecAttribute? outerContainer; - final BoxSpecAttribute? contentContainer; - final ImageSpecAttribute? image; - - const SlideSpecAttribute({ - this.h1, - this.h2, - this.h3, - this.h4, - this.h5, - this.h6, - this.paragraph, - this.link, - this.blockSpacing, - this.blockquote, - this.list, - this.table, - this.checkbox, - this.code, - this.a, - this.em, - this.strong, - this.del, - this.textAlign, - this.img, - this.horizontalRuleDecoration, - this.textScaleFactor, - this.innerContainer, - this.outerContainer, - this.contentContainer, - this.image, - super.animated, - }); - - /// Resolves to [SlideSpec] using the provided [MixData]. - /// - /// If a property is null in the [MixData], it falls back to the - /// default value defined in the `defaultValue` for that property. - /// - /// ```dart - /// final slideSpec = SlideSpecAttribute(...).resolve(mix); - /// ``` - @override - SlideSpec resolve(MixData mix) { - return SlideSpec( - h1: h1?.resolve(mix), - h2: h2?.resolve(mix), - h3: h3?.resolve(mix), - h4: h4?.resolve(mix), - h5: h5?.resolve(mix), - h6: h6?.resolve(mix), - paragraph: paragraph?.resolve(mix), - link: link?.resolve(mix), - blockSpacing: blockSpacing, - blockquote: blockquote?.resolve(mix), - list: list?.resolve(mix), - table: table?.resolve(mix), - checkbox: checkbox?.resolve(mix), - code: code?.resolve(mix), - a: a?.resolve(mix), - em: em?.resolve(mix), - strong: strong?.resolve(mix), - del: del?.resolve(mix), - textAlign: textAlign, - img: img?.resolve(mix), - horizontalRuleDecoration: horizontalRuleDecoration?.resolve(mix), - textScaleFactor: textScaleFactor, - innerContainer: innerContainer?.resolve(mix), - outerContainer: outerContainer?.resolve(mix), - contentContainer: contentContainer?.resolve(mix), - image: image?.resolve(mix), - animated: animated?.resolve(mix) ?? mix.animation, - ); - } - - /// Merges the properties of this [SlideSpecAttribute] with the properties of [other]. - /// - /// If [other] is null, returns this instance unchanged. Otherwise, returns a new - /// [SlideSpecAttribute] with the properties of [other] taking precedence over - /// the corresponding properties of this instance. - /// - /// Properties from [other] that are null will fall back - /// to the values from this instance. - @override - SlideSpecAttribute merge(SlideSpecAttribute? other) { - if (other == null) return this; - - return SlideSpecAttribute( - h1: h1?.merge(other.h1) ?? other.h1, - h2: h2?.merge(other.h2) ?? other.h2, - h3: h3?.merge(other.h3) ?? other.h3, - h4: h4?.merge(other.h4) ?? other.h4, - h5: h5?.merge(other.h5) ?? other.h5, - h6: h6?.merge(other.h6) ?? other.h6, - paragraph: paragraph?.merge(other.paragraph) ?? other.paragraph, - link: link?.merge(other.link) ?? other.link, - blockSpacing: other.blockSpacing ?? blockSpacing, - blockquote: blockquote?.merge(other.blockquote) ?? other.blockquote, - list: list?.merge(other.list) ?? other.list, - table: table?.merge(other.table) ?? other.table, - checkbox: checkbox?.merge(other.checkbox) ?? other.checkbox, - code: code?.merge(other.code) ?? other.code, - a: a?.merge(other.a) ?? other.a, - em: em?.merge(other.em) ?? other.em, - strong: strong?.merge(other.strong) ?? other.strong, - del: del?.merge(other.del) ?? other.del, - textAlign: other.textAlign ?? textAlign, - img: img?.merge(other.img) ?? other.img, - horizontalRuleDecoration: - horizontalRuleDecoration?.merge(other.horizontalRuleDecoration) ?? - other.horizontalRuleDecoration, - textScaleFactor: other.textScaleFactor ?? textScaleFactor, - innerContainer: - innerContainer?.merge(other.innerContainer) ?? other.innerContainer, - outerContainer: - outerContainer?.merge(other.outerContainer) ?? other.outerContainer, - contentContainer: contentContainer?.merge(other.contentContainer) ?? - other.contentContainer, - image: image?.merge(other.image) ?? other.image, - animated: animated?.merge(other.animated) ?? other.animated, - ); - } - - /// The list of properties that constitute the state of this [SlideSpecAttribute]. - /// - /// This property is used by the [==] operator and the [hashCode] getter to - /// compare two [SlideSpecAttribute] instances for equality. - @override - List get props => [ - h1, - h2, - h3, - h4, - h5, - h6, - paragraph, - link, - blockSpacing, - blockquote, - list, - table, - checkbox, - code, - a, - em, - strong, - del, - textAlign, - img, - horizontalRuleDecoration, - textScaleFactor, - innerContainer, - outerContainer, - contentContainer, - image, - animated, - ]; -} - -/// Utility class for configuring [SlideSpecAttribute] properties. -/// -/// This class provides methods to set individual properties of a [SlideSpecAttribute]. -/// Use the methods of this class to configure specific properties of a [SlideSpecAttribute]. -class SlideSpecUtility - extends SpecUtility { - /// Utility for defining [SlideSpecAttribute.h1] - late final h1 = MdHeadingSpecUtility((v) => only(h1: v)); - - /// Utility for defining [SlideSpecAttribute.h2] - late final h2 = MdHeadingSpecUtility((v) => only(h2: v)); - - /// Utility for defining [SlideSpecAttribute.h3] - late final h3 = MdHeadingSpecUtility((v) => only(h3: v)); - - /// Utility for defining [SlideSpecAttribute.h4] - late final h4 = MdHeadingSpecUtility((v) => only(h4: v)); - - /// Utility for defining [SlideSpecAttribute.h5] - late final h5 = MdHeadingSpecUtility((v) => only(h5: v)); - - /// Utility for defining [SlideSpecAttribute.h6] - late final h6 = MdHeadingSpecUtility((v) => only(h6: v)); - - /// Utility for defining [SlideSpecAttribute.paragraph] - late final paragraph = MdParagraphSpecUtility((v) => only(paragraph: v)); - - /// Utility for defining [SlideSpecAttribute.link] - late final link = TextStyleUtility((v) => only(link: v)); - - /// Utility for defining [SlideSpecAttribute.blockSpacing] - late final blockSpacing = DoubleUtility((v) => only(blockSpacing: v)); - - /// Utility for defining [SlideSpecAttribute.blockquote] - late final blockquote = MdBlockquoteSpecUtility((v) => only(blockquote: v)); - - /// Utility for defining [SlideSpecAttribute.list] - late final list = MdListSpecUtility((v) => only(list: v)); - - /// Utility for defining [SlideSpecAttribute.table] - late final table = MdTableSpecUtility((v) => only(table: v)); - - /// Utility for defining [SlideSpecAttribute.checkbox] - late final checkbox = TextStyleUtility((v) => only(checkbox: v)); - - /// Utility for defining [SlideSpecAttribute.code] - late final code = MdCodeblockSpecUtility((v) => only(code: v)); - - /// Utility for defining [SlideSpecAttribute.a] - late final a = TextStyleUtility((v) => only(a: v)); - - /// Utility for defining [SlideSpecAttribute.em] - late final em = TextStyleUtility((v) => only(em: v)); - - /// Utility for defining [SlideSpecAttribute.strong] - late final strong = TextStyleUtility((v) => only(strong: v)); - - /// Utility for defining [SlideSpecAttribute.del] - late final del = TextStyleUtility((v) => only(del: v)); - - /// Utility for defining [SlideSpecAttribute.textAlign] - late final textAlign = WrapAlignmentUtility((v) => only(textAlign: v)); - - /// Utility for defining [SlideSpecAttribute.img] - late final img = TextStyleUtility((v) => only(img: v)); - - /// Utility for defining [SlideSpecAttribute.horizontalRuleDecoration] - late final divider = - BoxDecorationUtility((v) => only(horizontalRuleDecoration: v)); - - /// Utility for defining [SlideSpecAttribute.textScaleFactor] - late final textScaleFactor = - TextScalerUtility((v) => only(textScaleFactor: v)); - - /// Utility for defining [SlideSpecAttribute.innerContainer] - late final innerContainer = BoxSpecUtility((v) => only(innerContainer: v)); - - /// Utility for defining [SlideSpecAttribute.outerContainer] - late final outerContainer = BoxSpecUtility((v) => only(outerContainer: v)); - - /// Utility for defining [SlideSpecAttribute.contentContainer] - late final contentContainer = - BoxSpecUtility((v) => only(contentContainer: v)); - - /// Utility for defining [SlideSpecAttribute.image] - late final image = ImageSpecUtility((v) => only(image: v)); - - /// Utility for defining [SlideSpecAttribute.animated] - late final animated = AnimatedUtility((v) => only(animated: v)); - - SlideSpecUtility(super.builder); - - static final self = SlideSpecUtility((v) => v); - - /// Returns a new [SlideSpecAttribute] with the specified properties. - @override - T only({ - MdHeadingSpecAttribute? h1, - MdHeadingSpecAttribute? h2, - MdHeadingSpecAttribute? h3, - MdHeadingSpecAttribute? h4, - MdHeadingSpecAttribute? h5, - MdHeadingSpecAttribute? h6, - MdParagraphSpecAttribute? paragraph, - TextStyleDto? link, - double? blockSpacing, - MdBlockquoteSpecAttribute? blockquote, - MdListSpecAttribute? list, - MdTableSpecAttribute? table, - TextStyleDto? checkbox, - MdCodeblockSpecAttribute? code, - TextStyleDto? a, - TextStyleDto? em, - TextStyleDto? strong, - TextStyleDto? del, - WrapAlignment? textAlign, - TextStyleDto? img, - BoxDecorationDto? horizontalRuleDecoration, - TextScaler? textScaleFactor, - BoxSpecAttribute? innerContainer, - BoxSpecAttribute? outerContainer, - BoxSpecAttribute? contentContainer, - ImageSpecAttribute? image, - AnimatedDataDto? animated, - }) { - return builder(SlideSpecAttribute( - h1: h1, - h2: h2, - h3: h3, - h4: h4, - h5: h5, - h6: h6, - paragraph: paragraph, - link: link, - blockSpacing: blockSpacing, - blockquote: blockquote, - list: list, - table: table, - checkbox: checkbox, - code: code, - a: a, - em: em, - strong: strong, - del: del, - textAlign: textAlign, - img: img, - horizontalRuleDecoration: horizontalRuleDecoration, - textScaleFactor: textScaleFactor, - innerContainer: innerContainer, - outerContainer: outerContainer, - contentContainer: contentContainer, - image: image, - animated: animated, - )); - } -} - -/// A tween that interpolates between two [SlideSpec] instances. -/// -/// This class can be used in animations to smoothly transition between -/// different [SlideSpec] specifications. -class SlideSpecTween extends Tween { - SlideSpecTween({ - super.begin, - super.end, - }); - - @override - SlideSpec lerp(double t) { - if (begin == null && end == null) { - return const SlideSpec(); - } - - if (begin == null) { - return end!; - } - - return begin!.lerp(end!, t); - } -} diff --git a/packages/superdeck/lib/styles/style_util.dart b/packages/superdeck/lib/styles/style_util.dart deleted file mode 100644 index 6a258b67..00000000 --- a/packages/superdeck/lib/styles/style_util.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../superdeck.dart'; - -class SlideVariant extends Variant { - const SlideVariant(super.name); -} - -TextStyle get baseTextStyle => - const TextStyle().copyWith(fontSize: 22, height: 1.4, color: Colors.white); -TextStyle get monoTextStyle => baseTextStyle.copyWith(fontSize: 16); -TextStyle get serifTextStyle => baseTextStyle.copyWith(fontSize: 50); - -TextStyle get headingTextStyle => baseTextStyle.copyWith(height: 1.2); - -final _util = SlideSpecUtility.self; -final _h1 = _util.h1; -final _h2 = _util.h2; -final _h3 = _util.h3; -final _h4 = _util.h4; -final _h5 = _util.h5; -final _h6 = _util.h6; -final _headings = _util.headings; -final _paragraph = _util.paragraph; -final _link = _util.link; -final _list = _util.list; -final _table = _util.table; -final _code = _util.code; -final _blockquote = _util.blockquote; -final _divider = _util.divider; -final _image = _util.image; -final _outerContainer = _util.outerContainer; -final _contentContainer = _util.contentContainer; -final _blockSpacing = _util.blockSpacing; -final _textStyle = _util.textStyle; -final _checkbox = _util.checkbox; - -Style get defaultStyle => Style.create([ - _outerContainer.color.black(), - // $.innerContainer.color.transparent(), - _contentContainer.padding.all(40), - // $.contentContainer.color(Colors.yellow), - _textStyle.as(baseTextStyle), - _headings.textStyle.as(headingTextStyle), - _h1.textStyle.fontSize(96), - _h1.padding.bottom(12), - _h2.textStyle.fontSize(72), - _h2.padding.bottom(9), - _h3.textStyle.fontSize(48), - _h3.padding.bottom(6), - _h4.textStyle.fontSize(36), - _h4.padding.bottom(4), - _h5.textStyle.fontSize(24), - _h5.padding.bottom(3), - _h6.textStyle.as(baseTextStyle), - _h6.padding.bottom(3), - _paragraph.textStyle.as(baseTextStyle), - _paragraph.padding.bottom(12), - _link.color(const Color.fromARGB(255, 66, 82, 96)), - _list.bulletStyle.as(baseTextStyle), - _checkbox.as(baseTextStyle), - - _table.headStyle.as(baseTextStyle.copyWith(fontWeight: FontWeight.bold)), - _table.bodyStyle.as(baseTextStyle), - _blockSpacing(20), - _table.cellPadding.all(12), - _table.border.all(color: Colors.grey, width: 2), - _table.cellDecoration.color(Colors.grey.withOpacity(0.1)), - _code.textStyle.as(monoTextStyle), - _code.padding.all(24), - _code.decoration.color(const Color.fromARGB(255, 23, 23, 23)), - _code.decoration.borderRadius.circular(10), - _blockquote.textStyle.as(serifTextStyle), - _blockquote.textStyle.fontSize(32), - _blockquote.padding.bottom(12), - _blockquote.padding.left(30), - _blockquote.decoration.border.left.color(Colors.grey), - _blockquote.decoration.border.left.width(4), - _divider.border.width(2), - _divider.color(Colors.grey), - - _image.fit.cover(), - ]); diff --git a/packages/superdeck/lib/superdeck.dart b/packages/superdeck/lib/superdeck.dart index dc013a99..81f5a161 100644 --- a/packages/superdeck/lib/superdeck.dart +++ b/packages/superdeck/lib/superdeck.dart @@ -1,11 +1,15 @@ library superdeck; -export 'package:mix/mix.dart'; -export 'package:superdeck/components/superdeck_app.dart'; -export 'package:superdeck/models/asset_model.dart'; -export 'package:superdeck/models/options_model.dart'; -export 'package:superdeck/models/slide_model.dart'; -export 'package:superdeck/providers/controller.dart'; -export 'package:superdeck/styles/style_spec.dart'; +export 'package:superdeck/src/components/molecules/block_widget.dart'; +export 'package:superdeck/src/components/parts/slide_parts.dart'; +export 'package:superdeck/src/components/superdeck_app.dart'; +export 'package:superdeck/src/modules/common/helpers/extensions.dart'; +export 'package:superdeck/src/modules/common/helpers/provider.dart'; +export 'package:superdeck/src/modules/common/styles/style.dart'; +export 'package:superdeck/src/modules/common/styles/style_spec.dart'; +export 'package:superdeck/src/modules/deck/deck_options.dart'; +export 'package:superdeck/src/modules/deck/deck_provider.dart'; +export 'package:superdeck/src/modules/deck/slide_configuration.dart'; +export 'package:superdeck_core/superdeck_core.dart'; -export 'components/organisms/app_shell.dart'; +export 'src/components/organisms/app_shell.dart'; diff --git a/packages/superdeck/lib/templates/image_template.dart b/packages/superdeck/lib/templates/image_template.dart deleted file mode 100644 index 39441020..00000000 --- a/packages/superdeck/lib/templates/image_template.dart +++ /dev/null @@ -1,52 +0,0 @@ -part of 'templates.dart'; - -class ImageTemplate extends SplitTemplateBuilder { - const ImageTemplate( - super.model, { - super.key, - }); - - @override - Widget build(BuildContext context) { - final spec = SlideSpec.of(context); - final src = config.options.src; - final boxFit = config.options.fit?.toBoxFit() ?? spec.image.fit; - - // THis slide breaks in half and I want to calculate the size based on if its in top or bottom - // or left or right. Also there is a property called flex which is how much of the slide it takes - // so I can use that to calculate the size of the canvas - final firstHalf = config.contentOptions?.flex ?? defaultFlex; - final imageHalf = config.options.flex; - -// available size const width = 1280.0; -//const height = 720.0; - - double width; - double height; - const availableSize = kResolution; - if (config.options.position.isHorizontal()) { - width = availableSize.width * firstHalf / (firstHalf + imageHalf); - height = availableSize.height; - } else { - width = availableSize.width; - height = availableSize.height * firstHalf / (firstHalf + imageHalf); - } - - final side = Container( - height: spec.image.height ?? height, - width: spec.image.width ?? width, - alignment: spec.image.alignment, - decoration: BoxDecoration( - image: DecorationImage( - image: getImageProvider(src, targetSize: Size(width, height)), - centerSlice: spec.image.centerSlice, - repeat: spec.image.repeat ?? ImageRepeat.noRepeat, - filterQuality: spec.image.filterQuality ?? FilterQuality.low, - fit: boxFit, - ), - ), - ); - - return buildSplitSlide(side); - } -} diff --git a/packages/superdeck/lib/templates/invalid_template.dart b/packages/superdeck/lib/templates/invalid_template.dart deleted file mode 100644 index 4b5b1267..00000000 --- a/packages/superdeck/lib/templates/invalid_template.dart +++ /dev/null @@ -1,40 +0,0 @@ -part of 'templates.dart'; - -class InvalidTemplate extends TemplateBuilder { - const InvalidTemplate( - super.config, { - super.key, - }); - - @override - Widget build(BuildContext context) { - const red = Color.fromARGB(255, 166, 6, 6); - - return SpecBuilder( - style: _invalidStyle, - builder: (context) { - // Maybe there are no validation errors just return the content - return Container( - decoration: BoxDecoration( - color: red, - border: Border.all(color: red, width: 20), - ), - child: buildContent(), - ); - }); - } -} - -final _ = SlideSpecUtility.self; - -final _invalidStyle = Style( - _.paragraph.textStyle.color(Colors.white), - _.h1.textStyle.color(const Color.fromARGB(255, 71, 1, 1)), - _.h1.textStyle.fontSize(36.0), - _.h1.textStyle.bold(), - _.h2.padding.top(0), - _.h2.textStyle.bold(), - _.h2.textStyle.color.yellow(), - _.code.textStyle.color.yellow(), - _.code.textStyle.backgroundColor(const Color.fromARGB(255, 84, 6, 6)), -); diff --git a/packages/superdeck/lib/templates/simple_template.dart b/packages/superdeck/lib/templates/simple_template.dart deleted file mode 100644 index 1c144da8..00000000 --- a/packages/superdeck/lib/templates/simple_template.dart +++ /dev/null @@ -1,11 +0,0 @@ -part of 'templates.dart'; - -class SimpleTemplate extends TemplateBuilder { - const SimpleTemplate( - super.config, { - super.key, - }); - - @override - Widget build(BuildContext context) => buildContent(); -} diff --git a/packages/superdeck/lib/templates/template_builder.dart b/packages/superdeck/lib/templates/template_builder.dart deleted file mode 100644 index e4f60148..00000000 --- a/packages/superdeck/lib/templates/template_builder.dart +++ /dev/null @@ -1,79 +0,0 @@ -part of 'templates.dart'; - -sealed class TemplateBuilder extends StatelessWidget { - @visibleForTesting - final T config; - - int get defaultFlex => 1; - - const TemplateBuilder(this.config, {super.key}); - - static TemplateBuilder buildTemplate(T config) { - return switch (config) { - (SimpleSlide c) => SimpleTemplate(c), - (WidgetSlide c) => WidgetTemplate(c), - (ImageSlide c) => ImageTemplate(c), - (TwoColumnSlide c) => TwoColumnTemplate(c), - (TwoColumnHeaderSlide c) => TwoColumnHeaderTemplate(c), - (InvalidSlide c) => InvalidTemplate(c), - _ => throw UnimplementedError( - 'Slide config not implemented ${config.runtimeType}'), - } as TemplateBuilder; - } - - Widget buildContent() { - return _buildContent( - config.content, - config.contentOptions, - ); - } - - @protected - Widget _buildContent(String content, ContentOptions? options) { - return SlideContent( - content: content, - options: options, - ); - } - - Widget buildContentSection(SectionData section) { - return Expanded( - flex: section.options?.flex ?? defaultFlex, - child: _buildContent(section.content, section.options), - ); - } -} - -abstract class SplitTemplateBuilder - extends TemplateBuilder { - const SplitTemplateBuilder( - super.config, { - super.key, - }); - - Widget buildSplitSlide(Widget side) { - final position = config.options.position; - final flex = config.options.flex; - - List children = [ - buildContentSection(( - content: config.content, - options: config.contentOptions ?? const ContentOptions(), - )), - Expanded(flex: flex, child: side), - ]; - - if (position == LayoutPosition.left || position == LayoutPosition.top) { - children = children.reversed.toList(); - } - - final isVertical = - position == LayoutPosition.top || position == LayoutPosition.bottom; - - if (isVertical) { - return Column(children: children); - } else { - return Row(children: children); - } - } -} diff --git a/packages/superdeck/lib/templates/templates.dart b/packages/superdeck/lib/templates/templates.dart deleted file mode 100644 index 12be6bf8..00000000 --- a/packages/superdeck/lib/templates/templates.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../components/atoms/cache_image_widget.dart'; -import '../components/molecules/code_preview.dart'; -import '../components/molecules/slide_content.dart'; -import '../helpers/constants.dart'; -import '../providers/examples_provider.dart'; -import '../superdeck.dart'; - -part 'image_template.dart'; -part 'invalid_template.dart'; -part 'simple_template.dart'; -part 'template_builder.dart'; -part 'two_column_template.dart'; -part 'widget_example_template.dart'; diff --git a/packages/superdeck/lib/templates/two_column_template.dart b/packages/superdeck/lib/templates/two_column_template.dart deleted file mode 100644 index 7759c207..00000000 --- a/packages/superdeck/lib/templates/two_column_template.dart +++ /dev/null @@ -1,76 +0,0 @@ -part of 'templates.dart'; - -class TwoColumnTemplate extends TemplateBuilder { - const TwoColumnTemplate( - super.config, { - super.key, - }); - - @override - Widget build(BuildContext context) { - final options = config.contentOptions ?? const ContentOptions(); - final alignment = options.alignment; - - return Container( - alignment: alignment.toAlignment(), - child: Row( - children: [ - buildContentSection(config.left), - buildContentSection(config.right), - ], - ), - ); - } -} - -class TwoColumnHeaderTemplate extends TemplateBuilder { - const TwoColumnHeaderTemplate( - super.config, { - super.key, - }); - - @override - Widget build(BuildContext context) { - final options = config.contentOptions ?? const ContentOptions(); - final alignment = options.alignment; - final flex = options.flex ?? defaultFlex; - - final header = config.header; - - final left = config.left; - final right = config.right; - return Column( - children: [ - Expanded( - flex: header.options?.flex ?? defaultFlex, - child: Row( - children: [ - buildContentSection(( - content: header.content, - options: options.merge(header.options), - )), - ], - ), - ), - Expanded( - flex: flex, - child: Container( - alignment: alignment.toAlignment(), - child: Row( - children: [ - buildContentSection(( - content: left.content, - options: options.merge(left.options), - )), - buildContentSection(( - content: right.content, - options: options.merge(right.options), - )), - ], - ), - ), - ), - ], - ); - } -} diff --git a/packages/superdeck/lib/templates/widget_example_template.dart b/packages/superdeck/lib/templates/widget_example_template.dart deleted file mode 100644 index 7f2e3169..00000000 --- a/packages/superdeck/lib/templates/widget_example_template.dart +++ /dev/null @@ -1,26 +0,0 @@ -part of 'templates.dart'; - -class WidgetTemplate extends SplitTemplateBuilder { - const WidgetTemplate( - super.model, { - super.key, - }); - - @override - Widget build(BuildContext context) { - final options = config.options; - - final exampleBuilder = ExamplesProvider.of(context)[options.name]; - - return buildSplitSlide( - Builder( - builder: (context) { - return ExamplePreview( - args: options.args, - builder: exampleBuilder!, - ); - }, - ), - ); - } -} diff --git a/packages/superdeck/pubspec.yaml b/packages/superdeck/pubspec.yaml index 9c2ee43d..49a27889 100644 --- a/packages/superdeck/pubspec.yaml +++ b/packages/superdeck/pubspec.yaml @@ -11,51 +11,52 @@ environment: dependencies: flutter: sdk: flutter - cached_network_image: ^3.3.1 - window_manager: ^0.3.9 + cached_network_image: ^3.4.1 + window_manager: ^0.4.3 collection: ^1.18.0 path: ^1.9.0 - dart_mappable: ^4.2.2 - yaml: ^3.1.2 - animate_do: ^3.3.4 syntax_highlight: ^0.4.0 scrollable_positioned_list: ^0.3.8 pdf: ^3.11.1 localstorage: ^5.0.0 - go_router: ^14.1.4 + go_router: ^14.2.7 file_picker: ^8.0.7 path_provider: ^2.1.4 - url_launcher: ^6.2.6 - mix: ^1.4.4 - mix_annotations: ^0.2.1 - flutter_markdown: ^0.7.3 - markdown: ^7.2.2 - flutter_hooks: ^0.20.5 - web: ^0.5.1 - file_saver: ^0.2.13 - render: ^0.0.1 - remix: - path: ../../../mix/packages/remix + url_launcher: ^6.3.1 + mix: ^1.5.4 + mix_annotations: ^0.3.1 + flutter_markdown: ^0.7.5 + superdeck_core: ^0.0.1 + markdown: ^7.3.0 + web: ^1.1.0 + file_saver: ^0.2.14 go_router_paths: ^0.2.2 - visibility_detector: ^0.4.0+2 - flutter_chat_ui: ^1.6.15 image_picker: ^1.1.2 - open_filex: ^4.5.0 - google_generative_ai: ^0.4.4 + open_filex: ^4.6.0 + auto_size_text: ^3.0.0 + material_symbols_icons: ^4.2780.0 + uri: ^1.0.0 + webview_flutter: ^4.10.0 + webview_flutter_web: ^0.2.3 + google_fonts: ^6.2.1 + dart_mappable: ^4.3.0 + meta: ^1.15.0 + universal_html: ^2.2.4 + + dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^4.0.0 - build_runner: ^2.4.10 - dart_mappable_builder: ^4.2.3 - - build_verify: ^3.1.0 - mix_generator: ^0.2.2 - custom_lint: ^0.6.4 - mix_lint: ^0.1.1 - + mix_generator: ^0.3.2 + custom_lint: ^0.6.8 + # mix_lint: ^0.1.1 + build_runner: ^2.4.13 + dart_mappable_builder: ^4.3.0 + dart_code_metrics_presets: ^2.19.0 flutter: assets: - - grammars/ \ No newline at end of file + - assets/ + - assets/grammars/ \ No newline at end of file diff --git a/packages/superdeck/pubspec_overrides.yaml b/packages/superdeck/pubspec_overrides.yaml index c11e4d1a..fd21af7e 100644 --- a/packages/superdeck/pubspec_overrides.yaml +++ b/packages/superdeck/pubspec_overrides.yaml @@ -1,11 +1,4 @@ +# melos_managed_dependency_overrides: superdeck_core dependency_overrides: - mix: - path: ../../../mix/packages/mix - mix_generator: - path: ../../../mix/packages/mix_generator - mix_lint: - path: ../../../mix/packages/mix_lint - mix_annotations: - path: ../../../mix/packages/mix_annotations - remix: - path: ../../../mix/packages/remix + superdeck_core: + path: ../superdeck_core diff --git a/packages/superdeck/test/fixtures/deck_reference.json b/packages/superdeck/test/fixtures/deck_reference.json index 78c0f41d..47879ef9 100644 --- a/packages/superdeck/test/fixtures/deck_reference.json +++ b/packages/superdeck/test/fixtures/deck_reference.json @@ -1,115 +1,205 @@ { - "config": { "transition": { "type": "fade_in", "duration": 0 } }, + "config": { + "transition": { + "type": "fade_in", + "duration": 0 + } + }, "slides": [ { - "style": "quote", - "layout": "image", + "key": "jrySbty2", "options": { - "src": "https://picsum.photos/600/600.webp", - "fit": "cover" + "style": "custom" }, - "content": "> Create your Flutter presentations faster and easier than ever.\n> You can quote me on that\n> ### Leo Farias", - "key": "DMiweBMq", - "content_options": { "alignment": "bottom_right" } + "markdown": "{@section}\n{@column}\n## This is an example of a widgets\n\n{@widget name: demo}\n\n{@column}\n\n```dart\nimport 'package:flutter/material.dart';\n\nvoid main() {\n final style = Style(\n $box.padding.all(),\n $box.border.all(),\n );\n}\n\n```", + "sections": [ + { + "options": null, + "sub_sections": [ + { + "content": "\n## This is an example of a widgets", + "options": null, + "type": "column" + }, + { + "options": { + "name": "demo", + "type": "widget_options" + }, + "content": "", + "type": "widget" + }, + { + "content": "\n```dart\nimport 'package:flutter/material.dart';\nvoid main() {\n final style = Style(\n $box.padding.all(),\n $box.border.all(),\n );\n}\n```", + "options": null, + "type": "column" + } + ] + } + ] }, { - "background": "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExZGt1MnQ5N2k3cXVma24wb3V5cThlZ3ExY2NvY3czcmozang0bGQ1ZSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/XzWd8acQ37byKR4tmd/giphy.gif", - "style": "cover", - "key": "bfnlriXq", - "content_options": null, - "content": "# Complex layout" + "key": "rHLJMgwc", + "markdown": "```mermaid\ngraph TD\n A[Enter Chart Definition] --> B(Preview)\n B --> C{decide}\n C --> D[Keep]\n C --> E[Edit Definition]\n E --> B\n D --> F[Save Image and Car]\n F --> B \n```\n\n{@column flex: 3}\n\n```mermaid\nsequenceDiagram\n participant Customer as 🧑 Customer\n participant Barista as ☕ Barista\n participant CoffeeMachine as 🏭 Coffee Machine\n```\n\n\n", + "sections": [ + { + "sub_sections": [ + { + "content": "![Mermaid Diagram](.superdeck/generated/mermaid_llAVsepR.png)", + "type": "column" + }, + { + "content": "\n![Mermaid Diagram](.superdeck/generated/mermaid_BTVEzSEg.png)\n", + "options": { + "flex": 3 + }, + "type": "column" + } + ] + } + ] }, { - "layout": "image", - "content": "## Image Layout\n\nCreate beautiful slides with images that fit your content.\n\n##### Options\n```yaml\ncontent:\noptions:\n src: https//www.url.com/image.jpg\n fit: cover\n position: left\n flex: 1\n```\n\n> Define position fit and flex options for the image.", - "style": "show_sections", + "key": "u0rRynPM", "options": { - "src": "https://picsum.photos/900/700?waves", - "fit": "cover", - "position": "left", - "flex": 1 + "style": "quote" }, - "key": "9wPFDzIT", - "content_options": { "alignment": "bottom_right", "flex": 1 } + "markdown": "{@section}\n{@column align: bottom_right flex: 3}\n# This presentation will be great\n\n{@column}\n\n{@section}\n\n\n{@column }\n{@column flex: 2 align: top_right}\n> Create your Flutter presentations faster and easier than ever.\n> You can quote me on that\n> ### Leo", + "sections": [ + { + "options": null, + "sub_sections": [ + { + "content": "\n# This presentation will be great", + "options": { + "flex": 3, + "align": "bottom_right" + }, + "type": "column" + }, + { + "content": "", + "options": null, + "type": "column" + } + ] + }, + { + "options": null, + "sub_sections": [ + { + "content": "", + "options": null, + "type": "column" + }, + { + "content": "\n> Create your Flutter presentations faster and easier than ever.\n> You can quote me on that\n> ### Leo", + "options": { + "flex": 2, + "align": "top_right" + }, + "type": "column" + } + ] + } + ] }, { - "layout": "two_column", - "style": "show_sections", - "sections": { - "left": { "flex": 2 }, - "right": { "alignment": "bottom_left" } - }, - "key": "zZ3F13ZY", - "content_options": null, - "content": "::left::\n\n# Two Column\n\nThis is a two-column layout. You can use it to compare two different concepts or ideas.\n\n::right::\n\n### Section Options\n\nEasily customize the content of each section to suit your needs.\n\nUse front matter to define the layout of each section\n\n\n```yaml\nsections:\n left:\n flex: 2\n right:\n alignment: bottom_left\n```" - }, - { - "layout": "two_column_header", - "content": "# Two Column + Header\n\n\n::left::\n\n### Left Section\nEasily customize the content of each section to suit your needs.\n\nUse front matter to define the layout of each section\n::right::\n\n#### Section Options\n\n```yaml\nsections:\n left:\n alignment: bottom_right\n flex: 2\n right:\n alignment: bottom_left\n header:\n alignment: bottom_left\n```", - "sections": { - "left": { "flex": 2 }, - "right": { "alignment": "bottom_left" }, - "header": { "alignment": "bottom_left" } - }, - "style": "show_sections", - "key": "SHmGSyUB", - "content_options": { "alignment": "center", "flex": 2 } - }, - { - "style": "rad", - "layout": "two_column", - "content": "# Mix\n\nIntegration with Mix gives you complete control over all styling elements in your slides with a simple and intuitive API.\n\n::right::\n\n```dart\nVariantAttribute get radStyle {\n return const SlideVariant('rad')(\n $.h1.textStyle.as(GoogleFonts.poppins()),\n $.h1.textStyle.fontSize(140),\n $.code.decoration.border.all(\n color: Colors.red,\n width: 3,\n ),\n $.code.decoration(\n color: Colors.black54,\n ),\n $.code.padding.all(40),\n\n $.outerContainer.margin.all(60),\n\n $.innerContainer.borderRadius(25),\n $.innerContainer.shadow(\n blurRadius: 0,\n spreadRadius: 10,\n color: Colors.red.withOpacity(1),\n ),\n $.innerContainer.gradient.radial(\n stops: [0.0, 1.0],\n radius: 0.7,\n colors: [Colors.purple, Colors.deepPurple],\n ),\n\n // Events\n onMouseHover((event) {\n final position = event.position;\n final dx = position.x * 10;\n final dy = position.y * 10;\n\n return Style(\n $.innerContainer.transform(_transformMatrix(position)),\n $.innerContainer.shadow.offset(dx, dy),\n $.innerContainer.gradient.radial(\n center: position,\n ),\n );\n }),\n\n (onPressed | onLongPressed)(\n $.innerContainer.shadow(\n blurRadius: 5,\n spreadRadius: 1,\n offset: Offset.zero,\n color: Colors.purpleAccent,\n ),\n $.innerContainer.border.all(color: Colors.white, width: 1),\n $.innerContainer.gradient.radial\n .colors([Colors.purpleAccent, Colors.purpleAccent]),\n ),\n );\n}\n```", - "sections": { - "left": null, - "right": { "alignment": "bottom_left", "flex": 2 } + "key": "huRpyhFQ", + "options": { + "style": "show_sections" }, - "key": "Ad4sj9H3", - "content_options": { "alignment": "center" } - }, - { - "style": "cover", - "background": "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExeGswdWJvY2oxazJoY3g2Y2poNHBvZXlpYmd5YTg0Z2g0ODRrbng4MyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/oB6KlAvOuaLtxYy8l4/giphy.gif", - "key": "J7Nbh0zk", - "content_options": null, - "content": "# Markdown support" - }, - { - "style": "show_sections", - "layout": "two_column", - "sections": null, - "content": "::left::\n\n\n**Bold Text**\n\n*Italic Text*\n\n~~Strikethrough~~\n\n`Inline Code`\n\n[Link here](https://github.com/leoafarias/superdeck)\n\n::right::\n\nLists\n\n1. Ordered list item 1\n2. Ordered list item 2\n\n- Unordered list item 1\n- Unordered list item 2\n\nQuotes\n\n> If you want to go fast, go alone. \n> If you want to go far, go together.\n> ### African Proverb", - "key": "ZiBdgDuC", - "content_options": { "flex": 4 } - }, - { - "layout": "two_column", - "key": "segi4wcj", - "content_options": null, - "content": "::left::\n\n\nCode\n```dart\nint factorial(int n) {\n return n == 0 ? 1 : n * factorial(n - 1);\n}\n```\n\nTasks\n- [ ] Item 1\n- [x] Item 2\n\nSubtasks\n\n- [x] Item 1\n - [ ] Subitem 1\n\n::right::\n\nImages\n![Unsplash Image](https://picsum.photos/300/200?landscape)\n\n\nTable\n\n| Header 1 | Header 2 |\n|----------|----------|\n| Cell 1A | Cell 1B |\n| Cell 2A | Cell 2B |\n\nDivider\n\n___" + "markdown": "{@section}\n{@image src: https://picsum.photos/1200/1200?waves align: top_left fit: cover}\n\n{@section flex: 2}\n{@column flex: 2}\n# Two Column HGoes here\n\nThis is a two-column layout. You can use it to compare two different concepts or ideas.\n\n\n{@column}\n\n### Section Options\n\nEasily customize the content of each section to suit your needs.\n\nUse front matter to define the layout of each section", + "sections": [ + { + "options": null, + "sub_sections": [ + { + "options": { + "src": "https://picsum.photos/1200/1200?waves", + "fit": "cover", + "align": "top_left", + "type": "image" + }, + "content": "", + "type": "image" + } + ] + }, + { + "options": { + "flex": 2 + }, + "sub_sections": [ + { + "content": "\n# Two Column HGoes here\nThis is a two-column layout. You can use it to compare two different concepts or ideas.", + "options": { + "flex": 2 + }, + "type": "column" + }, + { + "content": "\n### Section Options\nEasily customize the content of each section to suit your needs.\nUse front matter to define the layout of each section", + "options": null, + "type": "column" + } + ] + } + ] }, { - "title": "Mermaid example", - "layout": "two_column", - "key": "ElKv6elN", - "content_options": null, - "content": "::left::\n\n![Mermaid Diagram](superdeck/generated/sd_mermaid_s3Iic43G.png)\n \n\n::right::\n\n## Mermaid Support\n\nSuperdeck allows you to use Mermaid diagrams in your slides. It automatically converts the code into a visual representation." - }, - { - "layout": "widget", + "key": "twktEcis", "options": { - "name": "demo", - "args": { "text": "Hello, Superdeck!", "height": 200.0, "width": 300.0 } + "style": "show_sections" }, - "key": "7r83tgXu", - "content_options": null, - "content": "## Showcase your widgets" + "markdown": "{@section}\n{@column align: bottom_right}\n\n## First\n\n{@column} \n\n\n## Header\n\n{@section flex: 2}\n\n### Left Section\nEasily customize the content of each section to suit your needs.\n\nUse front matter to define the layout of each section\n\n{@column}\n\n#### Section Options\n\n```yaml\nsections:\n left:\n alignment: bottom_right\n flex: 2\n right:\n alignment: bottom_left\n header:\n alignment: bottom_left\n```", + "sections": [ + { + "options": null, + "sub_sections": [ + { + "content": "\n## First", + "options": { + "align": "bottom_right" + }, + "type": "column" + }, + { + "content": "\n## Header", + "options": null, + "type": "column" + } + ] + }, + { + "options": { + "flex": 2 + }, + "sub_sections": [ + { + "content": "### Left Section\nEasily customize the content of each section to suit your needs.\nUse front matter to define the layout of each section", + "type": "column" + }, + { + "content": "\n#### Section Options\n```yaml\nsections:\n left:\n alignment: bottom_right\n flex: 2\n right:\n alignment: bottom_left\n header:\n alignment: bottom_left\n```", + "options": null, + "type": "column" + } + ] + } + ] } ], "assets": [ { - "path": "superdeck/generated/sd_mermaid_s3Iic43G.png", - "width": 600, - "height": 866 + "path": ".superdeck/generated/mermaid_llAVsepR.png", + "width": 313, + "height": 449 + }, + { + "path": ".superdeck/generated/mermaid_BTVEzSEg.png", + "width": 650, + "height": 171 } ] } diff --git a/packages/superdeck/test/helpers/deep_merge_test.dart b/packages/superdeck/test/helpers/deep_merge_test.dart index f1bf88ad..917f1ef5 100644 --- a/packages/superdeck/test/helpers/deep_merge_test.dart +++ b/packages/superdeck/test/helpers/deep_merge_test.dart @@ -1,5 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:superdeck/helpers/deep_merge.dart'; +import 'package:superdeck/src/modules/common/helpers/deep_merge.dart'; void main() { group('deepMerge', () { diff --git a/packages/superdeck/test/helpers/measure_size_test.dart b/packages/superdeck/test/helpers/measure_size_test.dart index 649bd1b3..4b323476 100644 --- a/packages/superdeck/test/helpers/measure_size_test.dart +++ b/packages/superdeck/test/helpers/measure_size_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:superdeck/helpers/measure_size.dart'; +import 'package:superdeck/src/modules/common/helpers/measure_size.dart'; import '../test_helpers.dart'; diff --git a/packages/superdeck/test/helpers/memory_image_provider_test.dart b/packages/superdeck/test/helpers/memory_image_provider_test.dart deleted file mode 100644 index 6ef97407..00000000 --- a/packages/superdeck/test/helpers/memory_image_provider_test.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/widgets.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:superdeck/helpers/cache_memory_image.dart'; - -void main() { - group('CachedMemoryImage', () { - const base64TestImage = - 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg=='; - - testWidgets('builds Image widget with correct properties', - (WidgetTester tester) async { - await tester.pumpWidget( - const CachedMemoryImage( - base64Image: base64TestImage, - width: 100, - height: 100, - fit: BoxFit.fill, - ), - ); - - final Image image = tester.widget(find.byType(Image)); - expect(image.width, equals(100.0)); - expect(image.height, equals(100.0)); - expect(image.fit, equals(BoxFit.fill)); - expect(image.gaplessPlayback, isTrue); - }); - - testWidgets('decodes base64 string to bytes', (WidgetTester tester) async { - await tester.pumpWidget( - const CachedMemoryImage(base64Image: base64TestImage), - ); - - final Image image = tester.widget(find.byType(Image)); - final MemoryImage memoryImage = image.image as MemoryImage; - expect(memoryImage.bytes, equals(base64Decode(base64TestImage))); - }); - - testWidgets('uses base64 string as ValueKey', (WidgetTester tester) async { - await tester.pumpWidget( - const CachedMemoryImage(base64Image: base64TestImage), - ); - - final Image image = tester.widget(find.byType(Image)); - expect(image.key, equals(const ValueKey(base64TestImage))); - }); - }); -} diff --git a/packages/superdeck/test/helpers/syntax_highlighter_test.dart b/packages/superdeck/test/helpers/syntax_highlighter_test.dart index 83b7fe0b..f2466c53 100644 --- a/packages/superdeck/test/helpers/syntax_highlighter_test.dart +++ b/packages/superdeck/test/helpers/syntax_highlighter_test.dart @@ -1,5 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:superdeck/helpers/syntax_highlighter.dart'; +import 'package:superdeck/src/modules/common/helpers/syntax_highlighter.dart'; void main() { group('parseLineNumbers Tests', () { diff --git a/packages/superdeck/test/models/asset_model_test.dart b/packages/superdeck/test/models/asset_model_test.dart deleted file mode 100644 index ff0315bb..00000000 --- a/packages/superdeck/test/models/asset_model_test.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'dart:io'; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:superdeck/superdeck.dart'; - -void main() { - group('AssetFileType', () { - test('parse should return correct enum value', () { - expect(AssetFileType.parse('png'), AssetFileType.png); - expect(AssetFileType.parse('jpg'), AssetFileType.jpg); - expect(AssetFileType.parse('jpeg'), AssetFileType.jpeg); - expect(AssetFileType.parse('gif'), AssetFileType.gif); - expect(AssetFileType.parse('webp'), AssetFileType.webp); - }); - - test('parse should throw exception for invalid value', () { - expect(() => AssetFileType.parse('invalid'), throwsException); - }); - - test('tryParse should return correct enum value', () { - expect(AssetFileType.tryParse('png'), AssetFileType.png); - expect(AssetFileType.tryParse('jpg'), AssetFileType.jpg); - expect(AssetFileType.tryParse('jpeg'), AssetFileType.jpeg); - expect(AssetFileType.tryParse('gif'), AssetFileType.gif); - expect(AssetFileType.tryParse('webp'), AssetFileType.webp); - }); - - test('tryParse should return null for invalid value', () { - expect(AssetFileType.tryParse('invalid'), isNull); - }); - - test('isPng should return true for png', () { - expect(AssetFileType.png.isPng(), isTrue); - }); - - test('isJpg should return true for jpg and jpeg', () { - expect(AssetFileType.jpg.isJpg(), isTrue); - expect(AssetFileType.jpeg.isJpg(), isTrue); - }); - - test('isGif should return true for gif', () { - expect(AssetFileType.gif.isGif(), isTrue); - }); - }); - - group('SlideAsset', () { - late SlideAsset asset; - late File file; - - setUp(() { - file = File('test.png'); - - asset = SlideAsset( - path: file.path, width: 800, height: 600, reference: 'test'); - }); - - test('extension should return correct file extension', () { - expect(asset.extension, '.png'); - }); - - test('isPortrait should return true when height is greater than width', () { - expect(asset.isPortrait, isFalse); - }); - - test('isLandscape should return true when width is greater than height', - () { - asset = SlideAsset( - path: file.path, width: 1200, height: 800, reference: 'test'); - expect(asset.isLandscape, isTrue); - }); - }); -} diff --git a/packages/superdeck/test/models/config_model_test.dart b/packages/superdeck/test/models/config_model_test.dart deleted file mode 100644 index 2531b0ce..00000000 --- a/packages/superdeck/test/models/config_model_test.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:superdeck/models/config_model.dart'; -import 'package:superdeck/superdeck.dart'; - -void main() { - group('Config', () { - test('fromMap should create Config instance from map', () { - final map = { - 'background': 'bg.jpg', - 'style': 'dark', - 'transition': {'type': 'fade_in_down_big', 'duration': 500}, - 'cache_remote_assets': true, - }; - - final config = Config.fromMap(map); - - expect(config.background, 'bg.jpg'); - expect(config.style, 'dark'); - expect(config.transition?.type, TransitionType.fadeInDownBig); - expect(config.transition?.duration, const Duration(milliseconds: 500)); - expect(config.cacheRemoteAssets, true); - }); - - test('fromJson should create Config instance from JSON string', () { - const json = - '{"background":"bg.jpg","style":"light","transition":{"type":"slide_in_right","duration":1000},"cache_remote_assets":false}'; - - final config = Config.fromJson(json); - - expect(config.background, 'bg.jpg'); - expect(config.style, 'light'); - expect(config.transition?.type, TransitionType.slideInRight); - expect(config.transition?.duration, const Duration(seconds: 1)); - expect(config.cacheRemoteAssets, false); - }); - }); -} diff --git a/packages/superdeck/test/models/deck_reference_model_test.dart b/packages/superdeck/test/models/deck_reference_model_test.dart deleted file mode 100644 index af8d8b9b..00000000 --- a/packages/superdeck/test/models/deck_reference_model_test.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'dart:io'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:superdeck/models/reference_model.dart'; - -void main() { - group('DeckReference', () { - /// Create a test for performance parsing of fromJson - test('parses a json deck reference in less than 100ms', () async { - final fixture = File('test/fixtures/deck_reference.json'); - final contents = await fixture.readAsString(); - - final stopwatch = Stopwatch()..start(); - SuperDeckReference.fromJson(contents); - stopwatch.stop(); - - // Use Isolate.compute to run the parsing in a separate isolate - // and measure the time it takes to parse the json - - final stopwatch2 = Stopwatch()..start(); - - await compute(SuperDeckReference.fromJson, contents); - - stopwatch2.stop(); - - // stopwatch 2 should be faster than stopwatch - expect(stopwatch2.elapsedMilliseconds, - lessThan(stopwatch.elapsedMilliseconds)); - }); - - test('parses a json deck reference', () async { - final fixture = File('test/fixtures/deck_reference.json'); - final contents = await fixture.readAsString(); - final deckReference = SuperDeckReference.fromJson(contents); - - expect(deckReference, isNotNull); - expect(deckReference.config, isNotNull); - expect(deckReference.slides, isNotEmpty); - expect(deckReference.assets, isNotEmpty); - }); - }); -} diff --git a/packages/superdeck/test/models/syntax_tag_test.dart b/packages/superdeck/test/models/syntax_tag_test.dart deleted file mode 100644 index 4f6bb2cc..00000000 --- a/packages/superdeck/test/models/syntax_tag_test.dart +++ /dev/null @@ -1,155 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:superdeck/helpers/section_tag.dart'; - -void main() { - group('Tag Detection Tests', () { - test('Detects left and right tags with content', () { - const input = ''' -::left:: - -## Content One -Description of content - -::right:: - -#### Content Two - -- First bullet point -- Second bullet point -- Third bullet point -'''; - final expected = { - SectionTag.left: ''' - -## Content One -Description of content - -''', - SectionTag.right: ''' - -#### Content Two - -- First bullet point -- Second bullet point -- Third bullet point -''' - }; - expect( - parseContentSections(input), - expected, - ); - }); - - test('Handles input without tags correctly', () { - const input = ''' -## Content One -Description of content -'''; - final expected = { - SectionTag.first: ''' -## Content One -Description of content -''' - }; - expect(parseContentSections(input), expected); - }); - - test('Detects right tag only with content', () { - const input = ''' -## Content One -Description of content - -::right:: - -#### Content Two - -- First bullet point -- Second bullet point -- Third bullet point -'''; - final expected = { - SectionTag.first: ''' -## Content One -Description of content - -''', - SectionTag.right: ''' - -#### Content Two - -- First bullet point -- Second bullet point -- Third bullet point -''' - }; - expect(parseContentSections(input), expected); - }); - - test('Detects misaligned left tag with content', () { - const input = ''' -## Content One -Description of content - -::left:: - -#### Content Two - -- First bullet point -- Second bullet point -- Third bullet point -'''; - final expected = { - SectionTag.first: ''' -## Content One -Description of content - -''', - SectionTag.left: ''' - -#### Content Two - -- First bullet point -- Second bullet point -- Third bullet point -''' - }; - expect(parseContentSections(input), expected); - }); - - test('Throws exception for duplicate tags', () { - const input = ''' -::left:: - -## Content One -Description of content - -::left:: - -#### Content Two - -- First bullet point -- Second bullet point -- Third bullet point -'''; - expect( - () => parseContentSections( - input, - ), - throwsException); - }); - - test('Throws exception for empty content between same tags', () { - const input = ''' -::left:: -::left:: - -Description of content -'''; - expect( - () => parseContentSections( - input, - ), - throwsException); - }); - }); -} diff --git a/packages/superdeck/test/options_model_test.dart b/packages/superdeck/test/options_model_test.dart deleted file mode 100644 index 003b0579..00000000 --- a/packages/superdeck/test/options_model_test.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:superdeck/superdeck.dart'; - -void main() { - group('ContentOptions', () { - test('merge returns original instance when other is null', () { - const options = ContentOptions(); - expect(options.merge(null), same(options)); - }); - - test('merge returns new instance with merged values', () { - const options1 = - ContentOptions(flex: 2, alignment: ContentAlignment.topLeft); - const options2 = ContentOptions(alignment: ContentAlignment.bottomRight); - final merged = options1.merge(options2); - expect(merged.flex, options1.flex); - expect(merged.alignment, options2.alignment); - }); - }); - - group('ImageOptions', () { - test('constructor sets properties correctly', () { - const options = ImageOptions( - src: 'image.png', - fit: ImageFit.cover, - flex: 3, - position: LayoutPosition.left, - ); - expect(options.src, 'image.png'); - expect(options.fit, ImageFit.cover); - expect(options.flex, 3); - expect(options.position, LayoutPosition.left); - }); - }); - - group('WidgetOptions', () { - test('constructor sets properties correctly', () { - const options = WidgetOptions( - name: 'MyWidget', - args: {'key': 'value'}, - flex: 2, - position: LayoutPosition.top, - ); - expect(options.name, 'MyWidget'); - expect(options.args, {'key': 'value'}); - expect(options.flex, 2); - expect(options.position, LayoutPosition.top); - }); - }); - - group('TransitionOptions', () { - test('merge returns original instance when other is null', () { - const options = TransitionOptions(type: TransitionType.fadeIn); - expect(options.merge(null), same(options)); - }); - - test('merge returns new instance with merged values', () { - const options1 = TransitionOptions( - type: TransitionType.fadeIn, - duration: Duration(seconds: 1), - delay: Duration(milliseconds: 500), - curve: CurveType.easeIn, - ); - const options2 = TransitionOptions( - type: TransitionType.fadeOut, - duration: Duration(seconds: 2), - ); - final merged = options1.merge(options2); - expect(merged.type, options2.type); - expect(merged.duration, options2.duration); - expect(merged.delay, options1.delay); - expect(merged.curve, options1.curve); - }); - - test('totalAnimationDuration returns sum of duration and delay', () { - const options = TransitionOptions( - type: TransitionType.fadeIn, - duration: Duration(seconds: 1), - delay: Duration(milliseconds: 500), - ); - expect( - options.totalAnimationDuration, const Duration(milliseconds: 1500)); - }); - }); -} diff --git a/packages/superdeck/test/templates/image_template_test.dart b/packages/superdeck/test/templates/image_template_test.dart deleted file mode 100644 index b95220a5..00000000 --- a/packages/superdeck/test/templates/image_template_test.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:superdeck/components/molecules/slide_content.dart'; -import 'package:superdeck/superdeck.dart'; -import 'package:superdeck/templates/templates.dart'; - -import '../test_helpers.dart'; - -void main() { - group('ImageTemplate', () { - final slideConfig = ImageSlide( - key: 'image-slide', - content: '', - contentOptions: const ContentOptions(), - options: const ImageOptions( - src: 'https://example.com/image.jpg', - ), - ); - - testWidgets('builds image template with correct layout', - (WidgetTester tester) async { - await tester.pumpSlide(slideConfig); - - expect(find.byType(SlideContent), findsOneWidget); - expect(find.byType(ImageTemplate), findsOneWidget); - - final finder = find.byType(ImageTemplate); - final template = tester.widget(finder); - - expect(template.config, slideConfig); - }); - }); -} diff --git a/packages/superdeck/test/templates/simple_template_test.dart b/packages/superdeck/test/templates/simple_template_test.dart deleted file mode 100644 index feede34b..00000000 --- a/packages/superdeck/test/templates/simple_template_test.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:superdeck/models/slide_model.dart'; -import 'package:superdeck/templates/templates.dart'; - -import '../test_helpers.dart'; - -void main() { - group('SimpleTemplate', () { - const rawMarkdown = ''' -# Hello -'''; - final slideConfig = SimpleSlide(content: rawMarkdown, key: 'simple-slide'); - testWidgets('builds content', (WidgetTester tester) async { - await tester.pumpSlide(slideConfig); - final finder = find.byType(SimpleTemplate); - expect(finder, findsOneWidget); - // Check if template model equals to slide model - final template = tester.widget(finder); - expect(template.config, slideConfig); - }); - }); -} diff --git a/packages/superdeck/test/templates/slide_template_test.dart b/packages/superdeck/test/templates/slide_template_test.dart new file mode 100644 index 00000000..6add2efb --- /dev/null +++ b/packages/superdeck/test/templates/slide_template_test.dart @@ -0,0 +1,27 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mix/mix.dart'; +import 'package:superdeck/src/components/atoms/slide_view.dart'; +import 'package:superdeck/src/modules/deck/slide_configuration.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +import '../test_helpers.dart'; + +void main() { + group('SimpleTemplate', () { + const slide = Slide(key: 'simple-slide'); + final config = SlideConfiguration( + slide: slide, + slideIndex: 0, + style: Style(), + thumbnailFile: '', + ); + testWidgets('builds content', (WidgetTester tester) async { + await tester.pumpSlide(config); + final finder = find.byType(SlideView); + expect(finder, findsOneWidget); + // Check if template model equals to slide model + final template = tester.widget(finder); + expect(template.slide, config); + }); + }); +} diff --git a/packages/superdeck/test/test_helpers.dart b/packages/superdeck/test/test_helpers.dart index 16a51574..248a7422 100644 --- a/packages/superdeck/test/test_helpers.dart +++ b/packages/superdeck/test/test_helpers.dart @@ -1,37 +1,28 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:superdeck/components/atoms/slide_view.dart'; -import 'package:superdeck/providers/assets_provider.dart'; -import 'package:superdeck/providers/examples_provider.dart'; -import 'package:superdeck/providers/snapshot_provider.dart'; -import 'package:superdeck/providers/style_provider.dart'; -import 'package:superdeck/superdeck.dart'; +import 'package:superdeck/src/components/atoms/slide_view.dart'; +import 'package:superdeck/src/modules/common/helpers/provider.dart'; +import 'package:superdeck/src/modules/common/styles/style.dart'; +import 'package:superdeck/src/modules/deck/deck_options.dart'; +import 'package:superdeck/src/modules/deck/slide_configuration.dart'; +import 'package:superdeck_core/superdeck_core.dart'; extension WidgetTesterX on WidgetTester { Future pumpWithScaffold(Widget widget) async { await pumpWidget(MaterialApp(home: Scaffold(body: widget))); } - Future pumpSlide( - T slide, { + Future pumpSlide( + SlideConfiguration slide, { bool isSnapshot = false, - Style style = const Style.empty(), - Map examples = const {}, - List assets = const [], + DeckStyle? style, + Map widgets = const {}, + List assets = const [], }) async { return pumpWithScaffold( - SnapshotProvider( - isCapturing: isSnapshot, - child: StyleProvider( - baseStyle: style, - child: AssetsProvider( - assets: assets, - child: ExamplesProvider( - examples: examples, - child: SlideView(slide), - ), - ), - ), + InheritedData( + data: slide, + child: SlideView(slide), ), ); } diff --git a/packages/superdeck_cli/analysis_options.yaml b/packages/superdeck_cli/analysis_options.yaml index dee8927a..8d1275e0 100644 --- a/packages/superdeck_cli/analysis_options.yaml +++ b/packages/superdeck_cli/analysis_options.yaml @@ -1,30 +1,3 @@ -# This file configures the static analysis results for your project (errors, -# warnings, and lints). -# -# This enables the 'recommended' set of lints from `package:lints`. -# This set helps identify many issues that may lead to problems when running -# or consuming Dart code, and enforces writing Dart using a single, idiomatic -# style and format. -# -# If you want a smaller set of lints you can change this to specify -# 'package:lints/core.yaml'. These are just the most critical lints -# (the recommended set includes the core lints). -# The core lints are also what is used by pub.dev for scoring packages. - -include: package:lints/recommended.yaml - -# Uncomment the following section to specify additional rules. - -# linter: -# rules: -# - camel_case_types - -# analyzer: -# exclude: -# - path/to/excluded/files/** - -# For more information about the core and recommended set of lints, see -# https://dart.dev/go/core-lints - -# For additional information about configuring this file, see -# https://dart.dev/guides/language/analysis-options +include: + - package:lints/recommended.yaml + - ../../shared_analysis_options.yaml diff --git a/packages/superdeck_cli/bin/build.dart b/packages/superdeck_cli/bin/build.dart deleted file mode 100644 index 93e4be04..00000000 --- a/packages/superdeck_cli/bin/build.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'dart:io'; - -import 'package:args/command_runner.dart'; -import 'package:superdeck_cli/superdeck_cli.dart'; - -void main(List arguments) async { - try { - await SlidesLoader().generate(); - } on UsageException catch (e) { - print(e); - exit(64); - } -} diff --git a/packages/superdeck_cli/bin/export.dart b/packages/superdeck_cli/bin/export.dart new file mode 100644 index 00000000..7c4673d0 --- /dev/null +++ b/packages/superdeck_cli/bin/export.dart @@ -0,0 +1,211 @@ +// bin/flatten_project.dart + +import 'dart:io'; + +import 'package:path/path.dart' as p; + +void main(List args) async { + // Configuration + + // Define directories to include (e.g., 'lib', 'bin') + final includeDirectories = [ + 'lib', + + // Add more directories as needed + ]; + + // Define file extensions to include (e.g., '.dart', '.md') + final includeExtensions = [ + '.dart', + + // Add more extensions as needed + ]; + + // Define file extensions to exclude (e.g., '.log', '.json') + final excludeExtensions = [ + '.g.dart', + '.mapper.dart', + + // Add more extensions as needed + ]; + + // Define output directory and file + final currentDir = Directory.current; + final outputDir = Directory(p.join(currentDir.path, 'flattened')); + final timestamp = DateTime.now() + .toIso8601String() + .replaceAll(':', '-') + .replaceAll('.', '-'); + const separator = '---'; + final projectName = p.basename(currentDir.path); + final outputFilePath = p.join(outputDir.path, '$projectName-$timestamp.txt'); + final outputFile = File(outputFilePath); + + // Create output directory if it doesn't exist + if (!await outputDir.exists()) { + await outputDir.create(recursive: true); + print('Created output directory: ${outputDir.path}'); + } + + // Helper function to determine if a file should be included + bool shouldInclude(String relativePath) { + // Check if the file is within any of the included directories + bool isInIncludedDir = includeDirectories.any((dir) { + // Normalize directory path + final normalizedDir = p.normalize(dir); + + return relativePath.startsWith('$normalizedDir/'); + }); + + if (!isInIncludedDir) return false; + + // Check if the file has one of the included extensions + String extension = p.extension(relativePath).toLowerCase(); + + return includeExtensions.contains(extension) && + !excludeExtensions.contains(extension); + } + + // Helper function to check if a file is a text file + bool isTextFile(File file) { + try { + final bytes = file.readAsBytesSync(); + // Simple heuristic: if the file contains null bytes, it's likely binary + if (bytes.contains(0)) { + return false; + } + + return true; + } catch (e) { + return false; + } + } + + // Helper function to estimate tokens more accurately + int estimateTokens(File file) { + try { + final content = file.readAsStringSync(); + final matches = RegExp(r'\w+|[^\w\s]').allMatches(content); + int tokenCount = 0; + + for (final match in matches) { + String token = match.group(0)!; + + // Heuristic: Assume longer tokens might split into multiple tokens + if (token.length > 6) { + tokenCount += (token.length / 3).ceil(); // Approximate split + } else { + tokenCount += 1; + } + } + + return tokenCount; + } catch (e) { + // In case of read error, return 0 + return 0; + } + } + + // Start writing to the output file + final sink = outputFile.openWrite(); + + // Write header + sink.writeln('# Project: $projectName'); + sink.writeln('# Generated: ${DateTime.now()}'); + sink.writeln(''); + sink.writeln( + '# Complete Repository Structure:\n# (showing all directories and files with token counts)\n', + ); + + // Maps to store directory token counts and file lists + final directoryTokenCounts = {}; + final directoryFiles = >{}; + + // Traverse files in included directories + for (var dir in includeDirectories) { + final Directory targetDir = Directory(p.join(currentDir.path, dir)); + if (await targetDir.exists()) { + await for (var entity + in targetDir.list(recursive: true, followLinks: false)) { + if (entity is File) { + final relativePath = p + .relative(entity.path, from: currentDir.path) + .replaceAll('\\', '/'); + if (shouldInclude(relativePath) && isTextFile(entity)) { + final dirPath = p.dirname(relativePath); + final tokens = estimateTokens(entity); + directoryTokenCounts.update( + dirPath, + (value) => value + tokens, + ifAbsent: () => tokens, + ); + directoryFiles.putIfAbsent(dirPath, () => []).add(entity); + } + } + } + } else { + print('Directory does not exist: ${targetDir.path}'); + } + } + + // List directories with token counts + final sortedDirectories = directoryTokenCounts.keys.toList()..sort(); + for (var dir in sortedDirectories) { + final depth = dir.split('/').length - 1; + final indent = ' ' * depth; + final displayPath = dir.isEmpty ? '.' : dir; + final totalTokens = directoryTokenCounts[dir]!; + sink.writeln('#$indent$displayPath/ (~$totalTokens tokens)'); + + // List files in the directory + final files = directoryFiles[dir]! + ..sort((a, b) => a.path.compareTo(b.path)); + for (var file in files) { + final fileName = p.basename(file.path); + final tokens = estimateTokens(file); + sink.writeln('#$indent └── $fileName (~$tokens tokens)'); + } + } + + sink.writeln('#'); + sink.writeln(separator); + await sink.flush(); + + // Gather all included files for content appending + final allIncludedFiles = []; + for (var dirFilesList in directoryFiles.values) { + allIncludedFiles.addAll(dirFilesList); + } + + // Sort files by path + allIncludedFiles.sort((a, b) => a.path.compareTo(b.path)); + + // Initialize progress indicators + final totalFiles = allIncludedFiles.length; + int processedFiles = 0; + + // Append each file's content with separators + for (var file in allIncludedFiles) { + processedFiles++; + stdout.write('\rProcessing file $processedFiles of $totalFiles'); + + final relativeFilePath = + p.relative(file.path, from: currentDir.path).replaceAll('\\', '/'); + sink.writeln(separator); + sink.writeln(relativeFilePath); + sink.writeln(separator); + try { + final content = await file.readAsString(); + sink.writeln(content); + sink.writeln(''); + } catch (e) { + sink.writeln('// Error reading file: $e'); + } + } + + // Ensure the final separator is added + sink.writeln(separator); + await sink.close(); + + print('\nProcessing complete! Output saved to: $outputFilePath'); +} diff --git a/packages/superdeck_cli/bin/main.dart b/packages/superdeck_cli/bin/main.dart new file mode 100644 index 00000000..1b4eafd7 --- /dev/null +++ b/packages/superdeck_cli/bin/main.dart @@ -0,0 +1,27 @@ +#!/usr/bin/env dart + +import 'dart:io'; + +import 'package:scope/scope.dart'; +import 'package:superdeck_cli/src/helpers/context.dart'; +import 'package:superdeck_cli/src/runner.dart'; + +Future main(List args) async { + final scope = Scope()..value(contextKey, SDCliContext()); + + await _flushThenExit( + await scope.run(() async => SuperDeckRunner().run((args))), + ); +} + +/// Flushes the stdout and stderr streams, then exits the program with the given +/// status code. +/// +/// This returns a Future that will never complete, since the program will have +/// exited already. This is useful to prevent Future chains from proceeding +/// after you've decided to exit. +Future _flushThenExit(int status) { + return Future.wait([stdout.close(), stderr.close()]).then( + (_) => exit(status), + ); +} diff --git a/packages/superdeck_cli/bin/watch.dart b/packages/superdeck_cli/bin/watch.dart deleted file mode 100644 index c8181632..00000000 --- a/packages/superdeck_cli/bin/watch.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'dart:io'; - -import 'package:args/command_runner.dart'; -import 'package:superdeck_cli/src/slides_loader.dart'; - -void main(List arguments) async { - try { - final loader = SlidesLoader(); - await loader.generate(); - await loader.watch(); - } on UsageException catch (e) { - print(e); - exit(64); - } -} diff --git a/packages/superdeck_cli/lib/src/commands/build_command.dart b/packages/superdeck_cli/lib/src/commands/build_command.dart new file mode 100644 index 00000000..a7887a42 --- /dev/null +++ b/packages/superdeck_cli/lib/src/commands/build_command.dart @@ -0,0 +1,112 @@ +import 'dart:io'; + +import 'package:args/command_runner.dart'; +import 'package:mason_logger/mason_logger.dart'; +import 'package:superdeck_cli/src/generator_pipeline.dart'; +import 'package:superdeck_cli/src/helpers/exceptions.dart'; +import 'package:superdeck_cli/src/helpers/extensions.dart'; +import 'package:superdeck_cli/src/helpers/logger.dart'; +import 'package:superdeck_cli/src/helpers/update_pubspec.dart'; +import 'package:superdeck_cli/src/tasks/dart_formatter_task.dart'; +import 'package:superdeck_cli/src/tasks/mermaid_task.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +class BuildCommand extends Command { + bool _isRunning = false; + + BuildCommand() { + argParser.addFlag( + 'watch', + abbr: 'w', + help: 'Watch for changes and build the deck', + ); + } + + Future _runPipeline(TaskPipeline pipeline) async { + // wait wihle _isRunning is true + while (_isRunning) { + await Future.delayed(const Duration(milliseconds: 100)); + } + _isRunning = true; + final progress = logger.progress('Generating slides...'); + try { + final slides = await pipeline.run(); + progress.complete('Generated ${slides.length} slides.'); + } on Exception catch (e, stackTrace) { + progress.fail(); + printException(e); + + logger.detail(stackTrace.toString()); + } finally { + _isRunning = false; + } + } + + @override + Future run() async { + final configFile = DeckConfiguration.defaultFile; + DeckConfiguration deckConfig; + + // Load the configuration file or use defaults if it doesn't exist. + if (!await configFile.exists()) { + logger.warn( + 'Configuration file not found. Using default configuration.', + ); + deckConfig = DeckConfiguration(); + } else { + logger.info('Loading configuration from ${configFile.path}'); + final yamlConfig = await YamlUtils.loadYamlFile(configFile); + deckConfig = DeckConfiguration.parse(yamlConfig); + logger.info('Configuration loaded successfully.'); + } + + final pipeline = TaskPipeline( + tasks: [MermaidConverterTask(), DartFormatterTask()], + configuration: deckConfig, + store: FileSystemDataStore(deckConfig), + ); + final watch = boolArg('watch'); + + // Update pubspec assets and log the update. + await _ensurePubspecAssets(deckConfig); + + // Run the pipeline initially. + await _runPipeline(pipeline); + + // If watch mode is enabled, subscribe to file modifications. + if (watch) { + logger.info( + 'Watch mode enabled. Listening for changes in slides markdown file.', + ); + + await for (final event in pipeline.store.configuration.slidesFile + .watch(events: FileSystemEvent.modify)) { + try { + logger.info('Detected modification in file: ${event.path}'); + await _runPipeline(pipeline); + } on Exception catch (e, stackTrace) { + logger.err('Error processing file: $e'); + logger.detail(stackTrace.toString()); + } + } + } + + return ExitCode.success.code; + } + + @override + String get description => 'Build the deck'; + + @override + String get name => 'build'; +} + +Future _ensurePubspecAssets(DeckConfiguration configuration) async { + final pubspecContents = await configuration.pubspecFile.readAsString(); + final updatedPubspecContents = + await updatePubspecAssets(configuration, pubspecContents); + if (updatedPubspecContents != pubspecContents) { + await configuration.pubspecFile.writeAsString(updatedPubspecContents); + logger.info('Pubspec assets updated.'); + } +} diff --git a/packages/superdeck_cli/lib/src/constants.dart b/packages/superdeck_cli/lib/src/constants.dart deleted file mode 100644 index b6b72157..00000000 --- a/packages/superdeck_cli/lib/src/constants.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'dart:io'; - -import 'package:path/path.dart' as p; - -final File kMarkdownFile = File('slides.md'); - -final Directory kAssetsDir = Directory(p.join('.superdeck')); - -final Directory kGeneratedAssetsDir = - Directory(p.join(kAssetsDir.path, 'generated')); -final File kProjectConfigFile = File('superdeck.yaml'); - -final File kReferenceFile = File(p.join(kAssetsDir.path, 'slides.json')); diff --git a/packages/superdeck_cli/lib/src/generator_pipeline.dart b/packages/superdeck_cli/lib/src/generator_pipeline.dart new file mode 100644 index 00000000..591ec739 --- /dev/null +++ b/packages/superdeck_cli/lib/src/generator_pipeline.dart @@ -0,0 +1,141 @@ +import 'dart:async'; + +import 'package:logging/logging.dart'; +import 'package:superdeck_cli/src/parsers/markdown_parser.dart'; +import 'package:superdeck_cli/src/parsers/parsers/section_parser.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +import 'parsers/parsers/comment_parser.dart'; + +/// Represents the context in which a slide is processed. +/// It holds the raw slide data and manages associated assets. +class TaskContext { + /// The index of the slide in the original list. + final int slideIndex; + final FileSystemDataStore dataStore; + + /// The raw slide being processed. + RawSlideMarkdown slide; + + TaskContext(this.slideIndex, this.slide, this.dataStore); +} + +/// Manages the execution of a series of [SlideTask] instances to process slides. +/// It handles loading markdown content, parsing slides, executing tasks, +/// cleaning up assets, and saving the processed slides. +class TaskPipeline { + /// List of tasks to execute for each slide. + final List tasks; + final DeckConfiguration configuration; + final FileSystemDataStore store; + const TaskPipeline({ + required this.tasks, + required this.configuration, + required this.store, + }); + + /// Processes an individual slide by executing all tasks sequentially. + Future _processSlide(TaskContext context) async { + for (var task in tasks) { + try { + await task.run(context); + } on Exception catch (e, stackTrace) { + // Wrap and rethrow the exception with additional context. + Error.throwWithStackTrace( + SDTaskException(task.name, e, context.slideIndex), + stackTrace, + ); + } + } + + return context; + } + + Future> run() async { + await store.initialize(); + + // Load raw markdown content from the repository. + + final markdownRaw = await store.readDeckMarkdown(); + + // Initialize the markdown parser with necessary extractors. + final markdownParser = MarkdownParser(); + + // Parse the raw markdown into individual raw slides. + final rawSlides = await markdownParser.parse(markdownRaw); + + // Prepare a list of futures to process each slide concurrently. + final futures = >[]; + + for (var i = 0; i < rawSlides.length; i++) { + futures.add(_processSlide(TaskContext(i, rawSlides[i], store))); + } + + // Await all slide processing tasks to complete. + final results = await Future.wait(futures); + + // Extract the processed slides from the results. + final finalizedSlides = results.map((result) => result.slide); + + final slides = finalizedSlides.map((slide) => Slide( + key: slide.key, + options: SlideOptions.parse(slide.frontmatter), + sections: SectionParser().parse(slide.content), + comments: CommentParser().parse(slide.content), + )); + + // Dispose of all tasks after processing. + for (var task in tasks) { + await task.dispose(); + } + + // Convert the iterable of slides to a list for saving. + + // Save the processed slides back to the repository. + await store.saveReferences( + DeckReference(slides: slides.toList(), config: configuration), + ); + + return slides; + } +} + +/// Custom exception for errors that occur during task execution. +class SDTaskException implements Exception { + /// Name of the task where the error occurred. + final String taskName; + + /// The original exception that was thrown. + final Exception originalException; + + /// Index of the slide being processed when the error occurred. + final int slideIndex; + + const SDTaskException(this.taskName, this.originalException, this.slideIndex); + + @override + String toString() { + return 'Error in task "$taskName" at slide index $slideIndex: $originalException'; + } +} + +/// Abstract class representing a generic task in the slide processing pipeline. +/// Each concrete task should implement the [run] method to perform its specific operation. +abstract class Task { + /// Name of the task, used for logging and identification. + final String name; + + /// Logger instance for the task. + late final Logger logger = Logger('Task: $name'); + + Task(this.name); + + /// Executes the task using the provided [TaskContext]. + FutureOr run(TaskContext context); + + /// Disposes of any resources held by the task. + /// Override if the task holds resources that need explicit disposal. + FutureOr dispose() { + return Future.value(); + } +} diff --git a/packages/superdeck_cli/lib/src/helpers/context.dart b/packages/superdeck_cli/lib/src/helpers/context.dart new file mode 100644 index 00000000..70398ba0 --- /dev/null +++ b/packages/superdeck_cli/lib/src/helpers/context.dart @@ -0,0 +1,9 @@ +import 'package:scope/scope.dart'; + +final contextKey = ScopeKey(); + +SDCliContext get ctx => use(contextKey, withDefault: () => SDCliContext()); + +class SDCliContext { + const SDCliContext(); +} diff --git a/packages/superdeck_cli/lib/src/helpers/dart_process.dart b/packages/superdeck_cli/lib/src/helpers/dart_process.dart new file mode 100644 index 00000000..12c74daa --- /dev/null +++ b/packages/superdeck_cli/lib/src/helpers/dart_process.dart @@ -0,0 +1,74 @@ +import 'dart:io'; + +import 'package:path/path.dart' as p; +import 'package:source_span/source_span.dart'; +import 'package:superdeck_cli/src/helpers/exceptions.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +class DartProcess { + static Future _run(List args) { + return Process.run('dart', args); + } + + static Future format(String code) async { + final hash = generateValueHash(code); + // create a temp file with the code + final tempFile = File( + p.join( + Directory.systemTemp.path, + 'temp_${DateTime.now().microsecondsSinceEpoch}_${hash}.dart', + ), + ); + try { + await tempFile.create(recursive: true); + + await tempFile.writeAsString(code); + + final result = await _run(['format', '--fix', tempFile.path]); + + if (result.exitCode != 0) { + throw _handleFormattingError(result.stderr as String, code); + } + + return await tempFile.readAsString(); + } finally { + if (await tempFile.exists()) { + await tempFile.delete(); + } + } + } +} + +DeckFormatException _handleFormattingError(String stderr, String source) { + final match = + RegExp(r'line (\d+), column (\d+) of .*: (.+)').firstMatch(stderr); + + if (match != null) { + final line = int.parse(match.group(1)!); + final column = int.parse(match.group(2)!); + final message = match.group(3)!; + + // Create a SourceFile from the source code + final sourceFile = SourceFile.fromString(source); + + // Get the location using line and column (converting to 0-based indices) + final location = sourceFile.location( + sourceFile.getOffset(line - 1, column - 1), + ); + + // Create a point span at the error location + final span = location.pointSpan(); + + return DeckFormatException( + 'Dart code formatting error: $message', + span, + source, + ); + } + + return DeckFormatException( + 'Error formatting dart code: $stderr', + null, + source, + ); +} diff --git a/packages/superdeck_cli/lib/src/helpers/exceptions.dart b/packages/superdeck_cli/lib/src/helpers/exceptions.dart new file mode 100644 index 00000000..c8e66475 --- /dev/null +++ b/packages/superdeck_cli/lib/src/helpers/exceptions.dart @@ -0,0 +1,38 @@ +// ignore_for_file: avoid-duplicate-cascades + +import 'package:source_span/source_span.dart'; +import 'package:superdeck_cli/src/helpers/logger.dart'; + +class DeckTaskException implements Exception { + final int slideIndex; + final String taskName; + + final Exception exception; + + const DeckTaskException(this.taskName, this.exception, this.slideIndex); + + String get message { + return 'Error running task on slide $slideIndex'; + } + + @override + String toString() => message; +} + +class DeckFormatException extends SourceSpanFormatException { + DeckFormatException(super.message, super.span, [super.source]); +} + +void printException(Exception e) { + if (e is DeckTaskException) { + logger + ..err('slide: ${e.slideIndex}') + ..err('Task error: ${e.taskName}'); + + printException(e.exception); + } else if (e is DeckFormatException) { + logger.formatError(e); + } else { + logger.err(e.toString()); + } +} diff --git a/packages/superdeck_cli/lib/src/helpers/extensions.dart b/packages/superdeck_cli/lib/src/helpers/extensions.dart index 6d3ac3be..d4061c0d 100644 --- a/packages/superdeck_cli/lib/src/helpers/extensions.dart +++ b/packages/superdeck_cli/lib/src/helpers/extensions.dart @@ -1,5 +1,7 @@ import 'dart:io'; +import 'package:args/command_runner.dart'; + extension FileExt on File { Future ensureWrite(String content) async { if (!await exists()) { @@ -23,3 +25,60 @@ extension DirectoryExt on Directory { } } } + +// extension SlideX on Slide { +// String toMarkdown() { +// final buffer = StringBuffer(); + +// final options = this.options?.toMap(); + +// buffer.writeln('---'); +// if (options != null && options.isNotEmpty) { +// buffer.write(YamlWriter().write(options)); +// } +// buffer.writeln('---'); + +// buffer.writeln(markdown); + +// return buffer.toString(); +// } +// } + +extension CommandExtension on Command { + /// Checks if the command-line option named [name] was parsed, + /// safely handling null argResults. + bool wasParsed(String name) => argResults?.wasParsed(name) ?? false; + + /// Gets the parsed command-line option named [name] as `bool`. + bool boolArg(String name) => argResults?[name] == true; + + /// Gets the parsed command-line option named [name] as `String`, + /// handles null and empty strings without relying on a 'null' literal. + String? stringArg(String name) { + final arg = argResults?[name]; + if (arg is! String || arg.isEmpty || arg == 'null') { + return null; + } + + return arg; + } + + /// Gets the parsed command-line option named [name] as `int`, + /// converting a parsed string if available. + int? intArg(String name) { + final value = stringArg(name); + + return (value == null) ? null : int.tryParse(value); + } + + /// Gets the parsed command-line option named [name] as `List`, + /// and ensures a non-null, typed list is returned. + List stringsArg(String name) { + final arg = argResults?[name]; + if (arg is List) { + return arg.whereType().toList(); + } + + return []; + } +} diff --git a/packages/superdeck_cli/lib/src/helpers/logger.dart b/packages/superdeck_cli/lib/src/helpers/logger.dart new file mode 100644 index 00000000..463e7f3a --- /dev/null +++ b/packages/superdeck_cli/lib/src/helpers/logger.dart @@ -0,0 +1,77 @@ +import 'package:mason_logger/mason_logger.dart'; +import 'package:source_span/source_span.dart'; +import 'package:superdeck_cli/src/helpers/exceptions.dart'; + +final logger = Logger( + // Optionally, specify a custom `LogTheme` to override log styles. + theme: LogTheme(), + // Optionally, specify a log level (defaults to Level.info). + level: Level.info, +); + +extension LoggerX on Logger { + void formatError(DeckFormatException exception) { + final message = exception.message; + final sourceSpan = exception.source as SourceSpan; + final source = sourceSpan.text; + final start = sourceSpan.start; + + final arrow = _createArrow(start.column); + + final splitLines = source.split('\n'); + + // Get the longest line + final longestLine = splitLines.fold(0, (prev, element) { + return element.length > prev ? element.length : prev; + }); + + String padline(String line, [int? index]) { + final pageNumber = index != null ? '${index + 1}' : ' '; + + return ' $pageNumber | ${line.padRight(longestLine + 2)}'; + } + + // Print the error message with the source code + newLine(); + err('Formatting Error:'); + newLine(); + info('$message on line ${start.line + 1}, column ${start.column + 1}'); + newLine(); + + final exceptionLineNumber = start.line; + + // Calculate only 4 lines before and after the error line + final startLine = (exceptionLineNumber - 5).clamp(0, splitLines.length); + final endLine = (exceptionLineNumber + 5).clamp(0, splitLines.length); + + for (int i = startLine; i <= endLine; i++) { + final currentLineContent = splitLines[i]; + final isErrorLine = i == exceptionLineNumber; + + if (isErrorLine) { + info(padline(currentLineContent, i), style: _highlightLine); + info(padline(arrow), style: _highlightLine); + } else { + _formatCodeBlock(padline(currentLineContent, i)); + } + } + } + + void _formatCodeBlock(String message) { + info(message, style: _formatErrorStyle); + } + + void newLine() => info(''); +} + +String _createArrow(int column) { + return '${' ' * column}^'; +} + +String? _formatErrorStyle(String? m) { + return backgroundDefault.wrap(styleBold.wrap(white.wrap(m))); +} + +String? _highlightLine(String? m) { + return backgroundDefault.wrap(styleBold.wrap(yellow.wrap(m))); +} diff --git a/packages/superdeck_cli/lib/src/helpers/raw_models.dart b/packages/superdeck_cli/lib/src/helpers/raw_models.dart deleted file mode 100644 index 47bf53ed..00000000 --- a/packages/superdeck_cli/lib/src/helpers/raw_models.dart +++ /dev/null @@ -1,134 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:superdeck_cli/src/helpers/short_hash_id.dart'; -import 'package:superdeck_cli/src/helpers/yaml_to_map.dart'; - -typedef Json = Map; - -class RawReference { - final List assets; - final List slides; - final Json config; - - const RawReference({ - required this.assets, - required this.slides, - required this.config, - }); - - static RawReference loadFile(File file) { - try { - return fromJson(file.readAsStringSync()); - } catch (e) { - return const RawReference(assets: [], slides: [], config: {}); - } - } - - static RawReference fromMap(Json map) { - return RawReference( - assets: (map['assets'] as List) - .map((e) => RawAsset.fromMap(e)) - .toList(), - slides: (map['slides'] as List) - .map((e) => RawSlide(e as Json)) - .toList(), - config: map['config'] as Json, - ); - } - - static RawReference fromJson(String json) { - return fromMap(jsonDecode(json) as Json); - } -} - -class RawConfig { - final Json _map; - - const RawConfig(this._map); - - static RawConfig loadFile(File file) { - try { - final yamlString = file.readAsStringSync(); - return RawConfig(converYamlToMap(yamlString)); - } catch (e) { - return const RawConfig({}); - } - } - - Json toMap() => _map; -} - -class RawSlide { - final String key; - final Json _map; - - RawSlide(this._map) : key = shortHashId(_map.toString()); - - String get content => (_map['content'] ?? '') as String; - - static RawSlide fromYaml(String yamlString) { - final (:content, :options) = parseSlideMarkdown(yamlString); - return RawSlide({ - ...options, - - // Change from content on the frontMatter to content options in on the map - if (options['content'] != null) 'content_options': options['content'], - 'content': content, - }); - } - - RawSlide copyWith({ - String? content, - }) { - return RawSlide( - { - ..._map, - 'content': content ?? _map['content'], - }, - ); - } - - Json toMap() => { - ..._map, - 'key': key, - }; - - static RawSlide fromJson(String json) { - return RawSlide(jsonDecode(json)); - } -} - -class RawAsset { - final String path; - final String? reference; - final int width; - final int height; - - const RawAsset({ - required this.path, - required this.width, - required this.height, - required this.reference, - }); - - static RawAsset fromMap(Json json) { - return RawAsset( - path: json['path'] as String, - reference: json['reference'] as String?, - width: json['width'] as int, - height: json['height'] as int, - ); - } - - static RawAsset fromJson(String json) { - return fromMap(jsonDecode(json) as Json); - } - - Json toMap() => { - 'path': path, - 'width': width, - 'height': height, - if (reference != null) 'reference': reference, - }; -} diff --git a/packages/superdeck_cli/lib/src/helpers/slide_parser.dart b/packages/superdeck_cli/lib/src/helpers/slide_parser.dart deleted file mode 100644 index 359129a4..00000000 --- a/packages/superdeck_cli/lib/src/helpers/slide_parser.dart +++ /dev/null @@ -1,63 +0,0 @@ -// lib/slide_parser.dart - -import 'package:superdeck_cli/src/helpers/raw_models.dart'; - -class SlideParser { - final String contents; - SlideParser(this.contents); - - List _splitSlides(String content) { - final lines = content.split('\n'); - final slides = []; - final buffer = StringBuffer(); - bool inSlide = false; - - var isCodeBlock = false; - - for (var line in lines) { - if (line.trim().startsWith('```')) { - isCodeBlock = !isCodeBlock; - } - if (line.trim() == '---' && !isCodeBlock) { - if (buffer.isNotEmpty) { - if (inSlide) { - slides.add(buffer.toString().trim()); - inSlide = false; - buffer.clear(); - } else { - inSlide = true; - } - } - buffer.writeln(line); - } else { - buffer.writeln(line); - } - } - - if (buffer.isNotEmpty) { - slides.add(buffer.toString()); - } - - return slides; - } - - List run() { - final markdownContents = _splitSlides(contents.trim()); - return markdownContents.map(RawSlide.fromYaml).toList(); - } -} - -Map deepMerge( - Map source, Map updates) { - final result = Map.from(source); - - updates.forEach((key, value) { - if (value is Map && result[key] is Map) { - result[key] = deepMerge(result[key] as Map, value); - } else { - result[key] = value; - } - }); - - return result; -} diff --git a/packages/superdeck_cli/lib/src/helpers/update_pubspec.dart b/packages/superdeck_cli/lib/src/helpers/update_pubspec.dart index f0da6411..5a4358e2 100644 --- a/packages/superdeck_cli/lib/src/helpers/update_pubspec.dart +++ b/packages/superdeck_cli/lib/src/helpers/update_pubspec.dart @@ -1,52 +1,59 @@ -import 'dart:io'; - +import 'package:superdeck_core/superdeck_core.dart'; import 'package:yaml/yaml.dart'; import 'package:yaml_writer/yaml_writer.dart'; -/// Path/filename comment -const String pubspecPath = 'pubspec.yaml'; - -void main() { - updatePubspecAssets(); -} - -/// Updates the pubspec.yaml to include .superdeck/ and .superdeck/generated/ assets. -void updatePubspecAssets() { - // Read the pubspec.yaml file - final File file = File(pubspecPath); - if (!file.existsSync()) { - print('pubspec.yaml not found.'); - return; +/// Updates the 'assets' section of a pubspec.yaml file with superdeck paths. +/// +/// This function takes a [yamlContent] string representing the contents of a +/// pubspec.yaml file. It parses the YAML, adds the '.superdeck/' and +/// '.superdeck/generated/' paths to the 'assets' section under the 'flutter' +/// key if they don't already exist, and returns the updated YAML as a string. +/// +/// Returns the updated pubspec YAML content as a string. +String updatePubspecAssets( + DeckConfiguration configuration, + String pubspecContents, +) { + // Parse the YAML content into a map + final parsedYaml = loadYaml(pubspecContents); + + // Get the 'flutter' section from the parsed YAML, or an empty map if it doesn't exist + final flutterSection = + // ignore: avoid-dynamic + {...(parsedYaml['flutter'] ?? {}) as Map}.cast(); + + // Get the 'assets' list from the 'flutter' section, or an empty list if it doesn't exist + final assets = flutterSection['assets']?.toList() ?? []; + + bool needsUpdate = false; + + final superDeckDirPath = configuration.superdeckDir.path; + + // Add the '.superdeck/' path to the assets list if it's not already present + if (!assets.contains('${superDeckDirPath}/')) { + assets.add('${superDeckDirPath}/'); + needsUpdate = true; } - final String content = file.readAsStringSync(); - final YamlMap yamlContent = loadYaml(content); - - // Ensure the flutter: key exists - Map flutterSection = yamlContent['flutter'] ?? {}; + final assetsDirPath = configuration.assetsDir.path; - // Get the existing assets or create a new list if it doesn't exist - List assets = flutterSection['assets']?.toList() ?? []; - - // Add the new asset paths if they don't exist - if (!assets.contains('.superdeck/')) { - assets.add('.superdeck/'); + // Add the '.superdeck/generated/' path to the assets list if it's not already present + if (!assets.contains('${assetsDirPath}/')) { + assets.add('${assetsDirPath}/'); + needsUpdate = true; } - if (!assets.contains('.superdeck/generated/')) { - assets.add('.superdeck/generated/'); + + if (!needsUpdate) { + return pubspecContents; } - // Update the flutter section with the new assets list + // Update the 'assets' key in the 'flutter' section with the modified assets list flutterSection['assets'] = assets; - // Convert the updated map back to YAML - final updatedYaml = Map.from(yamlContent) + // Create a new map from the parsed YAML and update the 'flutter' key with the modified section + final updatedYaml = Map.from(parsedYaml) ..['flutter'] = flutterSection; - final String updatedContent = YamlWriter().write(updatedYaml); - - // Write the updated content back to the pubspec.yaml file - file.writeAsStringSync(updatedContent); - - print('pubspec.yaml updated successfully.'); + // Convert the updated YAML map back to a string and return it + return YamlWriter(allowUnquotedStrings: true).write(updatedYaml); } diff --git a/packages/superdeck_cli/lib/src/helpers/yaml_to_map.dart b/packages/superdeck_cli/lib/src/helpers/yaml_to_map.dart deleted file mode 100644 index b9f76d89..00000000 --- a/packages/superdeck_cli/lib/src/helpers/yaml_to_map.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'dart:convert'; - -import 'package:superdeck_cli/src/helpers/raw_models.dart'; -import 'package:yaml/yaml.dart'; - -final _frontMatterRegex = RegExp(r'---([\s\S]*?)---'); - -Json converYamlToMap(String yamlString) { - final yamlMap = loadYaml(yamlString) as YamlMap? ?? YamlMap(); - - final yaml = jsonEncode(yamlMap); - - return jsonDecode(yaml); -} - -({String content, Json options}) parseSlideMarkdown(String slideContents) { - final frontMatter = - _frontMatterRegex.firstMatch(slideContents)?.group(1) ?? ''; - final options = converYamlToMap(frontMatter); - - final content = slideContents - .substring(_frontMatterRegex.matchAsPrefix(slideContents)?.end ?? 0) - .trim(); - - return ( - content: content, - options: options, - ); -} diff --git a/packages/superdeck_cli/lib/src/parsers/markdown_parser.dart b/packages/superdeck_cli/lib/src/parsers/markdown_parser.dart new file mode 100644 index 00000000..68e9b3a9 --- /dev/null +++ b/packages/superdeck_cli/lib/src/parsers/markdown_parser.dart @@ -0,0 +1,91 @@ +import 'dart:convert'; + +import 'package:superdeck_cli/src/parsers/parsers/front_matter_parser.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +class RawSlideMarkdown { + final String key; + String content; + final Map frontmatter; + + RawSlideMarkdown({ + required this.key, + required this.content, + required this.frontmatter, + }); +} + +/// Responsible for splitting the entire markdown into separate slides, +/// extracting front matter, and capturing comments. +class MarkdownParser { + const MarkdownParser(); + + /// Splits the entire markdown into slides. + /// + /// A "slide" is defined by frontmatter sections delimited with `---`. + /// Code blocks (fenced by ```) are respected, so `---` inside a code block + /// won't be treated as frontmatter delimiters. + static List _splitSlides(String content) { + content = content.trim(); + final lines = LineSplitter().convert(content); + final slides = []; + final buffer = StringBuffer(); + bool insideFrontMatter = false; + + var isCodeBlock = false; + + for (var line in lines) { + final trimmed = line.trim(); + if (trimmed.startsWith('```')) { + isCodeBlock = !isCodeBlock; + } + if (isCodeBlock) { + buffer.writeln(line); + continue; + } + + if (insideFrontMatter && trimmed.isEmpty) { + insideFrontMatter = false; + } + + if (trimmed == '---') { + if (!insideFrontMatter) { + if (buffer.isNotEmpty) { + slides.add(buffer.toString().trim()); + buffer.clear(); + } + } + insideFrontMatter = !insideFrontMatter; + } + buffer.writeln(line); + } + + if (buffer.isNotEmpty) { + slides.add(buffer.toString()); + } + + return slides; + } + + List parse(String markdown) { + final rawSlides = _splitSlides(markdown); + + final slides = []; + + final _frontMatterExtractor = FrontmatterParser(); + + for (final rawSlide in rawSlides) { + final frontmatter = _frontMatterExtractor.parse(rawSlide); + + slides.add( + RawSlideMarkdown( + key: generateValueHash(rawSlide), + content: (frontmatter.contents ?? '').trim(), + frontmatter: frontmatter.frontmatter, + ), + ); + } + + return slides; + } +} diff --git a/packages/superdeck_cli/lib/src/parsers/parsers/base_parser.dart b/packages/superdeck_cli/lib/src/parsers/parsers/base_parser.dart new file mode 100644 index 00000000..73a56cdf --- /dev/null +++ b/packages/superdeck_cli/lib/src/parsers/parsers/base_parser.dart @@ -0,0 +1,5 @@ +abstract class BaseParser { + const BaseParser(); + + T parse(String content); +} diff --git a/packages/superdeck_cli/lib/src/parsers/parsers/block_parser.dart b/packages/superdeck_cli/lib/src/parsers/parsers/block_parser.dart new file mode 100644 index 00000000..deff6c2b --- /dev/null +++ b/packages/superdeck_cli/lib/src/parsers/parsers/block_parser.dart @@ -0,0 +1,89 @@ +import 'package:source_span/source_span.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +import '../../helpers/exceptions.dart'; +import 'base_parser.dart'; + +class ParsedBlock { + final String type; + final int startIndex; + final int endIndex; + final Map _data; + + const ParsedBlock({ + required this.type, + required Map data, + required this.startIndex, + required this.endIndex, + }) : _data = data; + + Map get data { + final keys = [ + SectionBlock.key, + ColumnBlock.key, + ImageBlock.key, + DartPadBlock.key, + WidgetBlock.key, + ]; + + return !keys.contains(type) + ? {..._data, 'name': type, 'type': WidgetBlock.key} + : {..._data, 'type': type}; + } +} + +class BlockParser extends BaseParser> { + const BlockParser(); + + @override + List parse(String text) { + // @tag + // @tag {key: value} + // @tag{key: value, key2: value2} + // @tag {key: value, key2: value2, key3: value3} + // @tag{ + // key: value + // key2: value2 + // key3: value3 + // } + + // Get the "tag", which could be any word, and also maybe it does not have space + final tagRegex = RegExp(r'^\s*@(\w+)(?:\s*{([^{}]*)})?', multiLine: true); + + final matches = tagRegex.allMatches(text); + + return matches.map((match) { + final typeString = match.group(1) ?? ''; + final optionsString = match.group(2) ?? ''; + + Map optiuons; + + try { + optiuons = YamlUtils.convertYamlToMap(optionsString); + } on Exception catch (e, stackTrace) { + // Create a SourceSpan for the options content. + + final sourceSpan = SourceSpan( + SourceLocation(match.start), + SourceLocation(match.end), + optionsString, + ); + Error.throwWithStackTrace( + DeckFormatException( + 'Failed to parse tag blocks: $e', + sourceSpan, + text, + ), + stackTrace, + ); + } + + return ParsedBlock( + type: typeString, + data: optiuons, + startIndex: match.start, + endIndex: match.end, + ); + }).toList(); + } +} diff --git a/packages/superdeck_cli/lib/src/parsers/parsers/comment_parser.dart b/packages/superdeck_cli/lib/src/parsers/parsers/comment_parser.dart new file mode 100644 index 00000000..949d3972 --- /dev/null +++ b/packages/superdeck_cli/lib/src/parsers/parsers/comment_parser.dart @@ -0,0 +1,25 @@ +import 'dart:convert'; + +import 'package:petitparser/petitparser.dart'; +import 'package:superdeck_cli/src/parsers/parsers/base_parser.dart'; +import 'package:superdeck_cli/src/parsers/parsers/grammar_definitions.dart'; + +class CommentParser extends BaseParser> { + const CommentParser(); + + @override + List parse(String content) { + final htmlCommentParser = HtmlCommentDefinition().build(); + final lines = LineSplitter().convert(content); + final comments = []; + for (var line in lines) { + final comment = htmlCommentParser.parse(line); + + if (comment is Success) { + comments.add(comment.value); + } + } + + return comments; + } +} diff --git a/packages/superdeck_cli/lib/src/parsers/parsers/fenced_code_parser.dart b/packages/superdeck_cli/lib/src/parsers/parsers/fenced_code_parser.dart new file mode 100644 index 00000000..59e479ac --- /dev/null +++ b/packages/superdeck_cli/lib/src/parsers/parsers/fenced_code_parser.dart @@ -0,0 +1,77 @@ +import 'package:superdeck_core/superdeck_core.dart'; + +import 'base_parser.dart'; + +final _codeFencePattern = RegExp( + r'```(?[^`]*)[\s\S]*?```', + multiLine: true, +); + +// ``` {: , : , ...} +// +// ``` + +// Data class to hold code block details +class ParsedFencedCode { + final Map options; + final String language; + final String content; + // The first index of the opening fence + final int startIndex; + // The last index of the closing fence + final int endIndex; + + const ParsedFencedCode({ + required this.options, + required this.language, + required this.content, + required this.startIndex, + required this.endIndex, + }); + + @override + String toString() { + return 'ParsedCodeBlock(language: $language, content: $content, startIndex: $startIndex, endIndex: $endIndex)'; + } +} + +class FencedCodeParser extends BaseParser> { + const FencedCodeParser(); + + @override + List parse(String text) { + final matches = _codeFencePattern.allMatches(text); + List parsedBlocks = []; + + for (final match in matches) { + final backtickInfo = match.namedGroup('backtickInfo'); + + final lines = backtickInfo?.split('\n'); + final firstLine = lines?.first ?? ''; + final rest = lines?.sublist(1).join('\n') ?? ''; + + final language = firstLine.split(' ')[0]; + final options = firstLine.replaceFirst(language, '').trim(); + + final content = rest; + + final startIndex = match.start; + final endIndex = match.end; + + final Map optionsMap = + options.isNotEmpty ? YamlUtils.convertYamlToMap(options) : {}; + + parsedBlocks.add( + ParsedFencedCode( + options: optionsMap, + language: language, + content: content.trim(), + startIndex: startIndex, + endIndex: endIndex, + ), + ); + } + + return parsedBlocks; + } +} diff --git a/packages/superdeck_cli/lib/src/parsers/parsers/front_matter_parser.dart b/packages/superdeck_cli/lib/src/parsers/parsers/front_matter_parser.dart new file mode 100644 index 00000000..b3bbef2a --- /dev/null +++ b/packages/superdeck_cli/lib/src/parsers/parsers/front_matter_parser.dart @@ -0,0 +1,37 @@ +import 'package:petitparser/petitparser.dart'; +import 'package:superdeck_cli/src/helpers/logger.dart'; +import 'package:superdeck_cli/src/parsers/parsers/base_parser.dart'; +import 'package:superdeck_cli/src/parsers/parsers/grammar_definitions.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +typedef ExtractedFrontmatter = ({ + Map frontmatter, + String? contents, +}); + +class FrontmatterParser extends BaseParser { + const FrontmatterParser(); + + ExtractedFrontmatter parse(String content) { + final parser = const FrontMatterGrammarDefinition() + .build(); + final result = parser.parse(content); + + if (result is Failure) { + throw FormatException(result.message, content, result.position); + } + + final yamlString = result.value.yaml; + final markdownContent = result.value.markdown; + Map yamlMap = {}; + + try { + yamlMap = YamlUtils.convertYamlToMap(yamlString); + } catch (e) { + logger.err('Cannot parse yaml frontmatter: $e'); + yamlMap = {}; + } + + return (frontmatter: yamlMap, contents: markdownContent); + } +} diff --git a/packages/superdeck_cli/lib/src/parsers/parsers/grammar_definitions.dart b/packages/superdeck_cli/lib/src/parsers/parsers/grammar_definitions.dart new file mode 100644 index 00000000..32307cb1 --- /dev/null +++ b/packages/superdeck_cli/lib/src/parsers/parsers/grammar_definitions.dart @@ -0,0 +1,158 @@ +import 'package:petitparser/petitparser.dart'; + +extension ParserExtension on Parser { + Parser between(Parser start, Parser end) => + (start & this & end).map((values) => values[1]); +} + +class StringOptionsDefinition extends GrammarDefinition { + const StringOptionsDefinition(); + + Parser _quote() => char('"') | char("'"); + + Parser quotedString() => + pattern('^"').star().flatten().between(ref(_quote), ref(_quote)); + + /// An expression is a key-value pair or a key only. + Parser expression() => + (ref(key) & char('=').trim().optional() & ref(value).optional()) + .map((values) { + String key = (values[0] as String).trim(); + final secondValue = values[2]; + if (secondValue != null) { + return switch (secondValue) { + SeparatedList list => MapEntry(key, list.elements), + String string => MapEntry(key, string.trim()), + _ => MapEntry(key, secondValue), + }; + } + + return MapEntry(key, true); // Default to true if value is missing + }); + + /// Defines the key with allowed characters: letters, numbers, underscores, hyphens, dots. + Parser key() => (word() | char('_') | char('-') | char('.')).plus().flatten(); + + /// Defines possible value types. + Parser value() => ref(quotedString) | ref(list) | ref(boolean) | ref(number); + + /// Parses lists enclosed in square brackets. + /// + /// Example: [1, 2, 3] + /// Example: ["1", "2", "3"] + + Parser list() => + (ref(numberList) | listItem().plusSeparated(char(',').trim())) + .optional() + .between(char('['), char(']')); + + /// Parses individual list items. + Parser listItem() => + ref(numberRange) | ref(number) | ref(quotedString) | ref(boolean); + + /// Parses boolean values (case-insensitive). + Parser boolean() => (stringIgnoreCase('true').map((_) => true) | + stringIgnoreCase('false').map((_) => false)); + + /// Parses numbers (integers and floating-point). + Parser number() => (char('-').optional() & + digit().plus() & + (char('.') & digit().plus()).optional()) + .flatten() + .map((value) => num.parse(value)); + + /// Parses a list of numbers and ranges, separated by commas. + Parser numberList() => + (ref(numberRange) | ref(number).map((value) => [value])) + .plusSeparated(char(',').trim()) + .map((values) { + // Flatten the list of lists + return values.elements + .expand((element) => element as List) + .toSet() + .toList(); + }); + + Parser numberRange() => (ref(number) & char('-') & ref(number)).map((values) { + final start = values[0] as int; + final end = values[2] as int; + if (start > end) { + throw FormatException('Invalid range: $start-$end'); + } + + return List.generate(end - start + 1, (index) => start + index); + }); + + /// Defines space separators (one or more spaces). + Parser space() => whitespace().plus(); + + /// The start rule parses multiple expressions separated by spaces + /// and converts them into a Map. + @override + Parser start() => ref(expression) + .starSeparated(ref(space)) + .map((entries) => Map.fromEntries( + entries.elements.cast>(), + )) + .end(); +} + +typedef FrontMatterGrammarDefinitionResult = ({String yaml, String markdown}); + +class FrontMatterGrammarDefinition + extends GrammarDefinition { + const FrontMatterGrammarDefinition(); + + Parser _delimiter() => string('---'); + + Parser _doubleDelimiter() => + (ref(_delimiter) & + ref(yamlString) & + ref(_delimiter) & + ref(markdownContent)) + .map((values) => (yaml: values[1], markdown: values[3])); + + Parser _singleDelimiter() => + (ref(_delimiter) & ref(markdownContent).optional()) + .map((values) => (yaml: '', markdown: values[1])); + + Parser _noDelimiter() => + ref(markdownContent).map((values) => (yaml: '', markdown: values[0])); + + Parser yamlString() => any().starLazy(ref(_delimiter)).flatten(); + + Parser markdownContent() => any().star().flatten(); + + @override + Parser start() => + (ref(_doubleDelimiter) | ref(_singleDelimiter) | ref(_noDelimiter)) + .map((values) => ( + yaml: (values.yaml ?? '').trim(), + markdown: (values.markdown ?? '').trim(), + )); +} + +class HtmlCommentDefinition extends GrammarDefinition { + const HtmlCommentDefinition(); + + Parser _open() => string(''); + + /// Matches the full HTML comment: `` + Parser htmlComment() => ref(commentBody).between(ref(_open), ref(_close)); + + /// Matches the comment body: + /// - Consumes any character unless it forms `--` + /// - Stops lazily before the closing `-->`. + /// + /// This ensures we don't allow `--` anywhere **inside** the comment content, + /// which is the strict rule for valid HTML comments. + Parser commentBody() => (string('--').not() & any()) + .starLazy(ref(_close)) + .flatten('Invalid HTML comment (contains `--` before closing).'); + + @override + Parser start() => + ref(htmlComment).trim().end().map((value) => (value as String).trim()); +} diff --git a/packages/superdeck_cli/lib/src/parsers/parsers/section_parser.dart b/packages/superdeck_cli/lib/src/parsers/parsers/section_parser.dart new file mode 100644 index 00000000..be8a5869 --- /dev/null +++ b/packages/superdeck_cli/lib/src/parsers/parsers/section_parser.dart @@ -0,0 +1,119 @@ +import 'dart:convert'; + +import 'package:superdeck_cli/src/parsers/parsers/base_parser.dart'; +import 'package:superdeck_cli/src/parsers/parsers/block_parser.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +class SectionParser extends BaseParser> { + const SectionParser(); + + @override + List parse(String content) { + final parsedBlocks = const BlockParser().parse(content); + + final updatedContent = _updateIgnoredTags(content); + + // If there are no tag blocks, we can just add the entire markdown as a single section. + if (parsedBlocks.isEmpty) { + return [SectionBlock.text(updatedContent)]; + } + + final aggregator = _SectionAggregator(); + + final firstBlock = parsedBlocks.first; + + if (firstBlock.startIndex > 0) { + aggregator.addContent(updatedContent.substring(0, firstBlock.startIndex)); + } + + for (var idx = 0; idx < parsedBlocks.length; idx++) { + final parsedBlock = parsedBlocks[idx]; + + final isLast = idx == parsedBlocks.length - 1; + + String blockContent; + if (isLast) { + blockContent = updatedContent.substring(parsedBlock.endIndex).trim(); + } else { + final nextBlock = parsedBlocks[idx + 1]; + blockContent = updatedContent.substring( + parsedBlock.endIndex, + nextBlock.startIndex, + ); + } + + final block = Block.parse(parsedBlock.data); + + aggregator + ..addBlock(block) + ..addContent(blockContent); + } + + return aggregator.sections; + } +} + +String _updateIgnoredTags(String content) { + final lines = LineSplitter().convert(content); + + List updatedLines = []; + + for (final line in lines) { + final ignoreTag = '_@'; + final trimmedLine = line.trim(); + if (trimmedLine.startsWith(ignoreTag)) { + updatedLines.add(line.replaceFirst(ignoreTag, '@')); + continue; + } + + updatedLines.add(line); + } + + return updatedLines.join('\n'); +} + +class _SectionAggregator { + List sections = []; + + _SectionAggregator(); + + SectionBlock _getSection() { + if (sections.isEmpty) { + sections.add(SectionBlock([])); + } + + return sections.last; + } + + void addContent(String content) { + final section = _getSection(); + final block = section.blocks.lastOrNull; + final blocks = [...section.blocks]; + + if (content.trim().isEmpty) { + return; + } + + if (block is ColumnBlock) { + final newContent = + block.content.isEmpty ? content : '${block.content}\n$content'; + + blocks.last = block.copyWith(content: newContent); + } else { + blocks.add(ColumnBlock(content)); + } + + sections.last = section.copyWith(blocks: blocks); + } + + void addBlock(Block block) { + if (block is SectionBlock) { + sections.add(block); + } else { + final lastSection = _getSection(); + final blocks = [...lastSection.blocks, block]; + + sections.last = lastSection.copyWith(blocks: blocks); + } + } +} diff --git a/packages/superdeck_cli/lib/src/runner.dart b/packages/superdeck_cli/lib/src/runner.dart new file mode 100644 index 00000000..6d8e1c9c --- /dev/null +++ b/packages/superdeck_cli/lib/src/runner.dart @@ -0,0 +1,25 @@ +import 'dart:async'; + +import 'package:args/command_runner.dart'; +import 'package:mason_logger/mason_logger.dart'; +import 'package:superdeck_cli/src/commands/build_command.dart'; +import 'package:superdeck_cli/src/helpers/exceptions.dart'; + +class SuperDeckRunner extends CommandRunner { + SuperDeckRunner() : super('superdeck', 'Superdeck CLI'); + + @override + Future run(Iterable args) async { + addCommand(BuildCommand()); + + try { + final exitCode = await super.run(args); + + return exitCode ?? ExitCode.software.code; + } on Exception catch (e) { + printException(e); + + return ExitCode.software.code; + } + } +} diff --git a/packages/superdeck_cli/lib/src/slides_loader.dart b/packages/superdeck_cli/lib/src/slides_loader.dart deleted file mode 100644 index 1ca794e5..00000000 --- a/packages/superdeck_cli/lib/src/slides_loader.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'dart:async'; - -import 'package:superdeck_cli/src/constants.dart'; -import 'package:superdeck_cli/src/tasks/dart_formatter_task.dart'; -import 'package:superdeck_cli/src/tasks/image_cache_task.dart'; -import 'package:superdeck_cli/src/tasks/mermaid_task.dart'; -import 'package:superdeck_cli/src/tasks/slide_thumbnail_task.dart'; -import 'package:watcher/watcher.dart'; - -import 'slides_pipeline.dart'; - -String _markdownContents = ''; - -class SlidesLoader { - SlidesLoader(); - - Future watch() async { - print('Watching for changes...'); - final watcher = FileWatcher(kMarkdownFile.path); - await for (final event in watcher.events) { - if (event.type == ChangeType.MODIFY) { - final newContents = await kMarkdownFile.readAsString(); - if (newContents != _markdownContents) { - _markdownContents = newContents; - await generate(); - } - } - } - } - - Future generate() async { - print('Generating slides...'); - - final pipeline = TaskPipeline([ - const MermaidConverterTask(), - const DartFormatterTask(), - const SlideThumbnailTask(), - const ImageCachingTask(), - ]); - - await pipeline.run(); - } -} diff --git a/packages/superdeck_cli/lib/src/slides_pipeline.dart b/packages/superdeck_cli/lib/src/slides_pipeline.dart deleted file mode 100644 index f6e6f3f2..00000000 --- a/packages/superdeck_cli/lib/src/slides_pipeline.dart +++ /dev/null @@ -1,216 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:collection/collection.dart'; -import 'package:image/image.dart' as img; -import 'package:path/path.dart' as p; -import 'package:superdeck_cli/src/constants.dart'; -import 'package:superdeck_cli/src/helpers/extensions.dart'; -import 'package:superdeck_cli/src/helpers/pretty_json.dart'; -import 'package:superdeck_cli/src/helpers/raw_models.dart'; -import 'package:superdeck_cli/src/helpers/short_hash_id.dart'; -import 'package:superdeck_cli/src/helpers/slide_parser.dart'; - -typedef MarkdownReplacement = ({ - Pattern pattern, - String replacement, -}); - -typedef PipelineResult = ({ - List slides, - List neededAssets, - List markdownReplacements, -}); - -class TaskController { - final RawSlide slide; - final List _assets; - final List markdownReplacements; - - TaskController._({ - required this.slide, - required List assets, - required this.markdownReplacements, - }) : _assets = assets; - - TaskController({ - required this.slide, - required List assets, - }) : _assets = assets, - markdownReplacements = []; - - List neededAssets = []; - - RawAsset? checkAssetExists(String assetName) { - return _assets - .firstWhereOrNull((element) => element.path.contains(assetName)); - } - - TaskController copyWith({ - RawSlide? slide, - List? assets, - List? markdownReplacements, - }) { - return TaskController._( - slide: slide ?? this.slide, - markdownReplacements: markdownReplacements ?? this.markdownReplacements, - assets: assets ?? _assets, - )..neededAssets = neededAssets; - } - - Future markFileAsNeeded(File file, [String? reference]) async { - var asset = _assets.firstWhereOrNull((f) => f.path == file.path); - - if (asset == null) { - print(file.path); - final image = await img.decodeImageFile(file.path); - if (image == null) { - throw Exception('Could not decode image'); - } - - asset = RawAsset( - path: file.path, - width: image.width, - height: image.height, - reference: reference, - ); - } else { - if (!await file.exists()) { - throw Exception('File does not exist'); - } - } - markNeeded(asset); - } - - void markNeeded(RawAsset asset) { - neededAssets.add(asset); - } -} - -class TaskPipeline { - final List builders; - - TaskPipeline( - this.builders, - ); - - Future _runEachSlide( - RawSlide slide, - List assets, - ) async { - var controller = TaskController(slide: slide, assets: assets); - for (var task in builders) { - controller = await task.run(controller); - } - return controller; - } - - Future run() async { - await kMarkdownFile.ensureExists(); - await kGeneratedAssetsDir.ensureExists(); - await kReferenceFile.ensureExists(); - final markdownRaw = kMarkdownFile.readAsStringSync(); - - final reference = RawReference.loadFile(kReferenceFile); - final rawAssets = reference.assets; - - final parser = SlideParser(markdownRaw); - final slides = parser.run(); - - final futures = >[]; - - for (var slide in slides) { - futures.add(_runEachSlide(slide, rawAssets)); - } - - final controllers = await Future.wait(futures); - - final result = ( - slides: controllers.map((e) => e.slide).toList(), - neededAssets: controllers.expand((e) => e.neededAssets).toList(), - markdownReplacements: - controllers.expand((e) => e.markdownReplacements).toList(), - ); - - await _applyResults(result); - - return result; - } -} - -Future _applyResults(PipelineResult result) async { - final config = RawConfig.loadFile(kProjectConfigFile); - await _applyMarkdownReplacements(result.markdownReplacements); - await _cleanupGeneratedFiles(result.neededAssets); - await kReferenceFile.writeAsString( - prettyJson( - { - 'config': config.toMap(), - 'slides': result.slides.map((e) => e.toMap()).toList(), - 'assets': result.neededAssets.map((e) => e.toMap()).toList(), - }, - ), - ); -} - -Future _cleanupGeneratedFiles(List assets) async { - final files = await _loadGeneratedFiles(); - - for (var file in files) { - if (!assets.any((element) => element.path == file.path)) { - if (await file.exists()) { - await file.delete(); - } - } - } -} - -Future _applyMarkdownReplacements( - List replacements, -) async { - var markdownRaw = await kMarkdownFile.readAsString(); - - for (final entry in replacements) { - markdownRaw = markdownRaw.replaceAll(entry.pattern, entry.replacement); - } - - await kMarkdownFile.writeAsString(markdownRaw); -} - -abstract class Task { - final String taskName; - const Task(this.taskName); - - FutureOr run( - TaskController controller, - ); - - String buildReferenceName(String content) { - return shortHashId(content); - } - - File buildAssetFile(String assetName) { - if (p.extension(assetName).isEmpty) { - assetName = '$assetName.png'; - } - final updatedFileName = ('${taskName}_$assetName'); - return File(p.join(kGeneratedAssetsDir.path, updatedFileName)); - } - - bool isAssetFile(File file) { - // check if file name starts with sd_ - return file.path.contains(kGeneratedAssetsDir.path); - } -} - -Future> _loadGeneratedFiles() async { - final files = []; - - await for (var entity in kGeneratedAssetsDir.list()) { - if (entity is File) { - files.add(entity); - } - } - - return files; -} diff --git a/packages/superdeck_cli/lib/src/tasks/dart_formatter_task.dart b/packages/superdeck_cli/lib/src/tasks/dart_formatter_task.dart index b852e7f8..31e17520 100644 --- a/packages/superdeck_cli/lib/src/tasks/dart_formatter_task.dart +++ b/packages/superdeck_cli/lib/src/tasks/dart_formatter_task.dart @@ -1,40 +1,34 @@ import 'dart:async'; -import 'package:dart_style/dart_style.dart'; -import 'package:superdeck_cli/src/slides_pipeline.dart'; +import 'package:superdeck_cli/src/generator_pipeline.dart'; +import 'package:superdeck_cli/src/helpers/dart_process.dart'; + +import '../parsers/parsers/fenced_code_parser.dart'; class DartFormatterTask extends Task { - const DartFormatterTask() : super('dart_formatter'); + DartFormatterTask() : super('dart_formatter'); @override - FutureOr run(controller) async { - final formattedMarkdown = _formatDartCodeBlocks(controller); - - return controller.copyWith( - slide: controller.slide.copyWith(content: formattedMarkdown), - ); - } - - String _formatDartCodeBlocks( - TaskController controller, - ) { - final codeBlockRegex = RegExp(r'```dart\n([\s\S]*?)\n```'); - final markdown = controller.slide.content; - return markdown.replaceAllMapped(codeBlockRegex, (match) { - final code = match.group(1)!; - final formatter = DartFormatter(); - final formattedCode = formatter.format(code); - - final replacement = '```dart\n$formattedCode\n```'; - - controller.markdownReplacements.add( - ( - pattern: match.group(0)!, - replacement: replacement, - ), - ); - - return replacement; - }); + Future run(TaskContext context) async { + final fencedCodeParser = const FencedCodeParser(); + final codeBlocks = fencedCodeParser.parse(context.slide.content); + + final dartBlocks = codeBlocks.where((e) => e.language == 'dart'); + + for (final dartBlock in dartBlocks) { + try { + final formattedCode = await DartProcess.format(dartBlock.content); + + final updatedMarkdown = context.slide.content.replaceRange( + dartBlock.startIndex, + dartBlock.endIndex, + '```dart\n$formattedCode\n```', + ); + + context.slide.content = updatedMarkdown; + } catch (e) { + logger.severe('Failed to format Dart code: $e'); + } + } } } diff --git a/packages/superdeck_cli/lib/src/tasks/image_cache_task.dart b/packages/superdeck_cli/lib/src/tasks/image_cache_task.dart deleted file mode 100644 index 4f218c0b..00000000 --- a/packages/superdeck_cli/lib/src/tasks/image_cache_task.dart +++ /dev/null @@ -1,111 +0,0 @@ -import 'dart:io'; - -import 'package:http/http.dart' as http; -import 'package:superdeck_cli/src/slides_pipeline.dart'; - -class ImageCachingTask extends Task { - const ImageCachingTask() : super('image_caching'); - - @override - Future run(controller) async { - final slide = controller.slide; - - var content = slide.content; - final slideData = slide.toMap(); - // Do not cache remot edata if cacheRemoteAssets is false - - // Get any url of images that are in the markdown - // Save it the local path on the device - // and replace the url with the local path - final imageRegex = RegExp(r'!\[.*?\]\((.*?)\)'); - final urlRegex = RegExp( - r'((http|https|ftp):\/\/)?(www\.)?([a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+)(\/[^\s]*)?', - caseSensitive: false, - ); - - final matches = imageRegex.allMatches(content); - - Future saveAsset(String url) async { - if (isAssetFile(File(url))) return; - // Look by hashcode to see if the asset is already cached - final refName = buildReferenceName(url); - - final asset = controller.checkAssetExists(refName); - - // if (asset != null && await File(asset.path).exists()) { - // final file = File(asset.path); - // try { - // print('File already exists: ${file.path}'); - // await controller.markFileAsNeeded(file, url); - // return; - // } catch (e) { - // log('Error saving asset: ${file.path} $e'); - // await file.delete(); - // saveAsset(url); - // } - // } - - try { - final checkFileType = await http.head(Uri.parse(url)); - final contentType = checkFileType.headers['content-type']; - if (contentType == null) { - return; - } - - if (!contentType.startsWith('image/')) { - return; - } - - final commonRenderableFormats = ['jpeg', 'png', 'gif', 'webp']; - final extension = contentType.split('/').last; - if (!commonRenderableFormats.contains(extension)) { - return; - } - final response = await http.get(Uri.parse(url)); - - if (response.statusCode == 200) { - // Get the extension from the Content-Type header - final contentType = response.headers['content-type']; - - final extension = contentType?.split('/').last; - - // Create a file with the appropriate extension - final file = buildAssetFile(buildReferenceName(url) + '.$extension'); - - // Write the image data to the file - await file.writeAsBytes(response.bodyBytes); - - print('Image saved to: ${url}'); - - await controller.markFileAsNeeded(file, url); - } else {} - } catch (e) { - print('Error: $e'); - } - } - - for (final Match match in matches) { - final assetUri = match.group(1); - if (assetUri == null) continue; - - await saveAsset(assetUri); - } - - if (slideData['background'] != null) { - // Check if it matches - if (urlRegex.hasMatch(slideData['background'])) { - await saveAsset(slideData['background']); - } - } - - if (slideData['options'] != null) { - if (slideData['options']['background'] != null) { - if (urlRegex.hasMatch(slideData['options']['background'])) { - await saveAsset(slideData['options']['background']); - } - } - } - - return controller; - } -} diff --git a/packages/superdeck_cli/lib/src/tasks/image_caching_task.dart b/packages/superdeck_cli/lib/src/tasks/image_caching_task.dart new file mode 100644 index 00000000..f8863d9d --- /dev/null +++ b/packages/superdeck_cli/lib/src/tasks/image_caching_task.dart @@ -0,0 +1,79 @@ +// import 'dart:io'; +// import 'dart:typed_data'; + +// import 'package:http/http.dart' as http; +// import 'package:superdeck_cli/src/generator_pipeline.dart'; +// import 'package:superdeck_cli/src/parsers/markdown_parser.dart'; +// import 'package:superdeck_core/superdeck_core.dart'; + +// /// A task responsible for caching images referenced in markdown slides. +// class ImageCachingTask extends Task { +// /// A set to track assets currently being processed to prevent duplicate downloads. +// static final Set _executingAssets = {}; + +// /// HTTP client used for downloading images. +// static final http.Client _httpClient = http.Client(); + +// /// Constructs an [ImageCachingTask] with the name 'image_caching'. +// ImageCachingTask() : super('remote_asset_caching'); + +// // Function to download and save a single asset. +// Future _fetchData(TaskContext context, String url) async { +// try { +// logger.info('Downloading asset: $url'); +// final response = await _httpClient.get(Uri.parse(url)); + +// // Verify successful response. +// if (response.statusCode != 200) { +// logger.warning( +// 'Failed to download $url: Status ${response.statusCode}', +// ); + +// return null; +// } + +// final contentType = response.headers['content-type'] ?? ''; +// // Validate content type. +// if (!contentType.startsWith('image/')) { +// logger.warning('Invalid content type for $url: $contentType'); + +// return null; +// } + +// // Define supported image formats. + +// final extension = contentType.split('/').last.toLowerCase(); + +// final assetExtension = AssetExtension.tryParse(extension); + +// if (assetExtension == null) { +// logger.warning('Unsupported image format for $url: $extension'); + +// return null; +// } + +// final asset = GeneratedAsset.image(url, assetExtension); + +// final assetFile = await context.dataStore.getAssetFile(asset); + +// if (await assetFile.exists()) { +// return null; +// } + +// await assetFile.writeAsBytes(response.bodyBytes); + +// return response.bodyBytes; +// } catch (e, stackTrace) { +// logger.severe('Error downloading asset $url: $e', e, stackTrace); +// } + +// return null; +// } + +// @override +// Future run(TaskContext context) async { + + +// return context; +// } +// } diff --git a/packages/superdeck_cli/lib/src/tasks/mermaid_task.dart b/packages/superdeck_cli/lib/src/tasks/mermaid_task.dart index 823329ce..b197d698 100644 --- a/packages/superdeck_cli/lib/src/tasks/mermaid_task.dart +++ b/packages/superdeck_cli/lib/src/tasks/mermaid_task.dart @@ -1,130 +1,201 @@ import 'dart:async'; -import 'dart:developer'; import 'dart:io'; -import 'dart:typed_data'; -import 'package:path/path.dart' as p; -import 'package:superdeck_cli/src/slides_pipeline.dart'; +import 'package:puppeteer/puppeteer.dart'; +import 'package:superdeck_cli/src/generator_pipeline.dart'; +import 'package:superdeck_cli/src/helpers/logger.dart'; +import 'package:superdeck_core/superdeck_core.dart'; -class MermaidConverterTask extends Task { - final _mermaidService = const MermaidService(); - const MermaidConverterTask() : super('mermaid'); - - @override - FutureOr run(controller) async { - final mermaidBlockRegex = RegExp(r'```mermaid([\s\S]*?)```'); - final slide = controller.slide; - - final matches = mermaidBlockRegex.allMatches(slide.content); - - if (matches.isEmpty) return controller; - final replacements = <({int start, int end, String markdown})>[]; +import '../parsers/parsers/fenced_code_parser.dart'; - for (final Match match in matches) { - final mermaidSyntax = match.group(1); - - if (mermaidSyntax == null) continue; +class MermaidConverterTask extends Task { + Browser? _browser; + + /// Extract large HTML templates to constants for better readability. + static final _mermaidHtmlTemplate = ''' + + +
__GRAPH_DEFINITION__
+ + + +'''; + MermaidConverterTask() : super('mermaid'); + Future _getBrowser() async { + _browser ??= await puppeteer.launch(); + + return _browser!; + } - final mermaidFile = buildAssetFile(buildReferenceName(mermaidSyntax)); + /// A helper function that automates page creation, content setup, and cleanup. + Future _withPage( + Browser browser, + Future Function(Page page) action, + ) async { + final page = await browser.newPage(); + try { + return await action(page); + } finally { + await page.close(); + } + } - if (!await mermaidFile.exists()) { - // Process the mermaid syntax to generate an image file - final imageData = await _mermaidService.generateImage(mermaidSyntax); + Future _generateMermaidGraph( + Browser browser, + String graphDefinition, + ) { + logger.detail('Generating mermaid graph:'); + logger.detail(graphDefinition); - if (imageData != null) { - await mermaidFile.writeAsBytes(imageData); - } - } + final htmlContent = _mermaidHtmlTemplate.replaceAll( + '__GRAPH_DEFINITION__', + graphDefinition, + ); - // If file existeed or was create it then replace it - if (await mermaidFile.exists()) { - await controller.markFileAsNeeded(mermaidFile); + return _withPage(browser, (page) async { + await page.setContent(htmlContent); + await page.waitForSelector( + 'pre.mermaid > svg', + timeout: const Duration(seconds: 5), + ); - replacements.add(( - start: match.start, - end: match.end, - markdown: '![Mermaid Diagram](${mermaidFile.path})', - )); - } - } + final element = await page.$('pre.mermaid > svg'); - var replacedData = slide.content; + return await element.evaluate('el => el.outerHTML'); + }); + } - // Apply replacements in reverse order - for (var replacement in replacements.reversed) { - final ( - :start, - :end, - :markdown, - ) = replacement; + Future> _convertSvgToImage(Browser browser, String svgContent) { + return _withPage(browser, (page) async { + await page.setViewport(DeviceViewport( + width: 1280, + height: 780, + deviceScaleFactor: 2, + )); + + await page.setContent(''' + + +
$svgContent
+ + + '''); + + final element = await page.$('.svg-container > svg'); + + return await element.screenshot( + format: ScreenshotFormat.png, + omitBackground: true, + ); + }); + } - replacedData = replacedData.replaceRange(start, end, markdown); + Future> _generateMermaidGraphImage( + Browser browser, + String graphDefinition, + ) async { + try { + final svgContent = await _generateMermaidGraph(browser, graphDefinition); + + return await _convertSvgToImage(browser, svgContent); + } catch (e, stackTrace) { + logger.err('Failed to generate Mermaid graph image: $e'); + Error.throwWithStackTrace( + Exception( + 'Mermaid generation timed out or failed. Original error: $e', + ), + stackTrace, + ); } - - return controller.copyWith( - slide: slide.copyWith(content: replacedData), - ); } -} -class MermaidService { - const MermaidService(); + @override + void dispose() { + _browser?.close(); + _browser = null; + } - Future generateImage(String mermaidSyntax) async { - final fileName = mermaidSyntax.hashCode; + @override + Future run(TaskContext context) async { + final stopwatch = Stopwatch()..start(); - final tempDir = Directory('.tmp_superdeck'); + final fencedCodeParser = const FencedCodeParser(); - final tempFile = File(p.join(tempDir.path, '$fileName.mmd')); - final outputFile = File(p.join(tempDir.path, '$fileName.png')); + final codeBlocks = fencedCodeParser.parse(context.slide.content); + final mermaidBlocks = codeBlocks.where((e) => e.language == 'mermaid'); - if (!await tempDir.exists()) { - await tempDir.create(recursive: true); + if (mermaidBlocks.isEmpty) { + return; } - try { - mermaidSyntax = mermaidSyntax.trim().replaceAll(r'\n', '\n'); + for (final mermaidBlock in mermaidBlocks) { + final mermaidAsset = GeneratedAsset.mermaid(mermaidBlock.content); + + final assetPath = + await context.dataStore.getGeneratedAssetPath(mermaidAsset); - await tempFile.writeAsString(mermaidSyntax); + final assetFile = File(assetPath); - // Check if can execute mmdc before executing command - final mmdcResult = await Process.run('mmdc', ['--version']); + if (await assetFile.exists()) { + logger.info( + 'Mermaid asset already exists for slide index: ${context.slideIndex}', + ); + } else { + final browser = await _getBrowser(); - if (mmdcResult.exitCode != 0) { - log( - '"mmdc" not found. You need mermaid cli installed to process mermaid syntax', + logger.info( + 'Generating mermaid graph image for slide index: ${context.slideIndex}', ); + final imageData = + await _generateMermaidGraphImage(browser, mermaidBlock.content); - return null; + await assetFile.writeAsBytes(imageData); } - final params = [ - '-t dark', - '-b transparent', - '-i ${tempFile.path}', - '-o ${outputFile.path}', - '--scale 2' - ]; - - final result = await Process.run( - 'mmdc', - params.expand((e) => e.split(' ')).toList(), + final mermaidImageSyntax = '![mermaid_graph](${assetFile.path})'; + final updatedMarkdown = context.slide.content.replaceRange( + mermaidBlock.startIndex, + mermaidBlock.endIndex, + mermaidImageSyntax, ); - if (result.exitCode != 0) { - log('Error while processing mermaid syntax'); - log(result.stderr); - return null; - } - - return outputFile.readAsBytes(); - } catch (e) { - log('Error while processing mermaid syntax: $e'); - return null; - } finally { - if (await tempDir.exists()) { - await tempDir.delete(recursive: true); - } + context.slide.content = updatedMarkdown; } + + stopwatch.stop(); + logger.info( + 'Completed MermaidConverterTask for slide index: ${context.slideIndex} in ${stopwatch.elapsedMicroseconds} microseconds', + ); } } diff --git a/packages/superdeck_cli/lib/src/tasks/slide_thumbnail_task.dart b/packages/superdeck_cli/lib/src/tasks/slide_thumbnail_task.dart deleted file mode 100644 index 55892839..00000000 --- a/packages/superdeck_cli/lib/src/tasks/slide_thumbnail_task.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'dart:async'; - -import 'package:superdeck_cli/src/slides_pipeline.dart'; - -/// This task marks the thumbnail file as needed if it exists. -/// The goal is to ensure that any generated thumbnails are kept. -class SlideThumbnailTask extends Task { - const SlideThumbnailTask() : super('thumbnail'); - - @override - FutureOr run(controller) async { - final file = buildAssetFile(controller.slide.key + '.png'); - - if (await file.exists()) { - await controller.markFileAsNeeded(file); - } - - return controller; - } -} diff --git a/packages/superdeck_cli/lib/superdeck_cli.dart b/packages/superdeck_cli/lib/superdeck_cli.dart index fb22aa63..e1ad1a39 100644 --- a/packages/superdeck_cli/lib/superdeck_cli.dart +++ b/packages/superdeck_cli/lib/superdeck_cli.dart @@ -1 +1 @@ -export 'package:superdeck_cli/src/slides_loader.dart' show SlidesLoader; +export 'package:superdeck_cli/src/runner.dart'; diff --git a/packages/superdeck_cli/pubspec.yaml b/packages/superdeck_cli/pubspec.yaml index cb2b800e..db89d54d 100644 --- a/packages/superdeck_cli/pubspec.yaml +++ b/packages/superdeck_cli/pubspec.yaml @@ -3,21 +3,30 @@ description: A starting point for Dart libraries or applications. version: 0.0.1 # repository: https://github.com/my_org/my_repo +executables: + superdeck: main environment: sdk: ">=3.3.0 <4.0.0" dependencies: yaml: ^3.1.2 - args: ^2.5.0 + args: ^2.6.0 path: ^1.9.0 collection: ^1.18.0 - image: ^4.2.0 - watcher: ^1.1.0 - dart_style: ^2.3.6 - yaml_writer: ^2.0.0 + image: ^4.3.0 + dart_style: ^2.3.7 + yaml_writer: ^2.0.1 http: ^1.2.2 - + puppeteer: ^3.15.0 + superdeck_core: ^0.0.1 + mason_logger: ^0.3.1 + yaml_edit: ^2.2.1 + logging: ^1.3.0 + scope: ^5.1.0 + source_span: ^1.10.0 + petitparser: ^6.0.2 dev_dependencies: - lints: ^4.0.0 + dart_code_metrics_presets: ^2.19.0 + lints: ^5.0.0 test: ^1.25.8 diff --git a/packages/superdeck_cli/pubspec_overrides.yaml b/packages/superdeck_cli/pubspec_overrides.yaml new file mode 100644 index 00000000..fd21af7e --- /dev/null +++ b/packages/superdeck_cli/pubspec_overrides.yaml @@ -0,0 +1,4 @@ +# melos_managed_dependency_overrides: superdeck_core +dependency_overrides: + superdeck_core: + path: ../superdeck_core diff --git a/packages/superdeck_cli/slides.md b/packages/superdeck_cli/slides.md deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/superdeck_cli/test/src/helpers/update_pubspec_test.dart b/packages/superdeck_cli/test/src/helpers/update_pubspec_test.dart new file mode 100644 index 00000000..4842fe16 --- /dev/null +++ b/packages/superdeck_cli/test/src/helpers/update_pubspec_test.dart @@ -0,0 +1,78 @@ +import 'package:superdeck_cli/src/helpers/update_pubspec.dart'; +import 'package:superdeck_core/superdeck_core.dart'; +import 'package:test/test.dart'; + +void main() { + final deckConfig = DeckConfiguration(); + group('updatePubspecAssets', () { + test('adds superdeck assets to empty pubspec', () { + final input = ''' +name: test_app +description: A test app +version: 1.0.0 +'''; + + final result = updatePubspecAssets(deckConfig, input); + expect(result.contains('.superdeck/'), isTrue); + expect(result.contains('.superdeck/assets/'), isTrue); + }); + + test('adds superdeck assets to pubspec with existing flutter section', () { + final input = ''' +name: test_app +flutter: + uses-material-design: true +'''; + final result = updatePubspecAssets(deckConfig, input); + expect(result.contains('.superdeck/'), isTrue); + expect(result.contains('.superdeck/assets/'), isTrue); + expect(result.contains('uses-material-design: true'), isTrue); + }); + + test('preserves existing assets while adding superdeck assets', () { + final input = ''' +name: test_app +flutter: + assets: + - assets/images/ + - assets/fonts/ +'''; + final result = updatePubspecAssets(deckConfig, input); + expect(result.contains('assets/images/'), isTrue); + expect(result.contains('assets/fonts/'), isTrue); + expect(result.contains('.superdeck/'), isTrue); + expect(result.contains('.superdeck/assets/'), isTrue); + }); + + test('does not duplicate existing superdeck assets', () { + final input = ''' +name: test_app +flutter: + assets: + - .superdeck/ + - .superdeck/assets/ +'''; + final result = updatePubspecAssets(deckConfig, input); + expect(result.split('.superdeck/').length - 1, equals(2)); + expect(result.split('.superdeck/assets/').length - 1, equals(1)); + }); + + test('preserves other flutter configuration', () { + final input = ''' +name: test_app +flutter: + uses-material-design: true + fonts: + - family: CustomFont + fonts: + - asset: fonts/CustomFont-Regular.ttf +'''; + final result = updatePubspecAssets(deckConfig, input); + expect(result.contains('uses-material-design: true'), isTrue); + expect(result.contains('family: CustomFont'), isTrue); + expect(result.contains('fonts/CustomFont-Regular.ttf'), isTrue); + expect(result.contains('.superdeck/'), isTrue); + expect(result.contains('.superdeck/assets/'), isTrue); + }); + }); +} diff --git a/packages/superdeck_cli/test/src/parsers/block_parser_test.dart b/packages/superdeck_cli/test/src/parsers/block_parser_test.dart new file mode 100644 index 00000000..452cb685 --- /dev/null +++ b/packages/superdeck_cli/test/src/parsers/block_parser_test.dart @@ -0,0 +1,395 @@ +import 'package:superdeck_cli/src/parsers/parsers/block_parser.dart'; +import 'package:superdeck_cli/src/parsers/parsers/fenced_code_parser.dart'; +import 'package:test/test.dart'; + +final List> testCaseCodeBlock = [ + { + 'description': 'Test Case 1: Basic js code with options', + 'input': ''' +```js {theme: dark, lineNumbers: true} +console.log("Hello, world!"); +function greet() { + return "Hi!"; +} +``` +''', + 'expectedBlocks': [ + { + 'language': 'js', + 'options': { + 'theme': 'dark', + 'lineNumbers': true, + }, + 'content': '''console.log("Hello, world!"); +function greet() { + return "Hi!"; +}''', + } + ], + }, + { + 'description': 'Test Case 2: Python code without options', + 'input': ''' +```python +print("Hello, world!") + +def greet(): + return "Hi!" +``` +''', + 'expectedBlocks': [ + { + 'language': 'python', + 'options': {}, + 'content': '''print("Hello, world!") + +def greet(): + return "Hi!"''', + } + ], + }, + { + 'description': 'Test Case 3: Ruby code with different options', + 'input': ''' +```ruby {author: "Jane Doe", version: 2.0} +puts "Hello, world!" + +def greet + "Hi!" +end +``` +''', + 'expectedBlocks': [ + { + 'language': 'ruby', + 'options': { + 'author': 'Jane Doe', + 'version': 2.0, + }, + 'content': '''puts "Hello, world!" + +def greet + "Hi!" +end''', + } + ], + }, + { + 'description': 'Test Case 4: Code content containing ``` inside', + 'input': ''' +```js {theme: light} +console.log("Starting code block..."); +function greet() { + console.log("Hi!"); +} +``` +''', + 'expectedBlocks': [ + { + 'language': 'js', + 'options': { + 'theme': 'light', + }, + 'content': '''console.log("Starting code block..."); +function greet() { + console.log("Hi!"); +}''', + } + ], + }, + { + 'description': + 'Test Case 5: Incorrectly formatted code block (missing closing ```', + 'input': ''' +```js {theme: dark, lineNumbers: true} +console.log("Hello, world!"); +function greet() { + return "Hi!"; +} +``` +''', + 'expectedBlocks': [ + { + 'language': 'js', + 'options': { + 'theme': 'dark', + 'lineNumbers': true, + }, + 'content': '''console.log("Hello, world!"); +function greet() { + return "Hi!"; +}''', + } + ], + }, + { + 'description': 'Test Case 6: Code block with complex options', + 'input': ''' +```dart {theme: "dark mode", showLineNumbers: true, indent: 2} +void main() { + print("Hello, Dart!"); +} +``` +''', + 'expectedBlocks': [ + { + 'language': 'dart', + 'options': { + 'theme': 'dark mode', + 'showLineNumbers': true, + 'indent': 2, + }, + 'content': '''void main() { + print("Hello, Dart!"); +}''', + } + ], + }, + { + 'description': 'Test Case 7: Dart code block with no options', + 'input': ''' +```dart +void main() { + print("Hello, Dart!"); +} +``` +''', + 'expectedBlocks': [ + { + 'language': 'dart', + 'options': {}, + 'content': '''void main() { + print("Hello, Dart!"); +}''', + } + ], + } +]; + +final List> testCaseTagBlock = [ + { + 'description': 'Test Case 1: Basic tag block', + 'input': ''' +@tag +''', + 'expectedBlocks': [ + ParsedBlock( + type: 'tag', + data: {}, + startIndex: 0, + endIndex: 4, + ) + ], + }, + { + 'description': 'Test Case 2: Tag block with options', + 'input': ''' +@tag {key: value} +''', + 'expectedBlocks': [ + ParsedBlock( + type: 'tag', + data: {'key': 'value'}, + startIndex: 0, + endIndex: 17, + ) + ], + }, + { + 'description': 'Test Case 3: Tag block with multiple options', + 'input': ''' +@tag { + key: value + key2: value2 +} +Test content +''', + 'expectedBlocks': [ + ParsedBlock( + type: 'tag', + data: {'key': 'value', 'key2': 'value2'}, + startIndex: 0, + endIndex: 36, + ) + ], + }, + { + 'description': + 'Test Case 4: Tag block with no space between tag and options', + 'input': ''' +@tag{key: value} +''', + 'expectedBlocks': [ + ParsedBlock( + type: 'tag', + data: {'key': 'value'}, + startIndex: 0, + endIndex: 16, + ) + ], + }, + { + 'description': 'Test Case 5: Tag block with options across multiple lines', + 'input': ''' +@tag{ + key: value + key2: value2 +} + +# Test content +## Test content 2 +''', + 'expectedBlocks': [ + ParsedBlock( + type: 'tag', + data: {'key': 'value', 'key2': 'value2'}, + startIndex: 0, + endIndex: 35, + ), + ], + }, + { + 'description': + 'Test Case 6: Tag block with multiple lines and space between tag and options', + 'input': ''' +@tag { + key: value + key2: value2 +} +''', + 'expectedBlocks': [ + ParsedBlock( + type: 'tag', + data: {'key': 'value', 'key2': 'value2'}, + startIndex: 0, + endIndex: 36, + ) + ], + }, + { + 'description': + 'Test Case 7: Tag block with different values like int, dboules, and bool', + 'input': ''' +@tag { + key: 1 + key2: 2.0 + key3: true +} +''', + 'expectedBlocks': [ + ParsedBlock( + type: 'tag', + data: {'key': 1, 'key2': 2.0, 'key3': true}, + startIndex: 0, + endIndex: 42, + ), + ], + }, + // multiple tags + { + 'description': 'Test Case 8: Multiple tags', + 'input': ''' +@tag1 {key: value} + +Test content 1 +@tag2 {key2: value2} + +Test content 2 +''', + 'expectedBlocks': [ + ParsedBlock( + type: 'tag1', + data: {'key': 'value'}, + startIndex: 0, + endIndex: 18, + ), + ParsedBlock( + type: 'tag2', + data: {'key2': 'value2'}, + startIndex: 35, + endIndex: 55, + ) + ], + }, + + { + 'description': 'Test Case 9: Multiple tags in different lines', + 'input': ''' +@tag1 {key: value} +@tag2 {key2: value2} +''', + 'expectedBlocks': [ + ParsedBlock( + type: 'tag1', + data: {'key': 'value'}, + startIndex: 0, + endIndex: 18, + ), + ParsedBlock( + type: 'tag2', + data: {'key2': 'value2'}, + startIndex: 19, + endIndex: 39, + ) + ], + }, + // does not match {@column} + { + 'description': 'Test Case 10: Does not match @column', + 'input': ''' +{@column} +''', + 'expectedBlocks': [], + } +]; + +void main() { + group('parseFencedCode', () { + final fencedCodeParser = const FencedCodeParser(); + for (final testCase in testCaseCodeBlock) { + test(testCase['description'], () { + final blocks = fencedCodeParser.parse(testCase['input']); + expect(blocks.length, testCase['expectedBlocks'].length, + reason: 'Number of parsed blocks does not match expected.'); + + for (int i = 0; i < testCase['expectedBlocks'].length; i++) { + final expected = testCase['expectedBlocks'][i]; + final actual = blocks[i]; + + expect(actual.language, expected['language'], + reason: 'Block \${i + 1}: Language mismatch.'); + expect(actual.options, expected['options'], + reason: 'Block \${i + 1}: Options mismatch.'); + expect(actual.content.trim(), expected['content'].trim(), + reason: 'Block \${i + 1}: Content mismatch.'); + } + }); + } + }); + + group('parseTagBlocks', () { + for (final testCase in testCaseTagBlock) { + final description = testCase['description']; + test(description, () { + final blocks = const BlockParser().parse(testCase['input']); + expect(blocks.length, testCase['expectedBlocks'].length, + reason: + '$description - Number of parsed blocks does not match expected.'); + + for (int i = 0; i < testCase['expectedBlocks'].length; i++) { + final expected = testCase['expectedBlocks'][i] as ParsedBlock; + final actual = blocks[i]; + + expect(actual.type, expected.type, + reason: '$description - Block ${i + 1}: Tag mismatch.'); + expect(actual.data, expected.data, + reason: '$description - Block ${i + 1}: Options mismatch.'); + expect(actual.startIndex, expected.startIndex, + reason: '$description - Block ${i + 1}: Start index mismatch.'); + expect(actual.endIndex, expected.endIndex, + reason: '$description - Block ${i + 1}: End index mismatch.'); + } + }); + } + }); +} diff --git a/packages/superdeck_cli/test/src/parsers/grammar_definitions_test.dart b/packages/superdeck_cli/test/src/parsers/grammar_definitions_test.dart new file mode 100644 index 00000000..fa45c86d --- /dev/null +++ b/packages/superdeck_cli/test/src/parsers/grammar_definitions_test.dart @@ -0,0 +1,245 @@ +import 'package:petitparser/petitparser.dart'; +import 'package:superdeck_cli/src/parsers/parsers/grammar_definitions.dart'; +import 'package:test/test.dart'; + +void main() { + group('HtmlCommentDefinition', () { + final parser = const HtmlCommentDefinition().build(); + + test('parses simple HTML comment', () { + expect(parser.parse('').value, 'Simple comment'); + }); + + test('parses empty HTML comment', () { + expect(parser.parse('').value, ''); + }); + + // Trst if you can have -- in the comment + test('parses HTML comment with --', () { + final result = parser.parse(''); + + expect(result is Failure, isTrue); + expect( + result.message, + 'Invalid HTML comment (contains `--` before closing).', + ); + expect(result.position, 4); + expect(() => result.value, throwsA(isA())); + }); + + // check if acomment has spaces in front or in teh end + test('parses HTML comment with spaces in front or in the end', () { + expect( + parser.parse(' ').value, 'Simple comment'); + expect(parser.parse(' ').value, 'Simple comment'); + }); + + test('parses multiline HTML comment', () { + const comment = ''''''; + expect(parser.parse(comment).value.trim(), + 'This is a\n multiline comment'); + }); + }); + + group('StringOptionsDefinition', () { + final parser = StringOptionsDefinition().build(); + test('Parses single boolean option without value', () { + expect(parser.parse('showLineNumbers=true').value, + {'showLineNumbers': true}); + }); + + // number example + test('Parses number option', () { + expect(parser.parse('count=10').value, {'count': 10}); + expect(parser.parse('count=10.5').value, {'count': 10.5}); + expect(parser.parse('count=-10.5').value, {'count': -10.5}); + }); + + test('Parses single boolean option without equal sign', () { + expect(parser.parse('showLineNumbers').value, {'showLineNumbers': true}); + }); + + test('Parses boolean options with true and false', () { + expect(parser.parse('showLineNumbers=true').value, + {'showLineNumbers': true}); + expect(parser.parse('showLineNumbers=false').value, + {'showLineNumbers': false}); + }); + + test('Parses string options with quotes', () { + expect(parser.parse('fileName="example.dart"').value, + {'fileName': 'example.dart'}); + expect(parser.parse('anotherOption="another_example.dart"').value, + {'anotherOption': 'another_example.dart'}); + }); + + test('Parses list options', () { + expect( + parser.parse('options=["option1", "option2", "option3"]').value, + { + 'options': ['option1', 'option2', 'option3'] + }, + ); + expect(parser.parse('lines=[2-5,3]').value, { + 'lines': [2, 3, 4, 5] + }); + }); + + test('Parses multiple options on the same line separated by spaces', () { + expect(parser.parse('flex=1 align="center"').value, + {'flex': 1, 'align': 'center'}); + expect(parser.parse('flex=3 align="top_left"').value, + {'flex': 3, 'align': 'top_left'}); + }); + + test('Handles mixed types', () { + expect( + parser + .parse( + 'showLineNumbers=true fileName="test.dart" options=["opt1", "opt2"] flex=2') + .value, + { + 'showLineNumbers': true, + 'fileName': 'test.dart', + 'options': ['opt1', 'opt2'], + 'flex': 2, + }, + ); + }); + + test('Handles expressions without values gracefully', () { + expect( + parser.parse('showLineNumbers fileName="test.dart"').value, + { + 'showLineNumbers': true, + 'fileName': 'test.dart', + }, + ); + }); + + test('Handles lists with quoted items', () { + expect( + parser.parse('options=["option one", "option two"]').value, + { + 'options': ['option one', 'option two'] + }, + ); + }); + + test('Handles numeric values', () { + expect(parser.parse('count=10 threshold=15.5').value, + {'count': 10, 'threshold': 15.5}); + }); + + test('Handles extended key characters', () { + expect( + parser + .parse( + 'user-name="JohnDoe" user.email="john.doe@example.com" isAdmin=true') + .value, + { + 'user-name': 'JohnDoe', + 'user.email': 'john.doe@example.com', + 'isAdmin': true, + }, + ); + }); + + test('Handles empty input', () { + expect(parser.parse('').value, {}); + }); + + test('Handles keys with underscores and numbers', () { + expect(parser.parse('key_1=10 key_2="value"').value, + {'key_1': 10, 'key_2': 'value'}); + }); + + test('Handles keys with camelCase and PascalCase', () { + expect(parser.parse('camelCaseKey=true PascalCaseKey="value"').value, + {'camelCaseKey': true, 'PascalCaseKey': 'value'}); + }); + + test('Handles values with special characters', () { + expect(parser.parse('key="!@#\$%^&*()_+-=[]{}|;:,.<>?"').value, + {'key': '!@#\$%^&*()_+-=[]{}|;:,.<>?'}); + }); + + test('Handles negative numeric values', () { + expect(parser.parse('negativeInt=-10 negativeDouble=-3.14').value, + {'negativeInt': -10, 'negativeDouble': -3.14}); + }); + + test('Handles list options with numeric values', () { + expect(parser.parse('numbers=[1, 2, 3]').value, { + 'numbers': [1, 2, 3] + }); + }); + + test('Handles list options with boolean values', () { + expect(parser.parse('booleans=[true, false]').value, { + 'booleans': [true, false] + }); + }); + }); + + group('FrontmatterDefinition', () { + final parser = const FrontMatterGrammarDefinition().build(); + + test('parses frontmatter', () { + const content = '''--- +title: "Sample Document" +author: "John Doe" +tags: + - example + - test +--- +# Heading +Content goes here. +'''; + final result = parser.parse(content).value; + expect(result.yaml, '''title: "Sample Document" +author: "John Doe" +tags: + - example + - test'''); + expect(result.markdown, '# Heading\nContent goes here.'); + }); + + test('parses frontmatter with no content', () { + const content = '''--- +title: "Empty Document" +author: "Jane Smith" +tags: + - empty + - test +--- +'''; + final result = parser.parse(content).value; + expect(result.yaml, '''title: "Empty Document" +author: "Jane Smith" +tags: + - empty + - test'''); + expect(result.markdown, ''); + }); + + test('parses frontmatter with single delimiter', () { + const content = '---'; + final result = parser.parse(content).value; + expect(result.yaml, ''); + expect(result.markdown, ''); + }); + + test('parses frontmatter with single delimiter and content', () { + const content = ''' +--- +# Heading +Content goes here. +'''; + final result = parser.parse(content).value; + expect(result.yaml, ''); + expect(result.markdown, '# Heading\nContent goes here.'); + }); + }); +} diff --git a/packages/superdeck_cli/test/src/parsers/section_parser_test.dart b/packages/superdeck_cli/test/src/parsers/section_parser_test.dart new file mode 100644 index 00000000..912c81c7 --- /dev/null +++ b/packages/superdeck_cli/test/src/parsers/section_parser_test.dart @@ -0,0 +1,421 @@ +import 'package:superdeck_cli/src/parsers/parsers/section_parser.dart'; +import 'package:superdeck_core/superdeck_core.dart'; +import 'package:test/test.dart'; + +void main() { + final sectionParser = SectionParser(); + + group('Basic Parsing', () { + test('Empty markdown returns no sections', () { + final sections = sectionParser.parse(''); + expect(sections[0].blocks.length, 1); + }); + + test('Markdown with no tags returns one section with all lines', () { + const markdown = ''' + # Just some heading + Some regular text. + '''; + final sections = sectionParser.parse(markdown); + expect( + sections.length, + 1, + reason: 'Should create a single default section for plain text.', + ); + expect( + sections[0].blocks.length, + 1, + reason: 'All lines should be in a single block.', + ); + expect(sections[0].blocks[0].content, markdown); + }); + }); + + group('Section Structure', () { + test('Section with columns', () { + const markdown = ''' +@section +# Title + +@column +content column 1. + +@column +content column 2. + +'''; + + final sections = sectionParser.parse(markdown); + expect(sections[0].blocks.length, equals(3)); + expect( + sections[0].blocks[0].content.trim(), + '# Title', + reason: 'First block is a title.', + ); + expect( + sections[0].blocks[1].content.trim(), + 'content column 1.', + reason: 'Second block should contain first column content.', + ); + expect( + sections[0].blocks[2].content.trim(), + 'content column 2.', + reason: 'Third block should contain second column content.', + ); + }); + + test('Only columns without sections', () { + const markdown = ''' +@column +Content column 1. + +@column +Content column 2. + +'''; + + final sections = sectionParser.parse(markdown); + expect(sections[0].blocks.length, equals(2)); + expect(sections[0].blocks[0].content.trim(), 'Content column 1.'); + expect(sections[0].blocks[1].content.trim(), 'Content column 2.'); + }); + + test('Columns then sections', () { + const markdown = ''' +# Regular Markdown + +This is some regular markdown content. + +@section +## Header Title + +@column +Content inside the header. +'''; + + final sections = sectionParser.parse(markdown); + expect(sections[0].blocks.length, equals(1)); + expect(sections[1].blocks.length, equals(2)); + + expect( + sections[0].blocks[0].content.trim(), + '# Regular Markdown\n\nThis is some regular markdown content.', + reason: 'First section should contain the initial markdown content.', + ); + + expect(sections[1].blocks[0].content.trim(), '## Header Title'); + expect( + sections[1].blocks[1].content.trim(), + 'Content inside the header.', + ); + }); + + test('Header, body, and footer with columns', () { + const markdown = ''' +@section +# Header Title + +@column +Header content column. + +@section +@column +Body content column 1. + +@column +Body content column 2. + +@section +@column +Footer content column. + +'''; + + final sections = sectionParser.parse(markdown); + + expect(sections[0].blocks.length, equals(2)); + expect(sections[1].blocks.length, equals(2)); + expect(sections[2].blocks.length, equals(1)); + expect(sections[0].blocks[0].content.trim(), '# Header Title'); + expect(sections[0].blocks[1].content.trim(), 'Header content column.'); + expect(sections[1].blocks[0].content.trim(), 'Body content column 1.'); + expect(sections[1].blocks[1].content.trim(), 'Body content column 2.'); + expect(sections[2].blocks[0].content.trim(), 'Footer content column.'); + }); + }); + + // 4. Attribute Tests + group('Attributes', () { + group('Column Attributes', () { + test('Header with columns and flex attribute', () { + const markdown = ''' +@section +@column{ + flex: 1 +} +Header content column 1. + +@column{ + flex: 2 +} +Header content column 2. +'''; + + final sections = sectionParser.parse(markdown); + expect(sections[0].blocks.length, equals(2)); + expect( + sections[0].blocks[0].content.trim(), + 'Header content column 1.', + ); + expect( + sections[0].blocks[1].content.trim(), + 'Header content column 2.', + ); + + expect( + sections[0].blocks[0].flex, + equals(1), + reason: 'First column should have flex=1', + ); + expect( + sections[0].blocks[1].flex, + equals(2), + reason: 'Second column should have flex=2', + ); + }); + + test('Section with columns and alignment attribute in snake case', () { + const markdown = ''' +@section +@column{ + align: center +} +Body content column 1. + +@column{ + align: bottom_right +} +Body content column 2. +'''; + + final sections = sectionParser.parse(markdown); + expect(sections[0].blocks.length, equals(2)); + expect(sections[0].blocks[0].content.trim(), 'Body content column 1.'); + expect(sections[0].blocks[1].content.trim(), 'Body content column 2.'); + + expect( + sections[0].blocks[0].align, + equals(ContentAlignment.center), + ); + expect( + sections[0].blocks[1].align, + equals(ContentAlignment.bottomRight), + ); + }); + + test( + 'Section with columns, flex, and alignment attributes in snake case', + () { + const markdown = ''' +@section +@column{ + flex: 3 + align: top_left +} +Footer content column 1. +@column{ + flex: 1 + align: center_right +} +Footer content column 2. +'''; + + final sections = sectionParser.parse(markdown); + expect(sections[0].blocks.length, equals(2)); + + expect( + sections[0].blocks[0].content.trim(), + 'Footer content column 1.', + ); + expect( + sections[0].blocks[1].content.trim(), + 'Footer content column 2.', + ); + + expect(sections[0].blocks[0].flex, equals(3)); + expect( + sections[0].blocks[0].align, + equals(ContentAlignment.topLeft), + ); + + expect(sections[0].blocks[1].flex, equals(1)); + expect( + sections[0].blocks[1].align, + equals(ContentAlignment.centerRight), + ); + }, + ); + + test('Sections with columns and attributes', () { + const markdown = ''' +@section +@column{ + flex: 1 + align: center +} +Header content. + +@section +@column{ + flex: 2 + align: center_left +} +Body content column 1. + +@column{ + flex: 1 + align: center_right +} +Body content column 2. + +@section +@column{ + flex: 1 + align: bottom_center +} +Footer content. + +'''; + + final sections = sectionParser.parse(markdown); + + expect(sections[0].blocks.length, equals(1)); + expect(sections[1].blocks.length, equals(2)); + expect(sections[2].blocks.length, equals(1)); + + expect(sections[0].blocks[0].content.trim(), 'Header content.'); + expect(sections[0].blocks[0].flex, equals(1)); + expect( + sections[0].blocks[0].align, + equals(ContentAlignment.center), + ); + + expect(sections[1].blocks[0].content.trim(), 'Body content column 1.'); + expect(sections[1].blocks[0].flex, equals(2)); + expect( + sections[1].blocks[0].align, + equals(ContentAlignment.centerLeft), + ); + + expect(sections[1].blocks[1].content.trim(), 'Body content column 2.'); + expect(sections[1].blocks[1].flex, equals(1)); + expect( + sections[1].blocks[1].align, + equals(ContentAlignment.centerRight), + ); + + expect(sections[2].blocks[0].content.trim(), 'Footer content.'); + expect(sections[2].blocks[0].flex, equals(1)); + expect( + sections[2].blocks[0].align, + equals(ContentAlignment.bottomCenter), + ); + }); + }); + + group('Inheritance', () { + test('Columns inherit options from the parent', () { + const markdown = ''' +@section {align: center} +@column +Header content. + +@section{ + align: top_left + flex: 2 +} +@column{ + flex: 3 +} +Body content. + +@section{ + align: bottom_right + flex: 1 +} +@column{ align: bottom_right} +Footer content. + +'''; + + final sections = sectionParser.parse(markdown); + + expect(sections.length, equals(3)); + + expect(sections[0].blocks.length, equals(1), + reason: 'First section should have one block.'); + expect(sections[1].blocks.length, equals(1), + reason: 'Second section should have one block.'); + expect(sections[2].blocks.length, equals(1), + reason: 'Third section should have one block.'); + + expect(sections[0].blocks[0].content.trim(), 'Header content.'); + expect( + sections[0].align, + equals(ContentAlignment.center), + reason: 'Should inherit center alignment from parent.', + ); + + expect(sections[1].blocks[0].content.trim(), 'Body content.'); + expect(sections[1].align, equals(ContentAlignment.topLeft)); + expect( + sections[1].blocks[0].flex, + equals(3), + reason: + 'Column should have its own flex overriding or complementing parent.', + ); + + expect(sections[2].blocks[0].content.trim(), 'Footer content.'); + expect(sections[2].align, equals(ContentAlignment.bottomRight)); + expect(sections[2].flex, equals(1)); + }); + }); + + group('Failure Cases', () { + test('Invalid flex attribute format', () { + const markdown = ''' +@section +@column{ flex: invalid} +Header content. + +'''; + expect( + () => sectionParser.parse(markdown), + throwsA(isA()), + reason: 'Invalid flex value should throw FormatException.', + ); + }); + + test('Invalid alignment attribute value', () { + const markdown = ''' +@section +@column{ + align: invalid_alignment +} +Header content. + +'''; + + expect( + () => sectionParser.parse(markdown), + throwsA(isA()), + reason: 'Invalid alignment value should throw FormatException.', + ); + }); + }); + }); +} + +extension on Block { + String get content => (this as ColumnBlock).content; +} diff --git a/packages/superdeck_cli/test/src/parsers/slide_parser_test.dart b/packages/superdeck_cli/test/src/parsers/slide_parser_test.dart new file mode 100644 index 00000000..d12fac84 --- /dev/null +++ b/packages/superdeck_cli/test/src/parsers/slide_parser_test.dart @@ -0,0 +1,282 @@ +import 'package:superdeck_cli/src/parsers/markdown_parser.dart'; +import 'package:test/test.dart'; + +void main() { + final markdownParser = MarkdownParser(); + group('MarkdownParser.parse', () { + test('parses valid markdown into RawSlides', () async { + const markdown = ''' +--- +title: Slide 1 +--- + +Content for slide 1 + +--- +title: Slide 2 +--- + +Content for slide 2 + +--- + +Content for slide 3 +'''; + + final slides = await markdownParser.parse(markdown); + + expect(slides.length, equals(3)); + expect(slides[0].frontmatter['title'], equals('Slide 1')); + expect(slides[0].content, equals('Content for slide 1')); + expect(slides[1].frontmatter['title'], equals('Slide 2')); + expect(slides[1].content, equals('Content for slide 2')); + expect(slides[2].frontmatter, {}); + expect(slides[2].content, equals('Content for slide 3')); + }); + + test( + 'parses RawSlides with additional properties in YAML frontmatter', + () async { + const markdown = ''' +--- +title: Slide 1 +--- +Content for slide 1 + +--- +title: Slide 2 +--- +Content for slide 2 +'''; + + final slides = await markdownParser.parse(markdown); + + expect(slides.length, equals(2)); + expect(slides[0].frontmatter['title'], equals('Slide 1')); + + expect(slides[0].content, equals('Content for slide 1')); + expect(slides[1].frontmatter['title'], equals('Slide 2')); + + expect(slides[1].content, equals('Content for slide 2')); + }, + ); + + test('handles RawSlides with no properties in frontmatter', () async { + const markdown = ''' +--- +--- +Content for slide 1 + +--- +--- +Content for slide 2 +'''; + + final slides = await markdownParser.parse(markdown); + + expect(slides.length, equals(2)); + expect(slides[0].frontmatter, {}); + expect(slides[0].content, equals('Content for slide 1')); + expect(slides[1].frontmatter, {}); + expect(slides[1].content, equals('Content for slide 2')); + }); + + test('handles RawSlides with empty frontmatter', () async { + const markdown = ''' +--- +title: +--- +Content for slide 1 + +--- +title: +--- +Content for slide 2 +'''; + + final slides = await markdownParser.parse(markdown); + + expect(slides.length, equals(2)); + expect(slides[0].frontmatter, {'title': null}); + expect(slides[0].content, equals('Content for slide 1')); + expect(slides[1].frontmatter, {'title': null}); + expect(slides[1].content, equals('Content for slide 2')); + }); + + test('handles empty markdown string', () async { + const markdown = ''; + + final slides = await markdownParser.parse(markdown); + + expect(slides, isEmpty); + }); + + test('ignores content outside slide separators', () async { + const markdown = ''' +This content is outside slides +--- +title: Slide 1 +--- +Content for slide 1 + +This last content is also outside slides +'''; + + final slides = await markdownParser.parse(markdown); + + expect(slides.length, equals(2)); + expect(slides[0].frontmatter, {}); + expect(slides[1].frontmatter['title'], equals('Slide 1')); + expect( + slides[1].content, + equals( + 'Content for slide 1\n\nThis last content is also outside slides', + ), + ); + }); + + test('parses RawSlide with no content but valid frontmatter', () async { + const markdown = ''' +--- +title: Slide 1 +--- +'''; + + final slides = await markdownParser.parse(markdown); + + expect(slides.length, equals(1)); + expect(slides[0].frontmatter['title'], equals('Slide 1')); + expect(slides[0].content, isEmpty); + }); + + test( + 'parses multiple RawSlides with some missing content or frontmatter', + () async { + const markdown = ''' +--- +title: Slide 1 +--- +Content for slide 1 + +--- +title: Slide 2 +--- +--- +title: Slide 3 +--- +Content for slide 3 +'''; + + final slides = await markdownParser.parse(markdown); + + expect(slides.length, equals(3)); + expect(slides[0].frontmatter['title'], equals('Slide 1')); + expect(slides[0].content, equals('Content for slide 1')); + + expect(slides[1].frontmatter['title'], equals('Slide 2')); + expect(slides[1].content, isEmpty); + + expect(slides[2].frontmatter['title'], equals('Slide 3')); + expect(slides[2].content, equals('Content for slide 3')); + }, + ); + }); + + // Group test notes from comments + group('Correctly parses slide notes from markdown comments', () { + test('parses notes from markdown comments', () async { + const markdown = ''' +--- +title: Slide 1 +--- +Content for slide 1 + + + +--- +title: Slide 2 +--- + +Content for slide 2 + +'''; + + final slides = await markdownParser.parse(markdown); + + expect(slides.length, equals(2)); + expect(slides[0].frontmatter['title'], equals('Slide 1')); + expect( + slides[0].content, + equals('Content for slide 1\n\n'), + ); + + expect(slides[1].frontmatter['title'], equals('Slide 2')); + expect(slides[1].content, equals('Content for slide 2')); + }); + + test('parses multiple notes from markdown comments', () async { + const markdown = ''' +--- +title: Slide 1 +--- +Content for slide 1 + + + + + + + +--- +title: Slide 2 +--- + +Content for slide 2 + +'''; + + final slides = await markdownParser.parse(markdown); + + expect(slides.length, equals(2)); + expect(slides[0].frontmatter['title'], equals('Slide 1')); + expect( + slides[0].content, + equals( + 'Content for slide 1\n\n\n\n\n\n', + ), + ); + + expect(slides[0].frontmatter['title'], equals('Slide 1')); + + expect(slides[1].frontmatter['title'], equals('Slide 2')); + expect(slides[1].content, equals('Content for slide 2')); + }); + }); + + // Test that mixes single --- with frontmatter + group('Handles slides with mixed frontmatter and ---', () { + test('parses slides with mixed frontmatter and ---', () async { + const markdown = ''' +--- +title: Slide 1 +--- +Content for slide 1 + +--- + +Content for the second slide +'''; + + final slides = await markdownParser.parse(markdown); + + expect(slides.length, equals(2)); + + expect(slides[0].frontmatter['title'], equals('Slide 1')); + expect(slides[0].content, equals('Content for slide 1')); + + expect(slides[1].frontmatter, {}); + expect(slides[1].content, equals('Content for the second slide')); + }); + }); +} diff --git a/packages/superdeck_core/.gitignore b/packages/superdeck_core/.gitignore new file mode 100644 index 00000000..3cceda55 --- /dev/null +++ b/packages/superdeck_core/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/packages/superdeck_core/CHANGELOG.md b/packages/superdeck_core/CHANGELOG.md new file mode 100644 index 00000000..effe43c8 --- /dev/null +++ b/packages/superdeck_core/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/packages/superdeck_core/LICENSE b/packages/superdeck_core/LICENSE new file mode 100644 index 00000000..b28006a2 --- /dev/null +++ b/packages/superdeck_core/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2022, Leo Farias +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/superdeck_core/README.md b/packages/superdeck_core/README.md new file mode 100644 index 00000000..f54c9700 --- /dev/null +++ b/packages/superdeck_core/README.md @@ -0,0 +1,3 @@ +# superdeck_core + +A core library for the Superdeck \ No newline at end of file diff --git a/packages/superdeck_core/analysis_options.yaml b/packages/superdeck_core/analysis_options.yaml new file mode 100644 index 00000000..258dae00 --- /dev/null +++ b/packages/superdeck_core/analysis_options.yaml @@ -0,0 +1,2 @@ +include: package:lints/recommended.yaml +extends: ../../shared_analysis_options.yaml diff --git a/packages/superdeck_core/build.yaml b/packages/superdeck_core/build.yaml new file mode 100644 index 00000000..2fc0149b --- /dev/null +++ b/packages/superdeck_core/build.yaml @@ -0,0 +1,14 @@ +targets: + $default: + builders: + dart_mappable_builder: + generate_for: + - lib/**/*.dart + +global_options: + dart_mappable_builder: + options: + caseStyle: snakeCase + enumCaseStyle: snakeCase + ignoreNull: true + generateMethods: [decode, encode, copy, stringify, equals] \ No newline at end of file diff --git a/packages/superdeck_core/example/superdeck_core_example.dart b/packages/superdeck_core/example/superdeck_core_example.dart new file mode 100644 index 00000000..1bc9f057 --- /dev/null +++ b/packages/superdeck_core/example/superdeck_core_example.dart @@ -0,0 +1,3 @@ +void main() { + // internal package for superdeck +} diff --git a/packages/superdeck_core/lib/src/helpers/data_store.dart b/packages/superdeck_core/lib/src/helpers/data_store.dart new file mode 100644 index 00000000..0bcf61c6 --- /dev/null +++ b/packages/superdeck_core/lib/src/helpers/data_store.dart @@ -0,0 +1,167 @@ +// deck_repository.dart (After) + +import 'dart:async'; +import 'dart:io'; + +import 'package:path/path.dart' as p; +import 'package:superdeck_core/superdeck_core.dart'; + +import 'pretty_json.dart'; + +abstract interface class IDataStore { + final DeckConfiguration configuration; + + IDataStore(this.configuration); + + Future initialize(); + + Future loadDeckReference(); + + Stream loadDeckReferenceStream(); + + String getGeneratedAssetPath(GeneratedAsset asset) { + return p.join(configuration.assetsDir.path, asset.fileName); + } + + Future readAssetByPath(String path); +} + +class LocalDataStore extends IDataStore { + LocalDataStore(super.configuration); + + Future fileReader(String path) async { + return File(path).readAsString(); + } + + @override + Future initialize() async {} + + @override + Future readAssetByPath(String path) async { + return fileReader(path); + } + + @override + Future loadDeckReference() async { + try { + // if (!await configuration.deckJson.exists()) { + // throw Exception('Deck reference file not found'); + // } + final content = await fileReader(configuration.deckJson.path); + return DeckReferenceMapper.fromJson(content); + } on Exception catch (e) { + return DeckReference( + slides: [ + ErrorSlide( + title: 'Superdeck reference error', + message: configuration.deckJson.path, + error: e, + ), + ], + config: configuration, + ); + } + } + + @override + Stream loadDeckReferenceStream() { + return Stream.fromFuture(loadDeckReference()); + } +} + +class FileSystemDataStore extends LocalDataStore { + FileSystemDataStore(super.configuration); + + final List _generatedAssets = []; + + @override + Future initialize() async { + if (!await configuration.assetsDir.exists()) { + await configuration.assetsDir.create(recursive: true); + } + + if (!await configuration.deckJson.exists()) { + await configuration.deckJson.writeAsString('{}'); + } + + if (!await configuration.slidesFile.exists()) { + await configuration.slidesFile.writeAsString(''); + } + + await super.initialize(); + } + + @override + String getGeneratedAssetPath(GeneratedAsset asset) { + _generatedAssets.add(asset); + return super.getGeneratedAssetPath(asset); + } + + Future saveReferences( + DeckReference reference, + ) async { + // Save deck reference + final deckJson = prettyJson(reference.toMap()); + await configuration.deckJson.writeAsString(deckJson); + + // Generate the asset references for each slide thumbnail + final thumbnails = + reference.slides.map((slide) => GeneratedAsset.thumbnail(slide.key)); + + // Combine thumbnail and generated assets + final allAssets = [ + ...thumbnails, + ..._generatedAssets, + ]; + +// Map asset references to their corresponding file paths + final assetFiles = allAssets.map( + (asset) => File(p.join(configuration.assetsDir.path, asset.fileName))); + + final assetsRef = GeneratedAssetsReference( + lastModified: DateTime.now(), + files: assetFiles.toList(), + ); + + // Save the assets reference + final assetsJson = prettyJson(assetsRef.toMap()); + await configuration.assetsRefJson.writeAsString(assetsJson); + + await _cleanupGeneratedAssets(assetsRef); + } + + Future readDeckMarkdown() async { + return await configuration.slidesFile.readAsString(); + } + + Future _cleanupGeneratedAssets( + GeneratedAssetsReference assetsReference, + ) async { + final existingFiles = await configuration.assetsDir + .list(recursive: true) + .where((e) => e is File) + .map((e) => e as File) + .toList(); + + final referencedFiles = + assetsReference.files.map((file) => file.path).toSet(); + + await Future.forEach(existingFiles, (File file) async { + if (!referencedFiles.contains(file.path)) { + await file.delete(); + } + }); + } + + @override + Stream loadDeckReferenceStream() async* { + // Emit the current reference immediately. + yield await loadDeckReference(); + + // For each file modification event, emit a new deck reference. + await for (final _ + in configuration.deckJson.watch(events: FileSystemEvent.modify)) { + yield await loadDeckReference(); + } + } +} diff --git a/packages/superdeck_core/lib/src/helpers/extensions.dart b/packages/superdeck_core/lib/src/helpers/extensions.dart new file mode 100644 index 00000000..3f7f3816 --- /dev/null +++ b/packages/superdeck_core/lib/src/helpers/extensions.dart @@ -0,0 +1,29 @@ +extension StringX on String { + String capitalize() { + return "${this[0].toUpperCase()}${substring(1)}"; + } + + String snakeCase() { + return replaceAll(RegExp(r'\s+'), '_') + .replaceAllMapped( + RegExp( + r'[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+'), + (match) => "${match.group(0)!.toLowerCase()}_") + .replaceAll(RegExp(r'(_)\1+'), '_') + .replaceAll(RegExp(r'^_|_$'), ''); + } +} + +extension ListX on List { + T? get tryFirst => isNotEmpty ? first : null; + T? get tryLast => isNotEmpty ? last : null; + T? tryElementAt(int index) { + if (index < 0 || index >= length) { + return null; + } + + return elementAt(index); + } +} + +/// Formats [json] diff --git a/packages/superdeck_cli/lib/src/helpers/short_hash_id.dart b/packages/superdeck_core/lib/src/helpers/generate_hash.dart similarity index 95% rename from packages/superdeck_cli/lib/src/helpers/short_hash_id.dart rename to packages/superdeck_core/lib/src/helpers/generate_hash.dart index 989b0cb8..3126fb02 100644 --- a/packages/superdeck_cli/lib/src/helpers/short_hash_id.dart +++ b/packages/superdeck_core/lib/src/helpers/generate_hash.dart @@ -8,7 +8,7 @@ /// [valueToHash] is the string input that you want to convert into a hash ID. /// /// Returns an 8-character string that represents the hashed ID. -String shortHashId(String valueToHash) { +String generateValueHash(String valueToHash) { const characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; diff --git a/packages/superdeck/lib/helpers/mappers.dart b/packages/superdeck_core/lib/src/helpers/mappers.dart similarity index 51% rename from packages/superdeck/lib/helpers/mappers.dart rename to packages/superdeck_core/lib/src/helpers/mappers.dart index 8a8c2b5a..627986ee 100644 --- a/packages/superdeck/lib/helpers/mappers.dart +++ b/packages/superdeck_core/lib/src/helpers/mappers.dart @@ -1,7 +1,8 @@ import 'dart:io'; import 'package:dart_mappable/dart_mappable.dart'; -import 'package:flutter/services.dart'; + +import '../models/block_model.dart'; class FileMapper extends SimpleMapper { const FileMapper(); @@ -17,6 +18,20 @@ class FileMapper extends SimpleMapper { } } +class DirectoryMapper extends SimpleMapper { + const DirectoryMapper(); + + @override + Directory decode(Object value) { + return Directory(value as String); + } + + @override + String encode(Directory self) { + return self.path; + } +} + class DurationMapper extends SimpleMapper { const DurationMapper(); @@ -31,24 +46,20 @@ class DurationMapper extends SimpleMapper { } } -class SizeMapper extends SimpleMapper { - const SizeMapper(); +class NullIfEmptyBlock extends SimpleMapper { + const NullIfEmptyBlock(); @override - Size decode(Object value) { - final valueMap = value as Map; - - final width = valueMap['width'] as num; - final height = valueMap['height'] as num; - - return Size( - width.toDouble(), - height.toDouble(), - ); + Block decode(dynamic value) { + return BlockMapper.fromMap(value); } @override - Map encode(Size self) { - return {"width": self.width, "height": self.height}; + dynamic encode(Block self) { + final map = self.toMap(); + if (map.isEmpty) { + return null; + } + return map; } } diff --git a/packages/superdeck_cli/lib/src/helpers/pretty_json.dart b/packages/superdeck_core/lib/src/helpers/pretty_json.dart similarity index 89% rename from packages/superdeck_cli/lib/src/helpers/pretty_json.dart rename to packages/superdeck_core/lib/src/helpers/pretty_json.dart index a21e6138..6ea44209 100644 --- a/packages/superdeck_cli/lib/src/helpers/pretty_json.dart +++ b/packages/superdeck_core/lib/src/helpers/pretty_json.dart @@ -1,8 +1,8 @@ import 'dart:convert'; -/// Formats [json] String prettyJson(dynamic json) { var spaces = ' ' * 2; var encoder = JsonEncoder.withIndent(spaces); + return encoder.convert(json); } diff --git a/packages/superdeck_core/lib/src/helpers/uuid_v4.dart b/packages/superdeck_core/lib/src/helpers/uuid_v4.dart new file mode 100644 index 00000000..acd1e056 --- /dev/null +++ b/packages/superdeck_core/lib/src/helpers/uuid_v4.dart @@ -0,0 +1,19 @@ +// UUID v4 generator +import 'dart:math'; + +String uuidV4() { + final random = Random(); + const hexDigits = '0123456789abcdef'; + + return List.generate(36, (index) { + if (index == 8 || index == 13 || index == 18 || index == 23) { + return '-'; + } else if (index == 14) { + return '4'; + } else if (index == 19) { + return hexDigits[(random.nextInt(4) + 8)]; + } else { + return hexDigits[random.nextInt(16)]; + } + }).join(); +} diff --git a/packages/superdeck_core/lib/src/helpers/watcher.dart b/packages/superdeck_core/lib/src/helpers/watcher.dart new file mode 100644 index 00000000..2314275c --- /dev/null +++ b/packages/superdeck_core/lib/src/helpers/watcher.dart @@ -0,0 +1,59 @@ +import 'dart:async'; +import 'dart:io'; + +class FileWatcher { + final File file; + Timer? _timer; + DateTime? _lastModified; + bool _isProcessing = false; + + // Extracted polling interval as a constant for clarity and easy modification. + static const _pollingInterval = Duration(milliseconds: 500); + + FileWatcher(this.file); + + /// Starts watching the file for changes. Calls [onFileChange] whenever the file changes. + void startWatching(FutureOr Function() onFileChange) { + _timer = Timer.periodic(_pollingInterval, (_) async { + if (_isProcessing) return; + + // Check if the file has actually changed since last time + if (await _fileHasChanged()) { + await _runOnFileChange(onFileChange); + } + }); + } + + /// Stops watching the file. + void stopWatching() { + _timer?.cancel(); + _timer = null; + } + + /// Checks if the watcher is currently active. + bool get isWatching => _timer?.isActive ?? false; + + /// Determines if the file has changed since the last recorded timestamp. + Future _fileHasChanged() async { + final currentLastModified = await file.lastModified(); + // If this is the first check, we establish a baseline and do not treat it as a change. + if (_lastModified == null) { + _lastModified = currentLastModified; + return false; + } + + final changed = currentLastModified != _lastModified; + _lastModified = currentLastModified; + return changed; + } + + /// Runs the provided [onFileChange] callback, managing _isProcessing state and error handling. + Future _runOnFileChange(FutureOr Function() onFileChange) async { + _isProcessing = true; + try { + await onFileChange(); + } finally { + _isProcessing = false; + } + } +} diff --git a/packages/superdeck_core/lib/src/helpers/yaml_utils.dart b/packages/superdeck_core/lib/src/helpers/yaml_utils.dart new file mode 100644 index 00000000..9ef25217 --- /dev/null +++ b/packages/superdeck_core/lib/src/helpers/yaml_utils.dart @@ -0,0 +1,25 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:yaml/yaml.dart'; + +class YamlUtils { + const YamlUtils._(); + static Map convertYamlToMap(String yamlString) { + if (yamlString.trim().isEmpty) return {}; + final yamlMap = loadYaml(yamlString, recover: true); + + if (yamlMap is YamlMap) { + return jsonDecode(jsonEncode(yamlMap)); + } else { + throw FormatException( + 'Invalid YAML format. $yamlString', + ); + } + } + + static Future> loadYamlFile(File file) async { + final yamlString = await file.readAsString(); + return convertYamlToMap(yamlString); + } +} diff --git a/packages/superdeck_core/lib/src/models/asset_model.dart b/packages/superdeck_core/lib/src/models/asset_model.dart new file mode 100644 index 00000000..634fafa6 --- /dev/null +++ b/packages/superdeck_core/lib/src/models/asset_model.dart @@ -0,0 +1,96 @@ +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:dart_mappable/dart_mappable.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +import '../helpers/mappers.dart'; + +part 'asset_model.mapper.dart'; + +@MappableEnum() +enum AssetExtension { + png, + jpeg, + gif, + webp, + svg; + + static final schema = Ack.enumValues(values); + + static AssetExtension? tryParse(String value) { + final extension = value.toLowerCase(); + + return extension == 'jpg' + ? AssetExtension.jpeg + : AssetExtension.values.firstWhereOrNull((e) => e.name == extension); + } +} + +@MappableClass() +class GeneratedAsset with GeneratedAssetMappable { + final String name; + final AssetExtension extension; + final String type; + + GeneratedAsset({ + required this.name, + required this.extension, + required this.type, + }); + + String get fileName => '${type}_$name.${extension.name}'; + + static String buildKey(String valueToHash) => generateValueHash(valueToHash); + + static final schema = Ack.object( + { + "name": Ack.string, + "extension": AssetExtension.schema, + "type": Ack.string, + }, + required: [ + "name", + "extension", + "type", + ], + ); + + static GeneratedAsset thumbnail(String slideKey) { + return GeneratedAsset( + name: slideKey, + extension: AssetExtension.png, + type: 'thumbnail', + ); + } + + static GeneratedAsset mermaid(String syntax) { + return GeneratedAsset( + name: GeneratedAsset.buildKey(syntax), + extension: AssetExtension.png, + type: 'mermaid', + ); + } + + static GeneratedAsset image(String url, AssetExtension extension) { + return GeneratedAsset( + name: GeneratedAsset.buildKey(url), + extension: extension, + type: 'image', + ); + } +} + +@MappableClass(includeCustomMappers: [ + DateTimeMapper(), + FileMapper(), +]) +class GeneratedAssetsReference with GeneratedAssetsReferenceMappable { + final DateTime lastModified; + final List files; + + GeneratedAssetsReference({ + required this.lastModified, + required this.files, + }); +} diff --git a/packages/superdeck_core/lib/src/models/asset_model.mapper.dart b/packages/superdeck_core/lib/src/models/asset_model.mapper.dart new file mode 100644 index 00000000..b224fbb2 --- /dev/null +++ b/packages/superdeck_core/lib/src/models/asset_model.mapper.dart @@ -0,0 +1,319 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member +// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter + +part of 'asset_model.dart'; + +class AssetExtensionMapper extends EnumMapper { + AssetExtensionMapper._(); + + static AssetExtensionMapper? _instance; + static AssetExtensionMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = AssetExtensionMapper._()); + } + return _instance!; + } + + static AssetExtension fromValue(dynamic value) { + ensureInitialized(); + return MapperContainer.globals.fromValue(value); + } + + @override + AssetExtension decode(dynamic value) { + switch (value) { + case 'png': + return AssetExtension.png; + case 'jpeg': + return AssetExtension.jpeg; + case 'gif': + return AssetExtension.gif; + case 'webp': + return AssetExtension.webp; + case 'svg': + return AssetExtension.svg; + default: + throw MapperException.unknownEnumValue(value); + } + } + + @override + dynamic encode(AssetExtension self) { + switch (self) { + case AssetExtension.png: + return 'png'; + case AssetExtension.jpeg: + return 'jpeg'; + case AssetExtension.gif: + return 'gif'; + case AssetExtension.webp: + return 'webp'; + case AssetExtension.svg: + return 'svg'; + } + } +} + +extension AssetExtensionMapperExtension on AssetExtension { + String toValue() { + AssetExtensionMapper.ensureInitialized(); + return MapperContainer.globals.toValue(this) as String; + } +} + +class GeneratedAssetMapper extends ClassMapperBase { + GeneratedAssetMapper._(); + + static GeneratedAssetMapper? _instance; + static GeneratedAssetMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = GeneratedAssetMapper._()); + AssetExtensionMapper.ensureInitialized(); + } + return _instance!; + } + + @override + final String id = 'GeneratedAsset'; + + static String _$name(GeneratedAsset v) => v.name; + static const Field _f$name = Field('name', _$name); + static AssetExtension _$extension(GeneratedAsset v) => v.extension; + static const Field _f$extension = + Field('extension', _$extension); + static String _$type(GeneratedAsset v) => v.type; + static const Field _f$type = Field('type', _$type); + + @override + final MappableFields fields = const { + #name: _f$name, + #extension: _f$extension, + #type: _f$type, + }; + @override + final bool ignoreNull = true; + + static GeneratedAsset _instantiate(DecodingData data) { + return GeneratedAsset( + name: data.dec(_f$name), + extension: data.dec(_f$extension), + type: data.dec(_f$type)); + } + + @override + final Function instantiate = _instantiate; + + static GeneratedAsset fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static GeneratedAsset fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin GeneratedAssetMappable { + String toJson() { + return GeneratedAssetMapper.ensureInitialized() + .encodeJson(this as GeneratedAsset); + } + + Map toMap() { + return GeneratedAssetMapper.ensureInitialized() + .encodeMap(this as GeneratedAsset); + } + + GeneratedAssetCopyWith + get copyWith => _GeneratedAssetCopyWithImpl( + this as GeneratedAsset, $identity, $identity); + @override + String toString() { + return GeneratedAssetMapper.ensureInitialized() + .stringifyValue(this as GeneratedAsset); + } + + @override + bool operator ==(Object other) { + return GeneratedAssetMapper.ensureInitialized() + .equalsValue(this as GeneratedAsset, other); + } + + @override + int get hashCode { + return GeneratedAssetMapper.ensureInitialized() + .hashValue(this as GeneratedAsset); + } +} + +extension GeneratedAssetValueCopy<$R, $Out> + on ObjectCopyWith<$R, GeneratedAsset, $Out> { + GeneratedAssetCopyWith<$R, GeneratedAsset, $Out> get $asGeneratedAsset => + $base.as((v, t, t2) => _GeneratedAssetCopyWithImpl(v, t, t2)); +} + +abstract class GeneratedAssetCopyWith<$R, $In extends GeneratedAsset, $Out> + implements ClassCopyWith<$R, $In, $Out> { + $R call({String? name, AssetExtension? extension, String? type}); + GeneratedAssetCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); +} + +class _GeneratedAssetCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, GeneratedAsset, $Out> + implements GeneratedAssetCopyWith<$R, GeneratedAsset, $Out> { + _GeneratedAssetCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + GeneratedAssetMapper.ensureInitialized(); + @override + $R call({String? name, AssetExtension? extension, String? type}) => + $apply(FieldCopyWithData({ + if (name != null) #name: name, + if (extension != null) #extension: extension, + if (type != null) #type: type + })); + @override + GeneratedAsset $make(CopyWithData data) => GeneratedAsset( + name: data.get(#name, or: $value.name), + extension: data.get(#extension, or: $value.extension), + type: data.get(#type, or: $value.type)); + + @override + GeneratedAssetCopyWith<$R2, GeneratedAsset, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _GeneratedAssetCopyWithImpl($value, $cast, t); +} + +class GeneratedAssetsReferenceMapper + extends ClassMapperBase { + GeneratedAssetsReferenceMapper._(); + + static GeneratedAssetsReferenceMapper? _instance; + static GeneratedAssetsReferenceMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals + .use(_instance = GeneratedAssetsReferenceMapper._()); + MapperContainer.globals.useAll([DateTimeMapper(), FileMapper()]); + } + return _instance!; + } + + @override + final String id = 'GeneratedAssetsReference'; + + static DateTime _$lastModified(GeneratedAssetsReference v) => v.lastModified; + static const Field _f$lastModified = + Field('lastModified', _$lastModified, key: r'last_modified'); + static List _$files(GeneratedAssetsReference v) => v.files; + static const Field> _f$files = + Field('files', _$files); + + @override + final MappableFields fields = const { + #lastModified: _f$lastModified, + #files: _f$files, + }; + @override + final bool ignoreNull = true; + + static GeneratedAssetsReference _instantiate(DecodingData data) { + return GeneratedAssetsReference( + lastModified: data.dec(_f$lastModified), files: data.dec(_f$files)); + } + + @override + final Function instantiate = _instantiate; + + static GeneratedAssetsReference fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static GeneratedAssetsReference fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin GeneratedAssetsReferenceMappable { + String toJson() { + return GeneratedAssetsReferenceMapper.ensureInitialized() + .encodeJson(this as GeneratedAssetsReference); + } + + Map toMap() { + return GeneratedAssetsReferenceMapper.ensureInitialized() + .encodeMap(this as GeneratedAssetsReference); + } + + GeneratedAssetsReferenceCopyWith + get copyWith => _GeneratedAssetsReferenceCopyWithImpl( + this as GeneratedAssetsReference, $identity, $identity); + @override + String toString() { + return GeneratedAssetsReferenceMapper.ensureInitialized() + .stringifyValue(this as GeneratedAssetsReference); + } + + @override + bool operator ==(Object other) { + return GeneratedAssetsReferenceMapper.ensureInitialized() + .equalsValue(this as GeneratedAssetsReference, other); + } + + @override + int get hashCode { + return GeneratedAssetsReferenceMapper.ensureInitialized() + .hashValue(this as GeneratedAssetsReference); + } +} + +extension GeneratedAssetsReferenceValueCopy<$R, $Out> + on ObjectCopyWith<$R, GeneratedAssetsReference, $Out> { + GeneratedAssetsReferenceCopyWith<$R, GeneratedAssetsReference, $Out> + get $asGeneratedAssetsReference => $base + .as((v, t, t2) => _GeneratedAssetsReferenceCopyWithImpl(v, t, t2)); +} + +abstract class GeneratedAssetsReferenceCopyWith< + $R, + $In extends GeneratedAssetsReference, + $Out> implements ClassCopyWith<$R, $In, $Out> { + ListCopyWith<$R, File, ObjectCopyWith<$R, File, File>> get files; + $R call({DateTime? lastModified, List? files}); + GeneratedAssetsReferenceCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); +} + +class _GeneratedAssetsReferenceCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, GeneratedAssetsReference, $Out> + implements + GeneratedAssetsReferenceCopyWith<$R, GeneratedAssetsReference, $Out> { + _GeneratedAssetsReferenceCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + GeneratedAssetsReferenceMapper.ensureInitialized(); + @override + ListCopyWith<$R, File, ObjectCopyWith<$R, File, File>> get files => + ListCopyWith($value.files, (v, t) => ObjectCopyWith(v, $identity, t), + (v) => call(files: v)); + @override + $R call({DateTime? lastModified, List? files}) => + $apply(FieldCopyWithData({ + if (lastModified != null) #lastModified: lastModified, + if (files != null) #files: files + })); + @override + GeneratedAssetsReference $make(CopyWithData data) => GeneratedAssetsReference( + lastModified: data.get(#lastModified, or: $value.lastModified), + files: data.get(#files, or: $value.files)); + + @override + GeneratedAssetsReferenceCopyWith<$R2, GeneratedAssetsReference, $Out2> + $chain<$R2, $Out2>(Then<$Out2, $R2> t) => + _GeneratedAssetsReferenceCopyWithImpl($value, $cast, t); +} diff --git a/packages/superdeck_core/lib/src/models/block_model.dart b/packages/superdeck_core/lib/src/models/block_model.dart new file mode 100644 index 00000000..af3a8e64 --- /dev/null +++ b/packages/superdeck_core/lib/src/models/block_model.dart @@ -0,0 +1,251 @@ +import 'package:dart_mappable/dart_mappable.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +import '../helpers/mappers.dart'; + +part 'block_model.mapper.dart'; + +@MappableClass( + discriminatorKey: 'type', +) +sealed class Block with BlockMappable { + final String type; + final ContentAlignment? align; + final int flex; + final bool scrollable; + Block({ + required this.type, + this.align, + this.flex = 1, + this.scrollable = false, + }); + + static final schema = Ack.object( + { + 'type': Ack.string, + 'align': ContentAlignment.schema.nullable(), + 'flex': Ack.int, + 'scrollable': Ack.boolean, + }, + required: ['type'], + additionalProperties: true, + ); + + static Block parse(Map map) { + typeSchema.validateOrThrow(map); + return BlockMapper.fromMap(map); + } + + static final typeSchema = Ack.discriminated( + discriminatorKey: 'type', + schemas: { + ColumnBlock.key: ColumnBlock.schema, + DartPadBlock.key: DartPadBlock.schema, + WidgetBlock.key: WidgetBlock.schema, + ImageBlock.key: ImageBlock.schema, + }, + ); +} + +@MappableClass( + includeCustomMappers: [NullIfEmptyBlock()], + discriminatorValue: SectionBlock.key, +) +class SectionBlock extends Block with SectionBlockMappable { + late final List blocks; + + static const key = 'section'; + + SectionBlock( + List? blocks, { + super.align, + super.flex, + super.scrollable, + }) : super(type: key) { + this.blocks = blocks ?? []; + } + + int get totalBlockFlex { + return blocks.fold(0, (total, block) => total + block.flex); + } + + static SectionBlock text(String content) { + return SectionBlock([ColumnBlock(content)]); + } + + static final schema = Block.schema.extend( + { + 'blocks': Block.typeSchema.list, + }, + ); +} + +@MappableClass(discriminatorValue: ColumnBlock.key) +class ColumnBlock extends Block with ColumnBlockMappable { + static const key = 'column'; + late final String content; + ColumnBlock( + String? content, { + super.align, + super.flex, + super.scrollable, + }) : super(type: key) { + this.content = content ?? ''; + } + + static final schema = Block.schema.extend( + { + 'content': Ack.string, + }, + ); +} + +@MappableEnum() +enum DartPadTheme { + dark, + light; + + static final schema = Ack.enumValues(DartPadTheme.values); +} + +@MappableClass(discriminatorValue: DartPadBlock.key) +class DartPadBlock extends Block with DartPadBlockMappable { + final String id; + final DartPadTheme? theme; + final bool embed; + final bool run; + + static const key = 'dartpad'; + + DartPadBlock({ + required this.id, + this.theme, + this.embed = true, + this.run = true, + super.align, + super.flex, + super.scrollable, + }) : super(type: key); + + String getDartPadUrl() { + return 'https://dartpad.dev/?id=$id&theme=$theme&embed=$embed&run=$run'; + } + + static final schema = Block.schema.extend( + { + 'id': Ack.string, + 'theme': DartPadTheme.schema.nullable(), + 'embed': Ack.boolean, + 'code': Ack.string, + }, + required: [ + "id", + ], + ); +} + +@MappableClass(discriminatorValue: ImageBlock.key) +class ImageBlock extends Block with ImageBlockMappable { + static const key = 'image'; + final GeneratedAsset asset; + final ImageFit? fit; + final double? width; + final double? height; + ImageBlock({ + required this.asset, + this.fit, + this.width, + this.height, + super.align, + super.flex, + super.scrollable, + }) : super(type: key); + + static final schema = Block.schema.extend( + { + "fit": ImageFit.schema.nullable(), + "asset": GeneratedAsset.schema(), + "width": Ack.double.nullable(), + "height": Ack.double.nullable(), + }, + required: [ + "asset", + ], + ); +} + +@MappableEnum() +enum ImageFit { + fill, + contain, + cover, + fitWidth, + fitHeight, + none, + scaleDown; + + static final schema = Ack.enumValues(ImageFit.values); +} + +@MappableClass( + discriminatorValue: WidgetBlock.key, + hook: UnmappedPropertiesHook('args'), +) +class WidgetBlock extends Block with WidgetBlockMappable { + static const key = 'widget'; + final Map args; + final String name; + @override + WidgetBlock({ + required this.name, + this.args = const {}, + super.align, + super.flex, + super.scrollable, + }) : super(type: key); + + static final schema = Block.schema.extend( + { + "name": Ack.string, + }, + required: [ + "name", + ], + additionalProperties: true, + ); +} + +@MappableEnum() +enum ContentAlignment { + topLeft, + topCenter, + topRight, + centerLeft, + center, + centerRight, + bottomLeft, + bottomCenter, + bottomRight; + + static final schema = Ack.enumValues(ContentAlignment.values); +} + +extension StringColumnExt on String { + ColumnBlock column() => ColumnBlock(this); +} + +extension BlockExt on Block { + Block alignCenter() => copyWith(align: ContentAlignment.center); + Block alignCenterLeft() => copyWith(align: ContentAlignment.centerLeft); + Block alignCenterRight() => copyWith(align: ContentAlignment.centerRight); + Block alignTopLeft() => copyWith(align: ContentAlignment.topLeft); + Block alignTopCenter() => copyWith(align: ContentAlignment.topCenter); + Block alignTopRight() => copyWith(align: ContentAlignment.topRight); + Block alignBottomLeft() => copyWith(align: ContentAlignment.bottomLeft); + Block alignBottomCenter() => copyWith(align: ContentAlignment.bottomCenter); + Block alignBottomRight() => copyWith(align: ContentAlignment.bottomRight); + + Block flex(int flex) => copyWith(flex: flex); + Block scrollable([bool scrollable = true]) => + copyWith(scrollable: scrollable); +} diff --git a/packages/superdeck_core/lib/src/models/block_model.mapper.dart b/packages/superdeck_core/lib/src/models/block_model.mapper.dart new file mode 100644 index 00000000..52f95eb7 --- /dev/null +++ b/packages/superdeck_core/lib/src/models/block_model.mapper.dart @@ -0,0 +1,1099 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member +// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter + +part of 'block_model.dart'; + +class DartPadThemeMapper extends EnumMapper { + DartPadThemeMapper._(); + + static DartPadThemeMapper? _instance; + static DartPadThemeMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = DartPadThemeMapper._()); + } + return _instance!; + } + + static DartPadTheme fromValue(dynamic value) { + ensureInitialized(); + return MapperContainer.globals.fromValue(value); + } + + @override + DartPadTheme decode(dynamic value) { + switch (value) { + case 'dark': + return DartPadTheme.dark; + case 'light': + return DartPadTheme.light; + default: + throw MapperException.unknownEnumValue(value); + } + } + + @override + dynamic encode(DartPadTheme self) { + switch (self) { + case DartPadTheme.dark: + return 'dark'; + case DartPadTheme.light: + return 'light'; + } + } +} + +extension DartPadThemeMapperExtension on DartPadTheme { + String toValue() { + DartPadThemeMapper.ensureInitialized(); + return MapperContainer.globals.toValue(this) as String; + } +} + +class ImageFitMapper extends EnumMapper { + ImageFitMapper._(); + + static ImageFitMapper? _instance; + static ImageFitMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = ImageFitMapper._()); + } + return _instance!; + } + + static ImageFit fromValue(dynamic value) { + ensureInitialized(); + return MapperContainer.globals.fromValue(value); + } + + @override + ImageFit decode(dynamic value) { + switch (value) { + case 'fill': + return ImageFit.fill; + case 'contain': + return ImageFit.contain; + case 'cover': + return ImageFit.cover; + case 'fit_width': + return ImageFit.fitWidth; + case 'fit_height': + return ImageFit.fitHeight; + case 'none': + return ImageFit.none; + case 'scale_down': + return ImageFit.scaleDown; + default: + throw MapperException.unknownEnumValue(value); + } + } + + @override + dynamic encode(ImageFit self) { + switch (self) { + case ImageFit.fill: + return 'fill'; + case ImageFit.contain: + return 'contain'; + case ImageFit.cover: + return 'cover'; + case ImageFit.fitWidth: + return 'fit_width'; + case ImageFit.fitHeight: + return 'fit_height'; + case ImageFit.none: + return 'none'; + case ImageFit.scaleDown: + return 'scale_down'; + } + } +} + +extension ImageFitMapperExtension on ImageFit { + String toValue() { + ImageFitMapper.ensureInitialized(); + return MapperContainer.globals.toValue(this) as String; + } +} + +class ContentAlignmentMapper extends EnumMapper { + ContentAlignmentMapper._(); + + static ContentAlignmentMapper? _instance; + static ContentAlignmentMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = ContentAlignmentMapper._()); + } + return _instance!; + } + + static ContentAlignment fromValue(dynamic value) { + ensureInitialized(); + return MapperContainer.globals.fromValue(value); + } + + @override + ContentAlignment decode(dynamic value) { + switch (value) { + case 'top_left': + return ContentAlignment.topLeft; + case 'top_center': + return ContentAlignment.topCenter; + case 'top_right': + return ContentAlignment.topRight; + case 'center_left': + return ContentAlignment.centerLeft; + case 'center': + return ContentAlignment.center; + case 'center_right': + return ContentAlignment.centerRight; + case 'bottom_left': + return ContentAlignment.bottomLeft; + case 'bottom_center': + return ContentAlignment.bottomCenter; + case 'bottom_right': + return ContentAlignment.bottomRight; + default: + throw MapperException.unknownEnumValue(value); + } + } + + @override + dynamic encode(ContentAlignment self) { + switch (self) { + case ContentAlignment.topLeft: + return 'top_left'; + case ContentAlignment.topCenter: + return 'top_center'; + case ContentAlignment.topRight: + return 'top_right'; + case ContentAlignment.centerLeft: + return 'center_left'; + case ContentAlignment.center: + return 'center'; + case ContentAlignment.centerRight: + return 'center_right'; + case ContentAlignment.bottomLeft: + return 'bottom_left'; + case ContentAlignment.bottomCenter: + return 'bottom_center'; + case ContentAlignment.bottomRight: + return 'bottom_right'; + } + } +} + +extension ContentAlignmentMapperExtension on ContentAlignment { + String toValue() { + ContentAlignmentMapper.ensureInitialized(); + return MapperContainer.globals.toValue(this) as String; + } +} + +class BlockMapper extends ClassMapperBase { + BlockMapper._(); + + static BlockMapper? _instance; + static BlockMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = BlockMapper._()); + SectionBlockMapper.ensureInitialized(); + ColumnBlockMapper.ensureInitialized(); + DartPadBlockMapper.ensureInitialized(); + ImageBlockMapper.ensureInitialized(); + WidgetBlockMapper.ensureInitialized(); + ContentAlignmentMapper.ensureInitialized(); + } + return _instance!; + } + + @override + final String id = 'Block'; + + static String _$type(Block v) => v.type; + static const Field _f$type = Field('type', _$type); + static ContentAlignment? _$align(Block v) => v.align; + static const Field _f$align = + Field('align', _$align, opt: true); + static int _$flex(Block v) => v.flex; + static const Field _f$flex = + Field('flex', _$flex, opt: true, def: 1); + static bool _$scrollable(Block v) => v.scrollable; + static const Field _f$scrollable = + Field('scrollable', _$scrollable, opt: true, def: false); + + @override + final MappableFields fields = const { + #type: _f$type, + #align: _f$align, + #flex: _f$flex, + #scrollable: _f$scrollable, + }; + @override + final bool ignoreNull = true; + + static Block _instantiate(DecodingData data) { + throw MapperException.missingSubclass( + 'Block', 'type', '${data.value['type']}'); + } + + @override + final Function instantiate = _instantiate; + + static Block fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static Block fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin BlockMappable { + String toJson(); + Map toMap(); + BlockCopyWith get copyWith; +} + +abstract class BlockCopyWith<$R, $In extends Block, $Out> + implements ClassCopyWith<$R, $In, $Out> { + $R call({ContentAlignment? align, int? flex, bool? scrollable}); + BlockCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class SectionBlockMapper extends SubClassMapperBase { + SectionBlockMapper._(); + + static SectionBlockMapper? _instance; + static SectionBlockMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = SectionBlockMapper._()); + BlockMapper.ensureInitialized().addSubMapper(_instance!); + MapperContainer.globals.useAll([NullIfEmptyBlock()]); + BlockMapper.ensureInitialized(); + ContentAlignmentMapper.ensureInitialized(); + } + return _instance!; + } + + @override + final String id = 'SectionBlock'; + + static List _$blocks(SectionBlock v) => v.blocks; + static const Field> _f$blocks = + Field('blocks', _$blocks); + static ContentAlignment? _$align(SectionBlock v) => v.align; + static const Field _f$align = + Field('align', _$align, opt: true); + static int _$flex(SectionBlock v) => v.flex; + static const Field _f$flex = + Field('flex', _$flex, opt: true, def: 1); + static bool _$scrollable(SectionBlock v) => v.scrollable; + static const Field _f$scrollable = + Field('scrollable', _$scrollable, opt: true, def: false); + static String _$type(SectionBlock v) => v.type; + static const Field _f$type = + Field('type', _$type, mode: FieldMode.member); + + @override + final MappableFields fields = const { + #blocks: _f$blocks, + #align: _f$align, + #flex: _f$flex, + #scrollable: _f$scrollable, + #type: _f$type, + }; + @override + final bool ignoreNull = true; + + @override + final String discriminatorKey = 'type'; + @override + final dynamic discriminatorValue = SectionBlock.key; + @override + late final ClassMapperBase superMapper = BlockMapper.ensureInitialized(); + + static SectionBlock _instantiate(DecodingData data) { + return SectionBlock(data.dec(_f$blocks), + align: data.dec(_f$align), + flex: data.dec(_f$flex), + scrollable: data.dec(_f$scrollable)); + } + + @override + final Function instantiate = _instantiate; + + static SectionBlock fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static SectionBlock fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin SectionBlockMappable { + String toJson() { + return SectionBlockMapper.ensureInitialized() + .encodeJson(this as SectionBlock); + } + + Map toMap() { + return SectionBlockMapper.ensureInitialized() + .encodeMap(this as SectionBlock); + } + + SectionBlockCopyWith get copyWith => + _SectionBlockCopyWithImpl(this as SectionBlock, $identity, $identity); + @override + String toString() { + return SectionBlockMapper.ensureInitialized() + .stringifyValue(this as SectionBlock); + } + + @override + bool operator ==(Object other) { + return SectionBlockMapper.ensureInitialized() + .equalsValue(this as SectionBlock, other); + } + + @override + int get hashCode { + return SectionBlockMapper.ensureInitialized() + .hashValue(this as SectionBlock); + } +} + +extension SectionBlockValueCopy<$R, $Out> + on ObjectCopyWith<$R, SectionBlock, $Out> { + SectionBlockCopyWith<$R, SectionBlock, $Out> get $asSectionBlock => + $base.as((v, t, t2) => _SectionBlockCopyWithImpl(v, t, t2)); +} + +abstract class SectionBlockCopyWith<$R, $In extends SectionBlock, $Out> + implements BlockCopyWith<$R, $In, $Out> { + ListCopyWith<$R, Block, BlockCopyWith<$R, Block, Block>> get blocks; + @override + $R call( + {List? blocks, + ContentAlignment? align, + int? flex, + bool? scrollable}); + SectionBlockCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class _SectionBlockCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, SectionBlock, $Out> + implements SectionBlockCopyWith<$R, SectionBlock, $Out> { + _SectionBlockCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + SectionBlockMapper.ensureInitialized(); + @override + ListCopyWith<$R, Block, BlockCopyWith<$R, Block, Block>> get blocks => + ListCopyWith($value.blocks, (v, t) => v.copyWith.$chain(t), + (v) => call(blocks: v)); + @override + $R call( + {Object? blocks = $none, + Object? align = $none, + int? flex, + bool? scrollable}) => + $apply(FieldCopyWithData({ + if (blocks != $none) #blocks: blocks, + if (align != $none) #align: align, + if (flex != null) #flex: flex, + if (scrollable != null) #scrollable: scrollable + })); + @override + SectionBlock $make(CopyWithData data) => + SectionBlock(data.get(#blocks, or: $value.blocks), + align: data.get(#align, or: $value.align), + flex: data.get(#flex, or: $value.flex), + scrollable: data.get(#scrollable, or: $value.scrollable)); + + @override + SectionBlockCopyWith<$R2, SectionBlock, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _SectionBlockCopyWithImpl($value, $cast, t); +} + +class ColumnBlockMapper extends SubClassMapperBase { + ColumnBlockMapper._(); + + static ColumnBlockMapper? _instance; + static ColumnBlockMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = ColumnBlockMapper._()); + BlockMapper.ensureInitialized().addSubMapper(_instance!); + ContentAlignmentMapper.ensureInitialized(); + } + return _instance!; + } + + @override + final String id = 'ColumnBlock'; + + static String _$content(ColumnBlock v) => v.content; + static const Field _f$content = + Field('content', _$content); + static ContentAlignment? _$align(ColumnBlock v) => v.align; + static const Field _f$align = + Field('align', _$align, opt: true); + static int _$flex(ColumnBlock v) => v.flex; + static const Field _f$flex = + Field('flex', _$flex, opt: true, def: 1); + static bool _$scrollable(ColumnBlock v) => v.scrollable; + static const Field _f$scrollable = + Field('scrollable', _$scrollable, opt: true, def: false); + static String _$type(ColumnBlock v) => v.type; + static const Field _f$type = + Field('type', _$type, mode: FieldMode.member); + + @override + final MappableFields fields = const { + #content: _f$content, + #align: _f$align, + #flex: _f$flex, + #scrollable: _f$scrollable, + #type: _f$type, + }; + @override + final bool ignoreNull = true; + + @override + final String discriminatorKey = 'type'; + @override + final dynamic discriminatorValue = ColumnBlock.key; + @override + late final ClassMapperBase superMapper = BlockMapper.ensureInitialized(); + + static ColumnBlock _instantiate(DecodingData data) { + return ColumnBlock(data.dec(_f$content), + align: data.dec(_f$align), + flex: data.dec(_f$flex), + scrollable: data.dec(_f$scrollable)); + } + + @override + final Function instantiate = _instantiate; + + static ColumnBlock fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static ColumnBlock fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin ColumnBlockMappable { + String toJson() { + return ColumnBlockMapper.ensureInitialized() + .encodeJson(this as ColumnBlock); + } + + Map toMap() { + return ColumnBlockMapper.ensureInitialized() + .encodeMap(this as ColumnBlock); + } + + ColumnBlockCopyWith get copyWith => + _ColumnBlockCopyWithImpl(this as ColumnBlock, $identity, $identity); + @override + String toString() { + return ColumnBlockMapper.ensureInitialized() + .stringifyValue(this as ColumnBlock); + } + + @override + bool operator ==(Object other) { + return ColumnBlockMapper.ensureInitialized() + .equalsValue(this as ColumnBlock, other); + } + + @override + int get hashCode { + return ColumnBlockMapper.ensureInitialized().hashValue(this as ColumnBlock); + } +} + +extension ColumnBlockValueCopy<$R, $Out> + on ObjectCopyWith<$R, ColumnBlock, $Out> { + ColumnBlockCopyWith<$R, ColumnBlock, $Out> get $asColumnBlock => + $base.as((v, t, t2) => _ColumnBlockCopyWithImpl(v, t, t2)); +} + +abstract class ColumnBlockCopyWith<$R, $In extends ColumnBlock, $Out> + implements BlockCopyWith<$R, $In, $Out> { + @override + $R call( + {String? content, ContentAlignment? align, int? flex, bool? scrollable}); + ColumnBlockCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class _ColumnBlockCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, ColumnBlock, $Out> + implements ColumnBlockCopyWith<$R, ColumnBlock, $Out> { + _ColumnBlockCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + ColumnBlockMapper.ensureInitialized(); + @override + $R call( + {Object? content = $none, + Object? align = $none, + int? flex, + bool? scrollable}) => + $apply(FieldCopyWithData({ + if (content != $none) #content: content, + if (align != $none) #align: align, + if (flex != null) #flex: flex, + if (scrollable != null) #scrollable: scrollable + })); + @override + ColumnBlock $make(CopyWithData data) => + ColumnBlock(data.get(#content, or: $value.content), + align: data.get(#align, or: $value.align), + flex: data.get(#flex, or: $value.flex), + scrollable: data.get(#scrollable, or: $value.scrollable)); + + @override + ColumnBlockCopyWith<$R2, ColumnBlock, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _ColumnBlockCopyWithImpl($value, $cast, t); +} + +class DartPadBlockMapper extends SubClassMapperBase { + DartPadBlockMapper._(); + + static DartPadBlockMapper? _instance; + static DartPadBlockMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = DartPadBlockMapper._()); + BlockMapper.ensureInitialized().addSubMapper(_instance!); + DartPadThemeMapper.ensureInitialized(); + ContentAlignmentMapper.ensureInitialized(); + } + return _instance!; + } + + @override + final String id = 'DartPadBlock'; + + static String _$id(DartPadBlock v) => v.id; + static const Field _f$id = Field('id', _$id); + static DartPadTheme? _$theme(DartPadBlock v) => v.theme; + static const Field _f$theme = + Field('theme', _$theme, opt: true); + static bool _$embed(DartPadBlock v) => v.embed; + static const Field _f$embed = + Field('embed', _$embed, opt: true, def: true); + static bool _$run(DartPadBlock v) => v.run; + static const Field _f$run = + Field('run', _$run, opt: true, def: true); + static ContentAlignment? _$align(DartPadBlock v) => v.align; + static const Field _f$align = + Field('align', _$align, opt: true); + static int _$flex(DartPadBlock v) => v.flex; + static const Field _f$flex = + Field('flex', _$flex, opt: true, def: 1); + static bool _$scrollable(DartPadBlock v) => v.scrollable; + static const Field _f$scrollable = + Field('scrollable', _$scrollable, opt: true, def: false); + static String _$type(DartPadBlock v) => v.type; + static const Field _f$type = + Field('type', _$type, mode: FieldMode.member); + + @override + final MappableFields fields = const { + #id: _f$id, + #theme: _f$theme, + #embed: _f$embed, + #run: _f$run, + #align: _f$align, + #flex: _f$flex, + #scrollable: _f$scrollable, + #type: _f$type, + }; + @override + final bool ignoreNull = true; + + @override + final String discriminatorKey = 'type'; + @override + final dynamic discriminatorValue = DartPadBlock.key; + @override + late final ClassMapperBase superMapper = BlockMapper.ensureInitialized(); + + static DartPadBlock _instantiate(DecodingData data) { + return DartPadBlock( + id: data.dec(_f$id), + theme: data.dec(_f$theme), + embed: data.dec(_f$embed), + run: data.dec(_f$run), + align: data.dec(_f$align), + flex: data.dec(_f$flex), + scrollable: data.dec(_f$scrollable)); + } + + @override + final Function instantiate = _instantiate; + + static DartPadBlock fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static DartPadBlock fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin DartPadBlockMappable { + String toJson() { + return DartPadBlockMapper.ensureInitialized() + .encodeJson(this as DartPadBlock); + } + + Map toMap() { + return DartPadBlockMapper.ensureInitialized() + .encodeMap(this as DartPadBlock); + } + + DartPadBlockCopyWith get copyWith => + _DartPadBlockCopyWithImpl(this as DartPadBlock, $identity, $identity); + @override + String toString() { + return DartPadBlockMapper.ensureInitialized() + .stringifyValue(this as DartPadBlock); + } + + @override + bool operator ==(Object other) { + return DartPadBlockMapper.ensureInitialized() + .equalsValue(this as DartPadBlock, other); + } + + @override + int get hashCode { + return DartPadBlockMapper.ensureInitialized() + .hashValue(this as DartPadBlock); + } +} + +extension DartPadBlockValueCopy<$R, $Out> + on ObjectCopyWith<$R, DartPadBlock, $Out> { + DartPadBlockCopyWith<$R, DartPadBlock, $Out> get $asDartPadBlock => + $base.as((v, t, t2) => _DartPadBlockCopyWithImpl(v, t, t2)); +} + +abstract class DartPadBlockCopyWith<$R, $In extends DartPadBlock, $Out> + implements BlockCopyWith<$R, $In, $Out> { + @override + $R call( + {String? id, + DartPadTheme? theme, + bool? embed, + bool? run, + ContentAlignment? align, + int? flex, + bool? scrollable}); + DartPadBlockCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class _DartPadBlockCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, DartPadBlock, $Out> + implements DartPadBlockCopyWith<$R, DartPadBlock, $Out> { + _DartPadBlockCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + DartPadBlockMapper.ensureInitialized(); + @override + $R call( + {String? id, + Object? theme = $none, + bool? embed, + bool? run, + Object? align = $none, + int? flex, + bool? scrollable}) => + $apply(FieldCopyWithData({ + if (id != null) #id: id, + if (theme != $none) #theme: theme, + if (embed != null) #embed: embed, + if (run != null) #run: run, + if (align != $none) #align: align, + if (flex != null) #flex: flex, + if (scrollable != null) #scrollable: scrollable + })); + @override + DartPadBlock $make(CopyWithData data) => DartPadBlock( + id: data.get(#id, or: $value.id), + theme: data.get(#theme, or: $value.theme), + embed: data.get(#embed, or: $value.embed), + run: data.get(#run, or: $value.run), + align: data.get(#align, or: $value.align), + flex: data.get(#flex, or: $value.flex), + scrollable: data.get(#scrollable, or: $value.scrollable)); + + @override + DartPadBlockCopyWith<$R2, DartPadBlock, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _DartPadBlockCopyWithImpl($value, $cast, t); +} + +class ImageBlockMapper extends SubClassMapperBase { + ImageBlockMapper._(); + + static ImageBlockMapper? _instance; + static ImageBlockMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = ImageBlockMapper._()); + BlockMapper.ensureInitialized().addSubMapper(_instance!); + GeneratedAssetMapper.ensureInitialized(); + ImageFitMapper.ensureInitialized(); + ContentAlignmentMapper.ensureInitialized(); + } + return _instance!; + } + + @override + final String id = 'ImageBlock'; + + static GeneratedAsset _$asset(ImageBlock v) => v.asset; + static const Field _f$asset = + Field('asset', _$asset); + static ImageFit? _$fit(ImageBlock v) => v.fit; + static const Field _f$fit = + Field('fit', _$fit, opt: true); + static double? _$width(ImageBlock v) => v.width; + static const Field _f$width = + Field('width', _$width, opt: true); + static double? _$height(ImageBlock v) => v.height; + static const Field _f$height = + Field('height', _$height, opt: true); + static ContentAlignment? _$align(ImageBlock v) => v.align; + static const Field _f$align = + Field('align', _$align, opt: true); + static int _$flex(ImageBlock v) => v.flex; + static const Field _f$flex = + Field('flex', _$flex, opt: true, def: 1); + static bool _$scrollable(ImageBlock v) => v.scrollable; + static const Field _f$scrollable = + Field('scrollable', _$scrollable, opt: true, def: false); + static String _$type(ImageBlock v) => v.type; + static const Field _f$type = + Field('type', _$type, mode: FieldMode.member); + + @override + final MappableFields fields = const { + #asset: _f$asset, + #fit: _f$fit, + #width: _f$width, + #height: _f$height, + #align: _f$align, + #flex: _f$flex, + #scrollable: _f$scrollable, + #type: _f$type, + }; + @override + final bool ignoreNull = true; + + @override + final String discriminatorKey = 'type'; + @override + final dynamic discriminatorValue = ImageBlock.key; + @override + late final ClassMapperBase superMapper = BlockMapper.ensureInitialized(); + + static ImageBlock _instantiate(DecodingData data) { + return ImageBlock( + asset: data.dec(_f$asset), + fit: data.dec(_f$fit), + width: data.dec(_f$width), + height: data.dec(_f$height), + align: data.dec(_f$align), + flex: data.dec(_f$flex), + scrollable: data.dec(_f$scrollable)); + } + + @override + final Function instantiate = _instantiate; + + static ImageBlock fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static ImageBlock fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin ImageBlockMappable { + String toJson() { + return ImageBlockMapper.ensureInitialized() + .encodeJson(this as ImageBlock); + } + + Map toMap() { + return ImageBlockMapper.ensureInitialized() + .encodeMap(this as ImageBlock); + } + + ImageBlockCopyWith get copyWith => + _ImageBlockCopyWithImpl(this as ImageBlock, $identity, $identity); + @override + String toString() { + return ImageBlockMapper.ensureInitialized() + .stringifyValue(this as ImageBlock); + } + + @override + bool operator ==(Object other) { + return ImageBlockMapper.ensureInitialized() + .equalsValue(this as ImageBlock, other); + } + + @override + int get hashCode { + return ImageBlockMapper.ensureInitialized().hashValue(this as ImageBlock); + } +} + +extension ImageBlockValueCopy<$R, $Out> + on ObjectCopyWith<$R, ImageBlock, $Out> { + ImageBlockCopyWith<$R, ImageBlock, $Out> get $asImageBlock => + $base.as((v, t, t2) => _ImageBlockCopyWithImpl(v, t, t2)); +} + +abstract class ImageBlockCopyWith<$R, $In extends ImageBlock, $Out> + implements BlockCopyWith<$R, $In, $Out> { + GeneratedAssetCopyWith<$R, GeneratedAsset, GeneratedAsset> get asset; + @override + $R call( + {GeneratedAsset? asset, + ImageFit? fit, + double? width, + double? height, + ContentAlignment? align, + int? flex, + bool? scrollable}); + ImageBlockCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class _ImageBlockCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, ImageBlock, $Out> + implements ImageBlockCopyWith<$R, ImageBlock, $Out> { + _ImageBlockCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + ImageBlockMapper.ensureInitialized(); + @override + GeneratedAssetCopyWith<$R, GeneratedAsset, GeneratedAsset> get asset => + $value.asset.copyWith.$chain((v) => call(asset: v)); + @override + $R call( + {GeneratedAsset? asset, + Object? fit = $none, + Object? width = $none, + Object? height = $none, + Object? align = $none, + int? flex, + bool? scrollable}) => + $apply(FieldCopyWithData({ + if (asset != null) #asset: asset, + if (fit != $none) #fit: fit, + if (width != $none) #width: width, + if (height != $none) #height: height, + if (align != $none) #align: align, + if (flex != null) #flex: flex, + if (scrollable != null) #scrollable: scrollable + })); + @override + ImageBlock $make(CopyWithData data) => ImageBlock( + asset: data.get(#asset, or: $value.asset), + fit: data.get(#fit, or: $value.fit), + width: data.get(#width, or: $value.width), + height: data.get(#height, or: $value.height), + align: data.get(#align, or: $value.align), + flex: data.get(#flex, or: $value.flex), + scrollable: data.get(#scrollable, or: $value.scrollable)); + + @override + ImageBlockCopyWith<$R2, ImageBlock, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _ImageBlockCopyWithImpl($value, $cast, t); +} + +class WidgetBlockMapper extends SubClassMapperBase { + WidgetBlockMapper._(); + + static WidgetBlockMapper? _instance; + static WidgetBlockMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = WidgetBlockMapper._()); + BlockMapper.ensureInitialized().addSubMapper(_instance!); + ContentAlignmentMapper.ensureInitialized(); + } + return _instance!; + } + + @override + final String id = 'WidgetBlock'; + + static String _$name(WidgetBlock v) => v.name; + static const Field _f$name = Field('name', _$name); + static Map _$args(WidgetBlock v) => v.args; + static const Field> _f$args = + Field('args', _$args, opt: true, def: const {}); + static ContentAlignment? _$align(WidgetBlock v) => v.align; + static const Field _f$align = + Field('align', _$align, opt: true); + static int _$flex(WidgetBlock v) => v.flex; + static const Field _f$flex = + Field('flex', _$flex, opt: true, def: 1); + static bool _$scrollable(WidgetBlock v) => v.scrollable; + static const Field _f$scrollable = + Field('scrollable', _$scrollable, opt: true, def: false); + static String _$type(WidgetBlock v) => v.type; + static const Field _f$type = + Field('type', _$type, mode: FieldMode.member); + + @override + final MappableFields fields = const { + #name: _f$name, + #args: _f$args, + #align: _f$align, + #flex: _f$flex, + #scrollable: _f$scrollable, + #type: _f$type, + }; + @override + final bool ignoreNull = true; + + @override + final String discriminatorKey = 'type'; + @override + final dynamic discriminatorValue = WidgetBlock.key; + @override + late final ClassMapperBase superMapper = BlockMapper.ensureInitialized(); + + @override + final MappingHook hook = const UnmappedPropertiesHook('args'); + static WidgetBlock _instantiate(DecodingData data) { + return WidgetBlock( + name: data.dec(_f$name), + args: data.dec(_f$args), + align: data.dec(_f$align), + flex: data.dec(_f$flex), + scrollable: data.dec(_f$scrollable)); + } + + @override + final Function instantiate = _instantiate; + + static WidgetBlock fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static WidgetBlock fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin WidgetBlockMappable { + String toJson() { + return WidgetBlockMapper.ensureInitialized() + .encodeJson(this as WidgetBlock); + } + + Map toMap() { + return WidgetBlockMapper.ensureInitialized() + .encodeMap(this as WidgetBlock); + } + + WidgetBlockCopyWith get copyWith => + _WidgetBlockCopyWithImpl(this as WidgetBlock, $identity, $identity); + @override + String toString() { + return WidgetBlockMapper.ensureInitialized() + .stringifyValue(this as WidgetBlock); + } + + @override + bool operator ==(Object other) { + return WidgetBlockMapper.ensureInitialized() + .equalsValue(this as WidgetBlock, other); + } + + @override + int get hashCode { + return WidgetBlockMapper.ensureInitialized().hashValue(this as WidgetBlock); + } +} + +extension WidgetBlockValueCopy<$R, $Out> + on ObjectCopyWith<$R, WidgetBlock, $Out> { + WidgetBlockCopyWith<$R, WidgetBlock, $Out> get $asWidgetBlock => + $base.as((v, t, t2) => _WidgetBlockCopyWithImpl(v, t, t2)); +} + +abstract class WidgetBlockCopyWith<$R, $In extends WidgetBlock, $Out> + implements BlockCopyWith<$R, $In, $Out> { + MapCopyWith<$R, String, dynamic, ObjectCopyWith<$R, dynamic, dynamic>> + get args; + @override + $R call( + {String? name, + Map? args, + ContentAlignment? align, + int? flex, + bool? scrollable}); + WidgetBlockCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class _WidgetBlockCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, WidgetBlock, $Out> + implements WidgetBlockCopyWith<$R, WidgetBlock, $Out> { + _WidgetBlockCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + WidgetBlockMapper.ensureInitialized(); + @override + MapCopyWith<$R, String, dynamic, ObjectCopyWith<$R, dynamic, dynamic>> + get args => MapCopyWith($value.args, + (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(args: v)); + @override + $R call( + {String? name, + Map? args, + Object? align = $none, + int? flex, + bool? scrollable}) => + $apply(FieldCopyWithData({ + if (name != null) #name: name, + if (args != null) #args: args, + if (align != $none) #align: align, + if (flex != null) #flex: flex, + if (scrollable != null) #scrollable: scrollable + })); + @override + WidgetBlock $make(CopyWithData data) => WidgetBlock( + name: data.get(#name, or: $value.name), + args: data.get(#args, or: $value.args), + align: data.get(#align, or: $value.align), + flex: data.get(#flex, or: $value.flex), + scrollable: data.get(#scrollable, or: $value.scrollable)); + + @override + WidgetBlockCopyWith<$R2, WidgetBlock, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _WidgetBlockCopyWithImpl($value, $cast, t); +} diff --git a/packages/superdeck_core/lib/src/models/deck_configuration.dart b/packages/superdeck_core/lib/src/models/deck_configuration.dart new file mode 100644 index 00000000..6637c780 --- /dev/null +++ b/packages/superdeck_core/lib/src/models/deck_configuration.dart @@ -0,0 +1,43 @@ +import 'dart:io'; + +import 'package:dart_mappable/dart_mappable.dart'; +import 'package:path/path.dart' as p; + +import '../../superdeck_core.dart'; +import '../helpers/mappers.dart'; + +part 'deck_configuration.mapper.dart'; + +@MappableClass( + includeCustomMappers: [ + DirectoryMapper(), + FileMapper(), + ], +) +class DeckConfiguration with DeckConfigurationMappable { + final superdeckDir = Directory('.superdeck'); + late final deckJson = File(p.join(superdeckDir.path, 'superdeck.json')); + late final assetsDir = Directory(p.join(superdeckDir.path, 'assets')); + late final assetsRefJson = + File(p.join(superdeckDir.path, 'generated_assets.json')); + late final slidesFile = File('slides.md'); + + DeckConfiguration({ + File? slidesMarkdown, + }); + + File get pubspecFile => File('pubspec.yaml'); + + static DeckConfiguration parse(Map map) { + schema.validateOrThrow(map); + return DeckConfigurationMapper.fromMap(map); + } + + static final schema = Ack.object( + { + 'slidesMarkdown': Ack.string, + }, + ); + + static File get defaultFile => File('superdeck.yaml'); +} diff --git a/packages/superdeck_core/lib/src/models/deck_configuration.mapper.dart b/packages/superdeck_core/lib/src/models/deck_configuration.mapper.dart new file mode 100644 index 00000000..53fab5a8 --- /dev/null +++ b/packages/superdeck_core/lib/src/models/deck_configuration.mapper.dart @@ -0,0 +1,142 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member +// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter + +part of 'deck_configuration.dart'; + +class DeckConfigurationMapper extends ClassMapperBase { + DeckConfigurationMapper._(); + + static DeckConfigurationMapper? _instance; + static DeckConfigurationMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = DeckConfigurationMapper._()); + MapperContainer.globals.useAll([DirectoryMapper(), FileMapper()]); + } + return _instance!; + } + + @override + final String id = 'DeckConfiguration'; + + static const Field _f$slidesMarkdown = Field( + 'slidesMarkdown', null, + key: r'slides_markdown', mode: FieldMode.param, opt: true); + static Directory _$superdeckDir(DeckConfiguration v) => v.superdeckDir; + static const Field _f$superdeckDir = Field( + 'superdeckDir', _$superdeckDir, + key: r'superdeck_dir', mode: FieldMode.member); + static File _$deckJson(DeckConfiguration v) => v.deckJson; + static const Field _f$deckJson = + Field('deckJson', _$deckJson, key: r'deck_json', mode: FieldMode.member); + static Directory _$assetsDir(DeckConfiguration v) => v.assetsDir; + static const Field _f$assetsDir = Field( + 'assetsDir', _$assetsDir, + key: r'assets_dir', mode: FieldMode.member); + static File _$assetsRefJson(DeckConfiguration v) => v.assetsRefJson; + static const Field _f$assetsRefJson = Field( + 'assetsRefJson', _$assetsRefJson, + key: r'assets_ref_json', mode: FieldMode.member); + static File _$slidesFile(DeckConfiguration v) => v.slidesFile; + static const Field _f$slidesFile = Field( + 'slidesFile', _$slidesFile, + key: r'slides_file', mode: FieldMode.member); + + @override + final MappableFields fields = const { + #slidesMarkdown: _f$slidesMarkdown, + #superdeckDir: _f$superdeckDir, + #deckJson: _f$deckJson, + #assetsDir: _f$assetsDir, + #assetsRefJson: _f$assetsRefJson, + #slidesFile: _f$slidesFile, + }; + @override + final bool ignoreNull = true; + + static DeckConfiguration _instantiate(DecodingData data) { + return DeckConfiguration(slidesMarkdown: data.dec(_f$slidesMarkdown)); + } + + @override + final Function instantiate = _instantiate; + + static DeckConfiguration fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static DeckConfiguration fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin DeckConfigurationMappable { + String toJson() { + return DeckConfigurationMapper.ensureInitialized() + .encodeJson(this as DeckConfiguration); + } + + Map toMap() { + return DeckConfigurationMapper.ensureInitialized() + .encodeMap(this as DeckConfiguration); + } + + DeckConfigurationCopyWith + get copyWith => _DeckConfigurationCopyWithImpl( + this as DeckConfiguration, $identity, $identity); + @override + String toString() { + return DeckConfigurationMapper.ensureInitialized() + .stringifyValue(this as DeckConfiguration); + } + + @override + bool operator ==(Object other) { + return DeckConfigurationMapper.ensureInitialized() + .equalsValue(this as DeckConfiguration, other); + } + + @override + int get hashCode { + return DeckConfigurationMapper.ensureInitialized() + .hashValue(this as DeckConfiguration); + } +} + +extension DeckConfigurationValueCopy<$R, $Out> + on ObjectCopyWith<$R, DeckConfiguration, $Out> { + DeckConfigurationCopyWith<$R, DeckConfiguration, $Out> + get $asDeckConfiguration => + $base.as((v, t, t2) => _DeckConfigurationCopyWithImpl(v, t, t2)); +} + +abstract class DeckConfigurationCopyWith<$R, $In extends DeckConfiguration, + $Out> implements ClassCopyWith<$R, $In, $Out> { + $R call({File? slidesMarkdown}); + DeckConfigurationCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); +} + +class _DeckConfigurationCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, DeckConfiguration, $Out> + implements DeckConfigurationCopyWith<$R, DeckConfiguration, $Out> { + _DeckConfigurationCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + DeckConfigurationMapper.ensureInitialized(); + @override + $R call({File? slidesMarkdown}) => + $apply(FieldCopyWithData({#slidesMarkdown: slidesMarkdown})); + @override + DeckConfiguration $make(CopyWithData data) => + DeckConfiguration(slidesMarkdown: data.get(#slidesMarkdown)); + + @override + DeckConfigurationCopyWith<$R2, DeckConfiguration, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _DeckConfigurationCopyWithImpl($value, $cast, t); +} diff --git a/packages/superdeck_core/lib/src/models/deck_reference.dart b/packages/superdeck_core/lib/src/models/deck_reference.dart new file mode 100644 index 00000000..a6ad6f90 --- /dev/null +++ b/packages/superdeck_core/lib/src/models/deck_reference.dart @@ -0,0 +1,15 @@ +import 'package:dart_mappable/dart_mappable.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +part 'deck_reference.mapper.dart'; + +@MappableClass() +class DeckReference with DeckReferenceMappable { + const DeckReference({ + required this.slides, + required this.config, + }); + + final List slides; + final DeckConfiguration config; +} diff --git a/packages/superdeck_core/lib/src/models/deck_reference.mapper.dart b/packages/superdeck_core/lib/src/models/deck_reference.mapper.dart new file mode 100644 index 00000000..2c374423 --- /dev/null +++ b/packages/superdeck_core/lib/src/models/deck_reference.mapper.dart @@ -0,0 +1,135 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member +// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter + +part of 'deck_reference.dart'; + +class DeckReferenceMapper extends ClassMapperBase { + DeckReferenceMapper._(); + + static DeckReferenceMapper? _instance; + static DeckReferenceMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = DeckReferenceMapper._()); + SlideMapper.ensureInitialized(); + DeckConfigurationMapper.ensureInitialized(); + } + return _instance!; + } + + @override + final String id = 'DeckReference'; + + static List _$slides(DeckReference v) => v.slides; + static const Field> _f$slides = + Field('slides', _$slides); + static DeckConfiguration _$config(DeckReference v) => v.config; + static const Field _f$config = + Field('config', _$config); + + @override + final MappableFields fields = const { + #slides: _f$slides, + #config: _f$config, + }; + @override + final bool ignoreNull = true; + + static DeckReference _instantiate(DecodingData data) { + return DeckReference( + slides: data.dec(_f$slides), config: data.dec(_f$config)); + } + + @override + final Function instantiate = _instantiate; + + static DeckReference fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static DeckReference fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin DeckReferenceMappable { + String toJson() { + return DeckReferenceMapper.ensureInitialized() + .encodeJson(this as DeckReference); + } + + Map toMap() { + return DeckReferenceMapper.ensureInitialized() + .encodeMap(this as DeckReference); + } + + DeckReferenceCopyWith + get copyWith => _DeckReferenceCopyWithImpl( + this as DeckReference, $identity, $identity); + @override + String toString() { + return DeckReferenceMapper.ensureInitialized() + .stringifyValue(this as DeckReference); + } + + @override + bool operator ==(Object other) { + return DeckReferenceMapper.ensureInitialized() + .equalsValue(this as DeckReference, other); + } + + @override + int get hashCode { + return DeckReferenceMapper.ensureInitialized() + .hashValue(this as DeckReference); + } +} + +extension DeckReferenceValueCopy<$R, $Out> + on ObjectCopyWith<$R, DeckReference, $Out> { + DeckReferenceCopyWith<$R, DeckReference, $Out> get $asDeckReference => + $base.as((v, t, t2) => _DeckReferenceCopyWithImpl(v, t, t2)); +} + +abstract class DeckReferenceCopyWith<$R, $In extends DeckReference, $Out> + implements ClassCopyWith<$R, $In, $Out> { + ListCopyWith<$R, Slide, SlideCopyWith<$R, Slide, Slide>> get slides; + DeckConfigurationCopyWith<$R, DeckConfiguration, DeckConfiguration> + get config; + $R call({List? slides, DeckConfiguration? config}); + DeckReferenceCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class _DeckReferenceCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, DeckReference, $Out> + implements DeckReferenceCopyWith<$R, DeckReference, $Out> { + _DeckReferenceCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + DeckReferenceMapper.ensureInitialized(); + @override + ListCopyWith<$R, Slide, SlideCopyWith<$R, Slide, Slide>> get slides => + ListCopyWith($value.slides, (v, t) => v.copyWith.$chain(t), + (v) => call(slides: v)); + @override + DeckConfigurationCopyWith<$R, DeckConfiguration, DeckConfiguration> + get config => $value.config.copyWith.$chain((v) => call(config: v)); + @override + $R call({List? slides, DeckConfiguration? config}) => + $apply(FieldCopyWithData({ + if (slides != null) #slides: slides, + if (config != null) #config: config + })); + @override + DeckReference $make(CopyWithData data) => DeckReference( + slides: data.get(#slides, or: $value.slides), + config: data.get(#config, or: $value.config)); + + @override + DeckReferenceCopyWith<$R2, DeckReference, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _DeckReferenceCopyWithImpl($value, $cast, t); +} diff --git a/packages/superdeck_core/lib/src/models/slide_model.dart b/packages/superdeck_core/lib/src/models/slide_model.dart new file mode 100644 index 00000000..3951914b --- /dev/null +++ b/packages/superdeck_core/lib/src/models/slide_model.dart @@ -0,0 +1,88 @@ +import 'package:dart_mappable/dart_mappable.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +part 'slide_model.mapper.dart'; + +@MappableClass() +class Slide with SlideMappable { + final String key; + final SlideOptions? options; + final List sections; + final List comments; + + const Slide({ + required this.key, + this.options, + this.sections = const [], + this.comments = const [], + }); + + static final schema = Ack.object( + { + "key": Ack.string, + 'options': SlideOptions.schema.nullable(), + 'sections': SectionBlock.schema.list, + 'comments': Ack.string.list, + }, + required: ['key'], + additionalProperties: true, + ); + + static Slide parse(Map map) { + schema.validateOrThrow(map); + return SlideMapper.fromMap(map); + } +} + +@MappableClass( + hook: UnmappedPropertiesHook('args'), +) +class SlideOptions with SlideOptionsMappable { + final String? title; + final String? style; + final Map args; + + const SlideOptions({ + this.title, + this.style, + this.args = const {}, + }); + + static SlideOptions parse(Map map) { + schema.validateOrThrow(map); + return SlideOptionsMapper.fromMap(map); + } + + static final schema = Ack.object( + { + "title": Ack.string.nullable(), + "style": Ack.string.nullable(), + }, + additionalProperties: true, + ); +} + +class ErrorSlide extends Slide { + ErrorSlide({ + required String title, + required String message, + required Exception error, + }) : super( + key: 'error', + sections: [ + SectionBlock([ + ColumnBlock(''' +> [!CAUTION] +> $title +> $message + + +```dart +${error.toString()} +``` +'''), + ColumnBlock('') + ]), + ], + ); +} diff --git a/packages/superdeck_core/lib/src/models/slide_model.mapper.dart b/packages/superdeck_core/lib/src/models/slide_model.mapper.dart new file mode 100644 index 00000000..c056cf36 --- /dev/null +++ b/packages/superdeck_core/lib/src/models/slide_model.mapper.dart @@ -0,0 +1,287 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, unnecessary_cast, override_on_non_overriding_member +// ignore_for_file: strict_raw_type, inference_failure_on_untyped_parameter + +part of 'slide_model.dart'; + +class SlideMapper extends ClassMapperBase { + SlideMapper._(); + + static SlideMapper? _instance; + static SlideMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = SlideMapper._()); + SlideOptionsMapper.ensureInitialized(); + SectionBlockMapper.ensureInitialized(); + } + return _instance!; + } + + @override + final String id = 'Slide'; + + static String _$key(Slide v) => v.key; + static const Field _f$key = Field('key', _$key); + static SlideOptions? _$options(Slide v) => v.options; + static const Field _f$options = + Field('options', _$options, opt: true); + static List _$sections(Slide v) => v.sections; + static const Field> _f$sections = + Field('sections', _$sections, opt: true, def: const []); + static List _$comments(Slide v) => v.comments; + static const Field> _f$comments = + Field('comments', _$comments, opt: true, def: const []); + + @override + final MappableFields fields = const { + #key: _f$key, + #options: _f$options, + #sections: _f$sections, + #comments: _f$comments, + }; + @override + final bool ignoreNull = true; + + static Slide _instantiate(DecodingData data) { + return Slide( + key: data.dec(_f$key), + options: data.dec(_f$options), + sections: data.dec(_f$sections), + comments: data.dec(_f$comments)); + } + + @override + final Function instantiate = _instantiate; + + static Slide fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static Slide fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin SlideMappable { + String toJson() { + return SlideMapper.ensureInitialized().encodeJson(this as Slide); + } + + Map toMap() { + return SlideMapper.ensureInitialized().encodeMap(this as Slide); + } + + SlideCopyWith get copyWith => + _SlideCopyWithImpl(this as Slide, $identity, $identity); + @override + String toString() { + return SlideMapper.ensureInitialized().stringifyValue(this as Slide); + } + + @override + bool operator ==(Object other) { + return SlideMapper.ensureInitialized().equalsValue(this as Slide, other); + } + + @override + int get hashCode { + return SlideMapper.ensureInitialized().hashValue(this as Slide); + } +} + +extension SlideValueCopy<$R, $Out> on ObjectCopyWith<$R, Slide, $Out> { + SlideCopyWith<$R, Slide, $Out> get $asSlide => + $base.as((v, t, t2) => _SlideCopyWithImpl(v, t, t2)); +} + +abstract class SlideCopyWith<$R, $In extends Slide, $Out> + implements ClassCopyWith<$R, $In, $Out> { + SlideOptionsCopyWith<$R, SlideOptions, SlideOptions>? get options; + ListCopyWith<$R, SectionBlock, + SectionBlockCopyWith<$R, SectionBlock, SectionBlock>> get sections; + ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get comments; + $R call( + {String? key, + SlideOptions? options, + List? sections, + List? comments}); + SlideCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class _SlideCopyWithImpl<$R, $Out> extends ClassCopyWithBase<$R, Slide, $Out> + implements SlideCopyWith<$R, Slide, $Out> { + _SlideCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = SlideMapper.ensureInitialized(); + @override + SlideOptionsCopyWith<$R, SlideOptions, SlideOptions>? get options => + $value.options?.copyWith.$chain((v) => call(options: v)); + @override + ListCopyWith<$R, SectionBlock, + SectionBlockCopyWith<$R, SectionBlock, SectionBlock>> + get sections => ListCopyWith($value.sections, + (v, t) => v.copyWith.$chain(t), (v) => call(sections: v)); + @override + ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get comments => + ListCopyWith($value.comments, (v, t) => ObjectCopyWith(v, $identity, t), + (v) => call(comments: v)); + @override + $R call( + {String? key, + Object? options = $none, + List? sections, + List? comments}) => + $apply(FieldCopyWithData({ + if (key != null) #key: key, + if (options != $none) #options: options, + if (sections != null) #sections: sections, + if (comments != null) #comments: comments + })); + @override + Slide $make(CopyWithData data) => Slide( + key: data.get(#key, or: $value.key), + options: data.get(#options, or: $value.options), + sections: data.get(#sections, or: $value.sections), + comments: data.get(#comments, or: $value.comments)); + + @override + SlideCopyWith<$R2, Slide, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t) => + _SlideCopyWithImpl($value, $cast, t); +} + +class SlideOptionsMapper extends ClassMapperBase { + SlideOptionsMapper._(); + + static SlideOptionsMapper? _instance; + static SlideOptionsMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = SlideOptionsMapper._()); + } + return _instance!; + } + + @override + final String id = 'SlideOptions'; + + static String? _$title(SlideOptions v) => v.title; + static const Field _f$title = + Field('title', _$title, opt: true); + static String? _$style(SlideOptions v) => v.style; + static const Field _f$style = + Field('style', _$style, opt: true); + static Map _$args(SlideOptions v) => v.args; + static const Field> _f$args = + Field('args', _$args, opt: true, def: const {}); + + @override + final MappableFields fields = const { + #title: _f$title, + #style: _f$style, + #args: _f$args, + }; + @override + final bool ignoreNull = true; + + @override + final MappingHook hook = const UnmappedPropertiesHook('args'); + static SlideOptions _instantiate(DecodingData data) { + return SlideOptions( + title: data.dec(_f$title), + style: data.dec(_f$style), + args: data.dec(_f$args)); + } + + @override + final Function instantiate = _instantiate; + + static SlideOptions fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static SlideOptions fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin SlideOptionsMappable { + String toJson() { + return SlideOptionsMapper.ensureInitialized() + .encodeJson(this as SlideOptions); + } + + Map toMap() { + return SlideOptionsMapper.ensureInitialized() + .encodeMap(this as SlideOptions); + } + + SlideOptionsCopyWith get copyWith => + _SlideOptionsCopyWithImpl(this as SlideOptions, $identity, $identity); + @override + String toString() { + return SlideOptionsMapper.ensureInitialized() + .stringifyValue(this as SlideOptions); + } + + @override + bool operator ==(Object other) { + return SlideOptionsMapper.ensureInitialized() + .equalsValue(this as SlideOptions, other); + } + + @override + int get hashCode { + return SlideOptionsMapper.ensureInitialized() + .hashValue(this as SlideOptions); + } +} + +extension SlideOptionsValueCopy<$R, $Out> + on ObjectCopyWith<$R, SlideOptions, $Out> { + SlideOptionsCopyWith<$R, SlideOptions, $Out> get $asSlideOptions => + $base.as((v, t, t2) => _SlideOptionsCopyWithImpl(v, t, t2)); +} + +abstract class SlideOptionsCopyWith<$R, $In extends SlideOptions, $Out> + implements ClassCopyWith<$R, $In, $Out> { + MapCopyWith<$R, String, Object?, ObjectCopyWith<$R, Object?, Object?>?> + get args; + $R call({String? title, String? style, Map? args}); + SlideOptionsCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>(Then<$Out2, $R2> t); +} + +class _SlideOptionsCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, SlideOptions, $Out> + implements SlideOptionsCopyWith<$R, SlideOptions, $Out> { + _SlideOptionsCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + SlideOptionsMapper.ensureInitialized(); + @override + MapCopyWith<$R, String, Object?, ObjectCopyWith<$R, Object?, Object?>?> + get args => MapCopyWith($value.args, + (v, t) => ObjectCopyWith(v, $identity, t), (v) => call(args: v)); + @override + $R call( + {Object? title = $none, + Object? style = $none, + Map? args}) => + $apply(FieldCopyWithData({ + if (title != $none) #title: title, + if (style != $none) #style: style, + if (args != null) #args: args + })); + @override + SlideOptions $make(CopyWithData data) => SlideOptions( + title: data.get(#title, or: $value.title), + style: data.get(#style, or: $value.style), + args: data.get(#args, or: $value.args)); + + @override + SlideOptionsCopyWith<$R2, SlideOptions, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t) => + _SlideOptionsCopyWithImpl($value, $cast, t); +} diff --git a/packages/superdeck_core/lib/superdeck_core.dart b/packages/superdeck_core/lib/superdeck_core.dart new file mode 100644 index 00000000..011cc8f4 --- /dev/null +++ b/packages/superdeck_core/lib/superdeck_core.dart @@ -0,0 +1,14 @@ +library superdeck_core; + +export 'package:ack/ack.dart'; + +export 'src/helpers/data_store.dart'; +export 'src/helpers/extensions.dart'; +export 'src/helpers/generate_hash.dart'; +export 'src/helpers/watcher.dart'; +export 'src/helpers/yaml_utils.dart'; +export 'src/models/asset_model.dart'; +export 'src/models/block_model.dart'; +export 'src/models/deck_configuration.dart'; +export 'src/models/deck_reference.dart'; +export 'src/models/slide_model.dart'; diff --git a/packages/superdeck_core/pubspec.yaml b/packages/superdeck_core/pubspec.yaml new file mode 100644 index 00000000..f80c4a48 --- /dev/null +++ b/packages/superdeck_core/pubspec.yaml @@ -0,0 +1,25 @@ +name: superdeck_core +description: A core library for SuperDeck. +version: 0.0.1 +homepage: https://github.com/leoafarias/superdeck + + +environment: + sdk: ">=3.3.0 <4.0.0" + +# Add regular dependencies here. +dependencies: + dart_mappable: ^4.2.2 + yaml: ^3.1.2 + collection: ^1.18.0 + path: ^1.9.0 + meta: ^1.15.0 + ack: ^0.0.2 + +dev_dependencies: + lints: ^4.0.0 + test: ^1.24.0 + build_runner: ^2.4.9 + dart_mappable_builder: ^4.2.3 + build_verify: ^3.1.0 + dart_code_metrics_presets: ^2.19.0 diff --git a/packages/superdeck_cli/test/src/helpers/short_hash_id_test.dart b/packages/superdeck_core/test/src/helpers/generate_hash_test.dart similarity index 68% rename from packages/superdeck_cli/test/src/helpers/short_hash_id_test.dart rename to packages/superdeck_core/test/src/helpers/generate_hash_test.dart index cecd8561..925d144d 100644 --- a/packages/superdeck_cli/test/src/helpers/short_hash_id_test.dart +++ b/packages/superdeck_core/test/src/helpers/generate_hash_test.dart @@ -1,14 +1,14 @@ -import 'package:superdeck_cli/src/helpers/short_hash_id.dart'; +import 'package:superdeck_core/src/helpers/generate_hash.dart'; import 'package:test/test.dart'; void main() { - group('shortHashId', () { + group('generateValueHash', () { test('generates unique hash for different input strings', () { String input1 = 'hello world'; String input2 = 'hello world!'; - String hash1 = shortHashId(input1); - String hash2 = shortHashId(input2); + String hash1 = generateValueHash(input1); + String hash2 = generateValueHash(input2); expect(hash1, isNot(equals(hash2))); }); @@ -16,8 +16,8 @@ void main() { test('generates same hash for same input string', () { String input = 'test input'; - String hash1 = shortHashId(input); - String hash2 = shortHashId(input); + String hash1 = generateValueHash(input); + String hash2 = generateValueHash(input); expect(hash1, equals(hash2)); }); @@ -25,7 +25,7 @@ void main() { test('generates 8-character hash', () { String input = 'some long input string'; - String hash = shortHashId(input); + String hash = generateValueHash(input); expect(hash.length, equals(8)); }); @@ -33,7 +33,7 @@ void main() { test('generates hash with valid characters', () { String input = 'another input'; - String hash = shortHashId(input); + String hash = generateValueHash(input); expect(hash, matches(RegExp(r'^[a-zA-Z0-9]{8}$'))); }); @@ -41,7 +41,7 @@ void main() { test('handles empty input string', () { String input = ''; - String hash = shortHashId(input); + String hash = generateValueHash(input); expect(hash.length, equals(8)); }); @@ -49,7 +49,7 @@ void main() { test('handles input string with unsupported characters', () { String input = 'input with spaces and !@#\$%^&*()'; - String hash = shortHashId(input); + String hash = generateValueHash(input); expect(hash.length, equals(8)); expect(hash, matches(RegExp(r'^[a-zA-Z0-9]{8}$'))); diff --git a/packages/superdeck_core/test/superdeck_core_test.dart b/packages/superdeck_core/test/superdeck_core_test.dart new file mode 100644 index 00000000..74084a7f --- /dev/null +++ b/packages/superdeck_core/test/superdeck_core_test.dart @@ -0,0 +1,13 @@ +void main() { + // group('A group of tests', () { + // final awesome = Awesome(); + + // setUp(() { + // // Additional setup goes here. + // }); + + // test('First Test', () { + // expect(awesome.isAwesome, isTrue); + // }); + // }); +} diff --git a/pubspec.lock b/pubspec.lock index ba8915f4..f322735a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,34 +13,34 @@ packages: dependency: transitive description: name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" async: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.11.0" - boolean_selector: + version: "2.13.0" + charcode: dependency: transitive description: - name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + name: charcode + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a url: "https://pub.dev" source: hosted - version: "2.1.1" - charcode: + version: "1.4.0" + checked_yaml: dependency: transitive description: - name: charcode - sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "2.0.3" cli_launcher: dependency: transitive description: @@ -53,18 +53,18 @@ packages: dependency: transitive description: name: cli_util - sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c url: "https://pub.dev" source: hosted - version: "0.4.1" + version: "0.4.2" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: @@ -85,18 +85,18 @@ packages: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" glob: dependency: transitive description: name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" graphs: dependency: transitive description: @@ -109,18 +109,18 @@ packages: dependency: transitive description: name: http - sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.3.0" http_parser: dependency: transitive description: name: http_parser - sha256: "40f592dd352890c3b60fec1b68e786cefb9603e05ff303dbc4dda49b304ecdf4" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.1.2" intl: dependency: transitive description: @@ -133,10 +133,10 @@ packages: dependency: transitive description: name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" json_annotation: dependency: transitive description: @@ -145,22 +145,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.9.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb - url: "https://pub.dev" - source: hosted - version: "0.12.16+1" melos: - dependency: "direct dev" + dependency: "direct main" description: name: melos - sha256: a3f06ed871e0348cb99909ad5ddf5f8b53cc61d894c302b5417d2db1ee7ec381 + sha256: "3f3ab3f902843d1e5a1b1a4dd39a4aca8ba1056f2d32fd8995210fa2843f646f" url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "6.3.2" meta: dependency: transitive description: @@ -181,18 +173,18 @@ packages: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" platform: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" pool: dependency: transitive description: @@ -205,10 +197,10 @@ packages: dependency: transitive description: name: process - sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" + sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.0.3" prompts: dependency: transitive description: @@ -221,10 +213,10 @@ packages: dependency: transitive description: name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" pub_updater: dependency: transitive description: @@ -233,109 +225,78 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.0" - pubspec: - dependency: transitive - description: - name: pubspec - sha256: f534a50a2b4d48dc3bc0ec147c8bd7c304280fff23b153f3f11803c4d49d927e - url: "https://pub.dev" - source: hosted - version: "2.3.0" - quiver: + pubspec_parse: dependency: transitive description: - name: quiver - sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "1.5.0" source_span: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 - url: "https://pub.dev" - source: hosted - version: "2.1.2" + version: "1.12.1" string_scanner: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.dev" - source: hosted - version: "1.2.1" - test_api: - dependency: transitive - description: - name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "1.2.2" typed_data: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c - url: "https://pub.dev" - source: hosted - version: "1.3.2" - uri: - dependency: transitive - description: - name: uri - sha256: "889eea21e953187c6099802b7b4cf5219ba8f3518f604a1033064d45b1b8268a" + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.4.0" web: dependency: transitive description: name: web - sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.0" yaml: dependency: transitive description: name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" yaml_edit: dependency: transitive description: name: yaml_edit - sha256: e9c1a3543d2da0db3e90270dbb1e4eebc985ee5e3ffe468d83224472b2194a5f + sha256: fb38626579fb345ad00e674e2af3a5c9b0cc4b9bfb8fd7f7ff322c7c9e62aef5 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" sdks: - dart: ">=3.4.0 <4.0.0" + dart: ">=3.6.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index 262e0518..f6690ba4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,10 @@ name: superdeck environment: - sdk: ">=3.3.0 <4.0.0" - -dev_dependencies: - melos: ^6.1.0 - + sdk: ^3.5.0 + flutter: ">=3.24.0" lint_staged: '**.dart': dart fix --apply && dcm fix +dependencies: + melos: ^6.3.2 diff --git a/shared_analysis_options.yaml b/shared_analysis_options.yaml new file mode 100644 index 00000000..44769fe6 --- /dev/null +++ b/shared_analysis_options.yaml @@ -0,0 +1,61 @@ +analyzer: + errors: + invalid_annotation_target: ignore + body_might_complete_normally_nullable: ignore + plugins: + - custom_lint + exclude: + - '**.mapper.dart' + - '**/generated_plugin_registrant.dart' +linter: + rules: + public_member_api_docs: false + always_use_package_imports: false + prefer_relative_imports: true + library_private_types_in_public_api: false + +dart_code_metrics: + extends: + - package:dart_code_metrics_presets/recommended.yaml + - package:dart_code_metrics_presets/metrics_recommended.yaml + metrics-exclude: + - test/** + rules-exclude: + - test/** + rules: + # avoid-collection-mutating-methods: true + newline-before-return: true + avoid-importing-entrypoint-exports: + only-in-src: true + prefer-match-file-name: false + prefer-overriding-parent-equality: false + prefer-correct-callback-field-name: false + prefer-single-widget-per-file: false + match-getter-setter-field-names: false + prefer-dedicated-media-query-methods: false + avoid-shadowing: false + enum-constants-ordering: false + avoid-unsafe-collection-methods: false + prefer-prefixed-global-constants: false + avoid-returning-widgets: false + arguments-ordering: + child-last: true + avoid-nested-conditional-expressions: + acceptable-level: 3 + member-ordering: + order: + - public-fields + - private-fields + - constructors + - static-methods + - private-methods + - private-getters + - private-setters + - public-getters + - public-setters + - public-methods + - overridden-public-methods + - overridden-public-getters + - build-method + prefer-named-boolean-parameters: + ignore-single: true \ No newline at end of file diff --git a/superdeck.code-workspace b/superdeck.code-workspace index cdf01b27..42f14488 100644 --- a/superdeck.code-workspace +++ b/superdeck.code-workspace @@ -1,9 +1,5 @@ { "folders": [ - { - "name": "root", - "path": "." - }, { "name": "superdeck", "path": "packages/superdeck" @@ -12,27 +8,37 @@ "name": "superdeck_cli", "path": "packages/superdeck_cli" }, + { + "name": "superdeck_core", + "path": "packages/superdeck_core" + }, + { + "path": "." + } + ], "settings": { "workbench.colorCustomizations": { - "activityBar.activeBackground": "#3c0b66", - "activityBar.background": "#3c0b66", + "activityBar.activeBackground": "#003a3f", + "activityBar.background": "#003a3f", "activityBar.foreground": "#e7e7e7", "activityBar.inactiveForeground": "#e7e7e799", - "activityBarBadge.background": "#905510", + "activityBarBadge.background": "#bf00b0", "activityBarBadge.foreground": "#e7e7e7", "commandCenter.border": "#e7e7e799", - "sash.hoverBorder": "#3c0b66", - "statusBar.background": "#210638", + "sash.hoverBorder": "#003a3f", + "statusBar.background": "#000b0c", "statusBar.foreground": "#e7e7e7", - "statusBarItem.hoverBackground": "#3c0b66", - "statusBarItem.remoteBackground": "#210638", + "statusBarItem.hoverBackground": "#003a3f", + "statusBarItem.remoteBackground": "#000b0c", "statusBarItem.remoteForeground": "#e7e7e7", - "titleBar.activeBackground": "#210638", + "titleBar.activeBackground": "#000b0c", "titleBar.activeForeground": "#e7e7e7", - "titleBar.inactiveBackground": "#21063899", + "titleBar.inactiveBackground": "#000b0c99", "titleBar.inactiveForeground": "#e7e7e799" }, - "peacock.color": "#210638" + "peacock.color": "#000b0c", + "window.confirmSaveUntitledWorkspace": false, + "cursor.general.enableShadowWorkspace": true } } \ No newline at end of file