appiq-solution 1.4.8 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +172 -48
- package/bmad-core/agent-teams/team-fullstack.yaml +1 -11
- package/bmad-core/agents/analyst.md +3 -7
- package/bmad-core/agents/architect.md +0 -6
- package/bmad-core/agents/dev.md +5 -24
- package/bmad-core/agents/pm.md +3 -7
- package/bmad-core/agents/qa.md +0 -17
- package/bmad-core/agents/sm.md +3 -8
- package/bmad-core/agents/ux-expert.md +3 -8
- package/bmad-core/data/technical-preferences.md +1 -147
- package/bmad-core/templates/fullstack-architecture-tmpl.yaml +5 -12
- package/bmad-core/workflows/brownfield-fullstack.yaml +1 -15
- package/bmad-core/workflows/greenfield-fullstack.yaml +5 -49
- package/bmad-core/working-in-the-brownfield.md +10 -19
- package/dist/agents/bmad-orchestrator.txt +0 -111
- package/dist/agents/pm.txt +2 -0
- package/dist/expansion-packs/bmad-2d-phaser-game-dev/teams/phaser-2d-nodejs-game-team.txt +0 -111
- package/dist/expansion-packs/bmad-2d-unity-game-dev/teams/unity-2d-game-team.txt +0 -111
- package/dist/teams/team-all.txt +2 -111
- package/dist/teams/team-fullstack.txt +2 -111
- package/dist/teams/team-ide-minimal.txt +0 -111
- package/dist/teams/team-no-ui.txt +2 -111
- package/{#Tools/APPIQ-METHOD/expansion-packs/bmad-flutter-mobile-dev → expansion-packs/appiq-flutter-mobile-dev}/config.yaml +1 -0
- package/package.json +60 -18
- package/tools/cli.js +3 -3
- package/tools/installer/lib/ide-setup.js +11 -0
- package/tools/installer/package-lock.json +2 -2
- package/#NEW APP PROMPT/ARCHITECTURE.md +0 -279
- package/#NEW APP PROMPT/ARCHITECTURE_UNIFIED.md +0 -414
- package/#NEW APP PROMPT/accesibility_prompt.md +0 -103
- package/#NEW APP PROMPT/clean-code.mdc +0 -55
- package/#NEW APP PROMPT/codequality.mdc +0 -47
- package/#NEW APP PROMPT/flutter-ai-rules/LICENSE +0 -21
- package/#NEW APP PROMPT/flutter-ai-rules/README.md +0 -104
- package/#NEW APP PROMPT/flutter-ai-rules/combined/README.md +0 -25
- package/#NEW APP PROMPT/flutter-ai-rules/combined/flutter_dart.md +0 -192
- package/#NEW APP PROMPT/flutter-ai-rules/combined/flutter_dart__under_6K.md +0 -99
- package/#NEW APP PROMPT/flutter-ai-rules/combined/flutter_dart_bloc_mocktail.md +0 -308
- package/#NEW APP PROMPT/flutter-ai-rules/combined/flutter_dart_bloc_mocktail__under_6K.md +0 -87
- package/#NEW APP PROMPT/flutter-ai-rules/combined/flutter_dart_change_notifier.md +0 -254
- package/#NEW APP PROMPT/flutter-ai-rules/combined/flutter_dart_change_notifier__under_6K.md +0 -98
- package/#NEW APP PROMPT/flutter-ai-rules/combined/flutter_dart_provider.md +0 -261
- package/#NEW APP PROMPT/flutter-ai-rules/combined/flutter_dart_provider__under_6K.md +0 -105
- package/#NEW APP PROMPT/flutter-ai-rules/combined/flutter_dart_riverpod_mockito.md +0 -371
- package/#NEW APP PROMPT/flutter-ai-rules/combined/flutter_dart_riverpod_mockito__under_6K.md +0 -92
- package/#NEW APP PROMPT/flutter-ai-rules/combined/flutter_with_bloc.md +0 -287
- package/#NEW APP PROMPT/flutter-ai-rules/combined/flutter_with_bloc__under_6K.md +0 -68
- package/#NEW APP PROMPT/flutter-ai-rules/combined/flutter_with_riverpod.md +0 -375
- package/#NEW APP PROMPT/flutter-ai-rules/combined/flutter_with_riverpod__under_6K.md +0 -106
- package/#NEW APP PROMPT/flutter-ai-rules/media/flutter_ai_rules.png +0 -0
- package/#NEW APP PROMPT/flutter-ai-rules/media/mocktail_md_01.png +0 -0
- package/#NEW APP PROMPT/flutter-ai-rules/media/mocktail_md_02.png +0 -0
- package/#NEW APP PROMPT/flutter-ai-rules/rules/bloc.md +0 -94
- package/#NEW APP PROMPT/flutter-ai-rules/rules/dart_3_updates.md +0 -93
- package/#NEW APP PROMPT/flutter-ai-rules/rules/effective_dart.md +0 -105
- package/#NEW APP PROMPT/flutter-ai-rules/rules/flutter_app_architecture.md +0 -57
- package/#NEW APP PROMPT/flutter-ai-rules/rules/flutter_change_notifier.md +0 -62
- package/#NEW APP PROMPT/flutter-ai-rules/rules/flutter_errors.md +0 -11
- package/#NEW APP PROMPT/flutter-ai-rules/rules/mockito.md +0 -31
- package/#NEW APP PROMPT/flutter-ai-rules/rules/mocktail.md +0 -24
- package/#NEW APP PROMPT/flutter-ai-rules/rules/provider.md +0 -69
- package/#NEW APP PROMPT/flutter-ai-rules/rules/riverpod.md +0 -188
- package/#NEW APP PROMPT/generate ssh.md +0 -4
- package/#NEW APP PROMPT/project_ai_prompt.md +0 -544
- package/#Tools/APPIQ-METHOD/.bmad-config.json +0 -13
- package/#Tools/APPIQ-METHOD/.cursor/commands/analyze.md +0 -27
- package/#Tools/APPIQ-METHOD/.cursor/commands/appiq.md +0 -27
- package/#Tools/APPIQ-METHOD/.cursor/commands/help.md +0 -27
- package/#Tools/APPIQ-METHOD/.cursor/commands/story.md +0 -27
- package/#Tools/APPIQ-METHOD/DEVELOPMENT_GUIDE.md +0 -855
- package/#Tools/APPIQ-METHOD/NPM-README.md +0 -138
- package/#Tools/APPIQ-METHOD/README.md +0 -107
- package/#Tools/APPIQ-METHOD/SMART_WORKFLOW_GUIDE.md +0 -401
- package/#Tools/APPIQ-METHOD/activate-appiq.js +0 -81
- package/#Tools/APPIQ-METHOD/appiq-solution/README.md +0 -226
- package/#Tools/APPIQ-METHOD/bmad-core/agent-teams/team-all.yaml +0 -14
- package/#Tools/APPIQ-METHOD/bmad-core/agent-teams/team-flutter-mobile.yaml +0 -114
- package/#Tools/APPIQ-METHOD/bmad-core/agent-teams/team-fullstack.yaml +0 -28
- package/#Tools/APPIQ-METHOD/bmad-core/agent-teams/team-ide-minimal.yaml +0 -10
- package/#Tools/APPIQ-METHOD/bmad-core/agent-teams/team-no-ui.yaml +0 -13
- package/#Tools/APPIQ-METHOD/bmad-core/agents/analyst.md +0 -85
- package/#Tools/APPIQ-METHOD/bmad-core/agents/architect.md +0 -90
- package/#Tools/APPIQ-METHOD/bmad-core/agents/bmad-master.md +0 -108
- package/#Tools/APPIQ-METHOD/bmad-core/agents/bmad-orchestrator.md +0 -150
- package/#Tools/APPIQ-METHOD/bmad-core/agents/bmad-smart-launcher.md +0 -170
- package/#Tools/APPIQ-METHOD/bmad-core/agents/dev.md +0 -95
- package/#Tools/APPIQ-METHOD/bmad-core/agents/init-flow-po.md +0 -143
- package/#Tools/APPIQ-METHOD/bmad-core/agents/pm.md +0 -85
- package/#Tools/APPIQ-METHOD/bmad-core/agents/po.md +0 -76
- package/#Tools/APPIQ-METHOD/bmad-core/agents/qa.md +0 -86
- package/#Tools/APPIQ-METHOD/bmad-core/agents/sm.md +0 -67
- package/#Tools/APPIQ-METHOD/bmad-core/agents/ux-expert.md +0 -71
- package/#Tools/APPIQ-METHOD/bmad-core/bmad-core/user-guide.md +0 -0
- package/#Tools/APPIQ-METHOD/bmad-core/checklists/architect-checklist.md +0 -443
- package/#Tools/APPIQ-METHOD/bmad-core/checklists/change-checklist.md +0 -182
- package/#Tools/APPIQ-METHOD/bmad-core/checklists/pm-checklist.md +0 -375
- package/#Tools/APPIQ-METHOD/bmad-core/checklists/po-master-checklist.md +0 -441
- package/#Tools/APPIQ-METHOD/bmad-core/checklists/security-validation-checklist.md +0 -332
- package/#Tools/APPIQ-METHOD/bmad-core/checklists/story-dod-checklist.md +0 -101
- package/#Tools/APPIQ-METHOD/bmad-core/checklists/story-draft-checklist.md +0 -156
- package/#Tools/APPIQ-METHOD/bmad-core/core-config.yaml +0 -20
- package/#Tools/APPIQ-METHOD/bmad-core/core-config.yaml.bak +0 -20
- package/#Tools/APPIQ-METHOD/bmad-core/data/backend-services-integration.md +0 -686
- package/#Tools/APPIQ-METHOD/bmad-core/data/bmad-kb.md +0 -803
- package/#Tools/APPIQ-METHOD/bmad-core/data/brainstorming-techniques.md +0 -36
- package/#Tools/APPIQ-METHOD/bmad-core/data/elicitation-methods.md +0 -134
- package/#Tools/APPIQ-METHOD/bmad-core/data/shadcn-ui-integration.md +0 -388
- package/#Tools/APPIQ-METHOD/bmad-core/data/technical-preferences.md +0 -149
- package/#Tools/APPIQ-METHOD/bmad-core/enhanced-ide-development-workflow.md +0 -43
- package/#Tools/APPIQ-METHOD/bmad-core/tasks/advanced-elicitation.md +0 -117
- package/#Tools/APPIQ-METHOD/bmad-core/tasks/brownfield-create-epic.md +0 -160
- package/#Tools/APPIQ-METHOD/bmad-core/tasks/brownfield-create-story.md +0 -147
- package/#Tools/APPIQ-METHOD/bmad-core/tasks/correct-course.md +0 -70
- package/#Tools/APPIQ-METHOD/bmad-core/tasks/create-brownfield-story.md +0 -304
- package/#Tools/APPIQ-METHOD/bmad-core/tasks/create-deep-research-prompt.md +0 -289
- package/#Tools/APPIQ-METHOD/bmad-core/tasks/create-flutter-story.md +0 -197
- package/#Tools/APPIQ-METHOD/bmad-core/tasks/create-next-story.md +0 -112
- package/#Tools/APPIQ-METHOD/bmad-core/tasks/document-project.md +0 -341
- package/#Tools/APPIQ-METHOD/bmad-core/tasks/facilitate-brainstorming-session.md +0 -136
- package/#Tools/APPIQ-METHOD/bmad-core/tasks/generate-ai-frontend-prompt.md +0 -51
- package/#Tools/APPIQ-METHOD/bmad-core/tasks/index-docs.md +0 -179
- package/#Tools/APPIQ-METHOD/bmad-core/tasks/intelligent-epic-creation.md +0 -234
- package/#Tools/APPIQ-METHOD/bmad-core/tasks/kb-mode-interaction.md +0 -75
- package/#Tools/APPIQ-METHOD/bmad-core/tasks/review-story.md +0 -145
- package/#Tools/APPIQ-METHOD/bmad-core/tasks/shard-doc.md +0 -187
- package/#Tools/APPIQ-METHOD/bmad-core/tasks/smart-project-analysis.md +0 -289
- package/#Tools/APPIQ-METHOD/bmad-core/tasks/validate-next-story.md +0 -134
- package/#Tools/APPIQ-METHOD/bmad-core/templates/architecture-tmpl.yaml +0 -650
- package/#Tools/APPIQ-METHOD/bmad-core/templates/brainstorming-output-tmpl.yaml +0 -156
- package/#Tools/APPIQ-METHOD/bmad-core/templates/brownfield-architecture-tmpl.yaml +0 -476
- package/#Tools/APPIQ-METHOD/bmad-core/templates/brownfield-prd-tmpl.yaml +0 -280
- package/#Tools/APPIQ-METHOD/bmad-core/templates/competitor-analysis-tmpl.yaml +0 -293
- package/#Tools/APPIQ-METHOD/bmad-core/templates/flutter-mobile-prd-tmpl.yaml +0 -330
- package/#Tools/APPIQ-METHOD/bmad-core/templates/flutter-story-tmpl.yaml +0 -376
- package/#Tools/APPIQ-METHOD/bmad-core/templates/flutter-ui-spec-tmpl.yaml +0 -415
- package/#Tools/APPIQ-METHOD/bmad-core/templates/front-end-architecture-tmpl.yaml +0 -206
- package/#Tools/APPIQ-METHOD/bmad-core/templates/front-end-spec-tmpl.yaml +0 -349
- package/#Tools/APPIQ-METHOD/bmad-core/templates/fullstack-architecture-tmpl.yaml +0 -812
- package/#Tools/APPIQ-METHOD/bmad-core/templates/market-research-tmpl.yaml +0 -252
- package/#Tools/APPIQ-METHOD/bmad-core/templates/prd-tmpl.yaml +0 -202
- package/#Tools/APPIQ-METHOD/bmad-core/templates/project-brief-tmpl.yaml +0 -221
- package/#Tools/APPIQ-METHOD/bmad-core/templates/story-tmpl.yaml +0 -137
- package/#Tools/APPIQ-METHOD/bmad-core/user-guide.md +0 -251
- package/#Tools/APPIQ-METHOD/bmad-core/workflows/brownfield-fullstack.yaml +0 -311
- package/#Tools/APPIQ-METHOD/bmad-core/workflows/brownfield-service.yaml +0 -187
- package/#Tools/APPIQ-METHOD/bmad-core/workflows/brownfield-ui.yaml +0 -197
- package/#Tools/APPIQ-METHOD/bmad-core/workflows/greenfield-fullstack.yaml +0 -284
- package/#Tools/APPIQ-METHOD/bmad-core/workflows/greenfield-service.yaml +0 -206
- package/#Tools/APPIQ-METHOD/bmad-core/workflows/greenfield-ui.yaml +0 -235
- package/#Tools/APPIQ-METHOD/bmad-core/working-in-the-brownfield.md +0 -373
- package/#Tools/APPIQ-METHOD/commands/README.md +0 -28
- package/#Tools/APPIQ-METHOD/commands/analyze.md +0 -27
- package/#Tools/APPIQ-METHOD/commands/appiq.md +0 -27
- package/#Tools/APPIQ-METHOD/commands/help.md +0 -27
- package/#Tools/APPIQ-METHOD/commands/story.md +0 -27
- package/#Tools/APPIQ-METHOD/dist/agents/analyst.txt +0 -2882
- package/#Tools/APPIQ-METHOD/dist/agents/architect.txt +0 -3543
- package/#Tools/APPIQ-METHOD/dist/agents/bmad-master.txt +0 -8756
- package/#Tools/APPIQ-METHOD/dist/agents/bmad-orchestrator.txt +0 -1490
- package/#Tools/APPIQ-METHOD/dist/agents/dev.txt +0 -428
- package/#Tools/APPIQ-METHOD/dist/agents/pm.txt +0 -2229
- package/#Tools/APPIQ-METHOD/dist/agents/po.txt +0 -1364
- package/#Tools/APPIQ-METHOD/dist/agents/qa.txt +0 -386
- package/#Tools/APPIQ-METHOD/dist/agents/sm.txt +0 -668
- package/#Tools/APPIQ-METHOD/dist/agents/ux-expert.txt +0 -701
- package/#Tools/APPIQ-METHOD/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-designer.txt +0 -2408
- package/#Tools/APPIQ-METHOD/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-developer.txt +0 -1631
- package/#Tools/APPIQ-METHOD/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-sm.txt +0 -822
- package/#Tools/APPIQ-METHOD/dist/expansion-packs/bmad-2d-phaser-game-dev/teams/phaser-2d-nodejs-game-team.txt +0 -10989
- package/#Tools/APPIQ-METHOD/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-architect.txt +0 -4047
- package/#Tools/APPIQ-METHOD/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-designer.txt +0 -3744
- package/#Tools/APPIQ-METHOD/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.txt +0 -465
- package/#Tools/APPIQ-METHOD/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-sm.txt +0 -990
- package/#Tools/APPIQ-METHOD/dist/expansion-packs/bmad-2d-unity-game-dev/teams/unity-2d-game-team.txt +0 -15467
- package/#Tools/APPIQ-METHOD/dist/expansion-packs/bmad-infrastructure-devops/agents/infra-devops-platform.txt +0 -2077
- package/#Tools/APPIQ-METHOD/dist/teams/team-all.txt +0 -11062
- package/#Tools/APPIQ-METHOD/dist/teams/team-fullstack.txt +0 -10392
- package/#Tools/APPIQ-METHOD/dist/teams/team-ide-minimal.txt +0 -3507
- package/#Tools/APPIQ-METHOD/dist/teams/team-no-ui.txt +0 -8951
- package/#Tools/APPIQ-METHOD/install-appiq.sh +0 -41
- package/#Tools/APPIQ-METHOD/package-lock.json +0 -631
- package/#Tools/APPIQ-METHOD/package.json +0 -44
- package/#Tools/APPIQ-METHOD/tasks/todo.md +0 -275
- package/#Tools/APPIQ-METHOD/tools/appiq-installer.js +0 -2711
- package/#Tools/APPIQ-METHOD/tools/bmad-npx-wrapper.js +0 -39
- package/#Tools/APPIQ-METHOD/tools/builders/web-builder.js +0 -681
- package/#Tools/APPIQ-METHOD/tools/bump-all-versions.js +0 -106
- package/#Tools/APPIQ-METHOD/tools/bump-expansion-version.js +0 -83
- package/#Tools/APPIQ-METHOD/tools/cli.js +0 -152
- package/#Tools/APPIQ-METHOD/tools/epic-solution-installer.js +0 -536
- package/#Tools/APPIQ-METHOD/tools/flattener/main.js +0 -570
- package/#Tools/APPIQ-METHOD/tools/installer/README.md +0 -8
- package/#Tools/APPIQ-METHOD/tools/installer/bin/bmad.js +0 -483
- package/#Tools/APPIQ-METHOD/tools/installer/config/ide-agent-config.yaml +0 -58
- package/#Tools/APPIQ-METHOD/tools/installer/config/install.config.yaml +0 -92
- package/#Tools/APPIQ-METHOD/tools/installer/lib/config-loader.js +0 -253
- package/#Tools/APPIQ-METHOD/tools/installer/lib/file-manager.js +0 -411
- package/#Tools/APPIQ-METHOD/tools/installer/lib/ide-base-setup.js +0 -227
- package/#Tools/APPIQ-METHOD/tools/installer/lib/ide-setup.js +0 -1096
- package/#Tools/APPIQ-METHOD/tools/installer/lib/installer.js +0 -1772
- package/#Tools/APPIQ-METHOD/tools/installer/lib/memory-profiler.js +0 -224
- package/#Tools/APPIQ-METHOD/tools/installer/lib/module-manager.js +0 -110
- package/#Tools/APPIQ-METHOD/tools/installer/lib/resource-locator.js +0 -310
- package/#Tools/APPIQ-METHOD/tools/installer/package-lock.json +0 -906
- package/#Tools/APPIQ-METHOD/tools/installer/package.json +0 -43
- package/#Tools/APPIQ-METHOD/tools/lib/dependency-resolver.js +0 -179
- package/#Tools/APPIQ-METHOD/tools/lib/yaml-utils.js +0 -29
- package/#Tools/APPIQ-METHOD/tools/md-assets/web-agent-startup-instructions.md +0 -39
- package/#Tools/APPIQ-METHOD/tools/semantic-release-sync-installer.js +0 -30
- package/#Tools/APPIQ-METHOD/tools/setup-ide-commands.js +0 -348
- package/#Tools/APPIQ-METHOD/tools/smart-installer.js +0 -592
- package/#Tools/APPIQ-METHOD/tools/sync-installer-version.js +0 -34
- package/#Tools/APPIQ-METHOD/tools/update-expansion-version.js +0 -54
- package/#Tools/APPIQ-METHOD/tools/upgraders/v3-to-v4-upgrader.js +0 -763
- package/#Tools/APPIQ-METHOD/tools/version-bump.js +0 -79
- package/#Tools/APPIQ-METHOD/tools/yaml-format.js +0 -240
- package/#Tools/SkyReels-V2/.pre-commit-config.yaml +0 -25
- package/#Tools/SkyReels-V2/LICENSE.txt +0 -38
- package/#Tools/SkyReels-V2/README.md +0 -766
- package/#Tools/SkyReels-V2/generate_video.py +0 -161
- package/#Tools/SkyReels-V2/generate_video_df.py +0 -220
- package/#Tools/SkyReels-V2/requirements.txt +0 -16
- package/#Tools/SkyReels-V2/skycaptioner_v1/README.md +0 -278
- package/#Tools/SkyReels-V2/skycaptioner_v1/examples/test.csv +0 -5
- package/#Tools/SkyReels-V2/skycaptioner_v1/examples/test_result.csv +0 -5
- package/#Tools/SkyReels-V2/skycaptioner_v1/infer_fusion_caption.sh +0 -9
- package/#Tools/SkyReels-V2/skycaptioner_v1/infer_struct_caption.sh +0 -8
- package/#Tools/SkyReels-V2/skycaptioner_v1/requirements.txt +0 -3
- package/#Tools/SkyReels-V2/skycaptioner_v1/scripts/gradio_fusion_caption.py +0 -146
- package/#Tools/SkyReels-V2/skycaptioner_v1/scripts/gradio_struct_caption.py +0 -92
- package/#Tools/SkyReels-V2/skycaptioner_v1/scripts/utils.py +0 -19
- package/#Tools/SkyReels-V2/skycaptioner_v1/scripts/vllm_fusion_caption.py +0 -256
- package/#Tools/SkyReels-V2/skycaptioner_v1/scripts/vllm_struct_caption.py +0 -156
- package/#Tools/SkyReels-V2/skyreels_v2_infer/__init__.py +0 -1
- package/#Tools/SkyReels-V2/skyreels_v2_infer/distributed/__init__.py +0 -0
- package/#Tools/SkyReels-V2/skyreels_v2_infer/distributed/xdit_context_parallel.py +0 -286
- package/#Tools/SkyReels-V2/skyreels_v2_infer/modules/__init__.py +0 -69
- package/#Tools/SkyReels-V2/skyreels_v2_infer/modules/attention.py +0 -179
- package/#Tools/SkyReels-V2/skyreels_v2_infer/modules/clip.py +0 -525
- package/#Tools/SkyReels-V2/skyreels_v2_infer/modules/t5.py +0 -454
- package/#Tools/SkyReels-V2/skyreels_v2_infer/modules/tokenizers.py +0 -78
- package/#Tools/SkyReels-V2/skyreels_v2_infer/modules/transformer.py +0 -839
- package/#Tools/SkyReels-V2/skyreels_v2_infer/modules/vae.py +0 -639
- package/#Tools/SkyReels-V2/skyreels_v2_infer/modules/xlm_roberta.py +0 -165
- package/#Tools/SkyReels-V2/skyreels_v2_infer/pipelines/__init__.py +0 -5
- package/#Tools/SkyReels-V2/skyreels_v2_infer/pipelines/diffusion_forcing_pipeline.py +0 -659
- package/#Tools/SkyReels-V2/skyreels_v2_infer/pipelines/image2video_pipeline.py +0 -156
- package/#Tools/SkyReels-V2/skyreels_v2_infer/pipelines/prompt_enhancer.py +0 -65
- package/#Tools/SkyReels-V2/skyreels_v2_infer/pipelines/text2video_pipeline.py +0 -110
- package/#Tools/SkyReels-V2/skyreels_v2_infer/scheduler/__init__.py +0 -0
- package/#Tools/SkyReels-V2/skyreels_v2_infer/scheduler/fm_solvers_unipc.py +0 -759
- package/#Tools/SuperClaude/.claude/commands/analyze.md +0 -36
- package/#Tools/SuperClaude/.claude/commands/build.md +0 -35
- package/#Tools/SuperClaude/.claude/commands/cleanup.md +0 -46
- package/#Tools/SuperClaude/.claude/commands/deploy.md +0 -55
- package/#Tools/SuperClaude/.claude/commands/design.md +0 -91
- package/#Tools/SuperClaude/.claude/commands/dev-setup.md +0 -37
- package/#Tools/SuperClaude/.claude/commands/document.md +0 -99
- package/#Tools/SuperClaude/.claude/commands/estimate.md +0 -69
- package/#Tools/SuperClaude/.claude/commands/explain.md +0 -85
- package/#Tools/SuperClaude/.claude/commands/git.md +0 -47
- package/#Tools/SuperClaude/.claude/commands/improve.md +0 -162
- package/#Tools/SuperClaude/.claude/commands/index.md +0 -159
- package/#Tools/SuperClaude/.claude/commands/load.md +0 -65
- package/#Tools/SuperClaude/.claude/commands/migrate.md +0 -76
- package/#Tools/SuperClaude/.claude/commands/scan.md +0 -163
- package/#Tools/SuperClaude/.claude/commands/shared/ambiguity-check.yml +0 -40
- package/#Tools/SuperClaude/.claude/commands/shared/audit.yml +0 -21
- package/#Tools/SuperClaude/.claude/commands/shared/checkpoint.yml +0 -28
- package/#Tools/SuperClaude/.claude/commands/shared/cleanup-patterns.yml +0 -107
- package/#Tools/SuperClaude/.claude/commands/shared/command-memory.yml +0 -52
- package/#Tools/SuperClaude/.claude/commands/shared/command-templates.yml +0 -167
- package/#Tools/SuperClaude/.claude/commands/shared/config-validator.yml +0 -206
- package/#Tools/SuperClaude/.claude/commands/shared/constants.yml +0 -311
- package/#Tools/SuperClaude/.claude/commands/shared/documentation-dirs.yml +0 -134
- package/#Tools/SuperClaude/.claude/commands/shared/error-handling.yml +0 -341
- package/#Tools/SuperClaude/.claude/commands/shared/evidence.yml +0 -75
- package/#Tools/SuperClaude/.claude/commands/shared/execution-lifecycle.yml +0 -277
- package/#Tools/SuperClaude/.claude/commands/shared/git-operations.yml +0 -217
- package/#Tools/SuperClaude/.claude/commands/shared/git-workflow.yml +0 -37
- package/#Tools/SuperClaude/.claude/commands/shared/implementation.yml +0 -199
- package/#Tools/SuperClaude/.claude/commands/shared/loading-config.yml +0 -73
- package/#Tools/SuperClaude/.claude/commands/shared/mcp-flags.yml +0 -109
- package/#Tools/SuperClaude/.claude/commands/shared/migration-report.md +0 -165
- package/#Tools/SuperClaude/.claude/commands/shared/patterns.yml +0 -155
- package/#Tools/SuperClaude/.claude/commands/shared/performance.yml +0 -317
- package/#Tools/SuperClaude/.claude/commands/shared/planning-mode.yml +0 -51
- package/#Tools/SuperClaude/.claude/commands/shared/research-first.yml +0 -278
- package/#Tools/SuperClaude/.claude/commands/shared/session-recovery.yml +0 -144
- package/#Tools/SuperClaude/.claude/commands/shared/severity-levels.yml +0 -252
- package/#Tools/SuperClaude/.claude/commands/shared/task-system.yml +0 -380
- package/#Tools/SuperClaude/.claude/commands/shared/task-ultracompressed.yml +0 -88
- package/#Tools/SuperClaude/.claude/commands/shared/templates/feature-template.md +0 -59
- package/#Tools/SuperClaude/.claude/commands/shared/templates/task-ultracompressed.md +0 -26
- package/#Tools/SuperClaude/.claude/commands/shared/thinking-modes.yml +0 -61
- package/#Tools/SuperClaude/.claude/commands/shared/ultracompressed.yml +0 -97
- package/#Tools/SuperClaude/.claude/commands/shared/user-experience.yml +0 -208
- package/#Tools/SuperClaude/.claude/commands/shared/validation.yml +0 -54
- package/#Tools/SuperClaude/.claude/commands/shared/workflow-chains.yml +0 -138
- package/#Tools/SuperClaude/.claude/commands/spawn.md +0 -106
- package/#Tools/SuperClaude/.claude/commands/task.md +0 -110
- package/#Tools/SuperClaude/.claude/commands/test.md +0 -67
- package/#Tools/SuperClaude/.claude/commands/troubleshoot.md +0 -93
- package/#Tools/SuperClaude/.github/ISSUE_TEMPLATE/bug_report.yml +0 -114
- package/#Tools/SuperClaude/.github/ISSUE_TEMPLATE/feature_request.yml +0 -119
- package/#Tools/SuperClaude/.github/ISSUE_TEMPLATE/question.yml +0 -87
- package/#Tools/SuperClaude/.github/pull_request_template.md +0 -62
- package/#Tools/SuperClaude/CHANGELOG.md +0 -109
- package/#Tools/SuperClaude/CLAUDE.md +0 -95
- package/#Tools/SuperClaude/CODE_OF_CONDUCT.md +0 -45
- package/#Tools/SuperClaude/CONTRIBUTING.md +0 -225
- package/#Tools/SuperClaude/Commands_Cheat_Sheet.md +0 -160
- package/#Tools/SuperClaude/LICENSE +0 -21
- package/#Tools/SuperClaude/MCP.md +0 -117
- package/#Tools/SuperClaude/PERSONAS.md +0 -115
- package/#Tools/SuperClaude/README.md +0 -249
- package/#Tools/SuperClaude/RULES.md +0 -220
- package/#Tools/SuperClaude/SECURITY.md +0 -91
- package/#Tools/SuperClaude/install.sh +0 -296
- package/#Tools/claude-talk-to-figma-mcp/.github/workflows/test.yml +0 -36
- package/#Tools/claude-talk-to-figma-mcp/CHANGELOG.md +0 -88
- package/#Tools/claude-talk-to-figma-mcp/Dockerfile +0 -16
- package/#Tools/claude-talk-to-figma-mcp/LICENSE +0 -22
- package/#Tools/claude-talk-to-figma-mcp/TESTING.md +0 -216
- package/#Tools/claude-talk-to-figma-mcp/bun.lock +0 -964
- package/#Tools/claude-talk-to-figma-mcp/context/01/00-analisis.md +0 -151
- package/#Tools/claude-talk-to-figma-mcp/context/01/01-tools-refactor.md +0 -370
- package/#Tools/claude-talk-to-figma-mcp/context/01/02-tools-refactor-backlog.md +0 -253
- package/#Tools/claude-talk-to-figma-mcp/context/01/03-tools-refactor-test.md +0 -138
- package/#Tools/claude-talk-to-figma-mcp/context/01/04-tools-refactor-channel-error.md +0 -133
- package/#Tools/claude-talk-to-figma-mcp/context/01/04-tools-refactor-test-backlog.md +0 -0
- package/#Tools/claude-talk-to-figma-mcp/context/01/05-tools-refactor-four-tools-error.md +0 -356
- package/#Tools/claude-talk-to-figma-mcp/context/01/06-tools-refactor-four-tools-error-backlog.md +0 -425
- package/#Tools/claude-talk-to-figma-mcp/context/01/prompt-dev-nodejs-bun.md +0 -105
- package/#Tools/claude-talk-to-figma-mcp/context/01-prompt.md +0 -135
- package/#Tools/claude-talk-to-figma-mcp/context/02/00-analisis.md +0 -131
- package/#Tools/claude-talk-to-figma-mcp/context/02/01-plan.md +0 -162
- package/#Tools/claude-talk-to-figma-mcp/context/02/02-backlog.md +0 -241
- package/#Tools/claude-talk-to-figma-mcp/context/02-analisis.md +0 -202
- package/#Tools/claude-talk-to-figma-mcp/context/99-notas.md +0 -139
- package/#Tools/claude-talk-to-figma-mcp/context/pr-14/pr-14-analisis.md +0 -221
- package/#Tools/claude-talk-to-figma-mcp/context/pr-14/pr-14-claude-tests.md +0 -152
- package/#Tools/claude-talk-to-figma-mcp/context/pr-14/pr-14-npm-publishing.md +0 -272
- package/#Tools/claude-talk-to-figma-mcp/images/claude-talk-to-figma.png +0 -0
- package/#Tools/claude-talk-to-figma-mcp/images/mcp-figma-plugin-configuration.png +0 -0
- package/#Tools/claude-talk-to-figma-mcp/jest.config.cjs +0 -25
- package/#Tools/claude-talk-to-figma-mcp/package.json +0 -67
- package/#Tools/claude-talk-to-figma-mcp/prompts/prompt-ux-ui-especialista-es.md +0 -82
- package/#Tools/claude-talk-to-figma-mcp/prompts/prompt-ux-ui-especialista-gal.md +0 -80
- package/#Tools/claude-talk-to-figma-mcp/prompts/prompt-ux-ui-especialista-pt.md +0 -80
- package/#Tools/claude-talk-to-figma-mcp/prompts/prompt-ux-ui-specialist.md +0 -80
- package/#Tools/claude-talk-to-figma-mcp/readme.md +0 -307
- package/#Tools/claude-talk-to-figma-mcp/scripts/configure-claude.js +0 -90
- package/#Tools/claude-talk-to-figma-mcp/scripts/setup.sh +0 -30
- package/#Tools/claude-talk-to-figma-mcp/scripts/test-integration.js +0 -339
- package/#Tools/claude-talk-to-figma-mcp/smithery.yaml +0 -26
- package/#Tools/claude-talk-to-figma-mcp/src/claude_mcp_plugin/code.js +0 -3295
- package/#Tools/claude-talk-to-figma-mcp/src/claude_mcp_plugin/manifest.json +0 -24
- package/#Tools/claude-talk-to-figma-mcp/src/claude_mcp_plugin/setcharacters.js +0 -215
- package/#Tools/claude-talk-to-figma-mcp/src/claude_mcp_plugin/ui.html +0 -656
- package/#Tools/claude-talk-to-figma-mcp/src/socket.ts +0 -349
- package/#Tools/claude-talk-to-figma-mcp/src/talk_to_figma_mcp/bun.lock +0 -246
- package/#Tools/claude-talk-to-figma-mcp/src/talk_to_figma_mcp/config/config.ts +0 -22
- package/#Tools/claude-talk-to-figma-mcp/src/talk_to_figma_mcp/package.json +0 -31
- package/#Tools/claude-talk-to-figma-mcp/src/talk_to_figma_mcp/prompts/index.ts +0 -297
- package/#Tools/claude-talk-to-figma-mcp/src/talk_to_figma_mcp/server.ts +0 -62
- package/#Tools/claude-talk-to-figma-mcp/src/talk_to_figma_mcp/tools/component-tools.ts +0 -48
- package/#Tools/claude-talk-to-figma-mcp/src/talk_to_figma_mcp/tools/creation-tools.ts +0 -620
- package/#Tools/claude-talk-to-figma-mcp/src/talk_to_figma_mcp/tools/document-tools.ts +0 -390
- package/#Tools/claude-talk-to-figma-mcp/src/talk_to_figma_mcp/tools/index.ts +0 -28
- package/#Tools/claude-talk-to-figma-mcp/src/talk_to_figma_mcp/tools/modification-tools.ts +0 -408
- package/#Tools/claude-talk-to-figma-mcp/src/talk_to_figma_mcp/tools/text-tools.ts +0 -531
- package/#Tools/claude-talk-to-figma-mcp/src/talk_to_figma_mcp/tsconfig.json +0 -18
- package/#Tools/claude-talk-to-figma-mcp/src/talk_to_figma_mcp/types/color.ts +0 -13
- package/#Tools/claude-talk-to-figma-mcp/src/talk_to_figma_mcp/types/index.ts +0 -87
- package/#Tools/claude-talk-to-figma-mcp/src/talk_to_figma_mcp/utils/defaults.ts +0 -23
- package/#Tools/claude-talk-to-figma-mcp/src/talk_to_figma_mcp/utils/figma-helpers.ts +0 -142
- package/#Tools/claude-talk-to-figma-mcp/src/talk_to_figma_mcp/utils/logger.ts +0 -8
- package/#Tools/claude-talk-to-figma-mcp/src/talk_to_figma_mcp/utils/websocket.ts +0 -258
- package/#Tools/claude-talk-to-figma-mcp/tests/fixtures/test-data.ts +0 -32
- package/#Tools/claude-talk-to-figma-mcp/tests/integration/set-fill-color.test.ts +0 -309
- package/#Tools/claude-talk-to-figma-mcp/tests/integration/set-stroke-color.test.ts +0 -387
- package/#Tools/claude-talk-to-figma-mcp/tests/setup.ts +0 -7
- package/#Tools/claude-talk-to-figma-mcp/tests/unit/utils/defaults.test.ts +0 -131
- package/#Tools/claude-talk-to-figma-mcp/tsconfig.json +0 -17
- package/#Tools/claude-talk-to-figma-mcp/tsup.config.ts +0 -14
- package/#Tools/jwt creator/AuthKey_MZ7UQGFV2Q.p8 +0 -6
- package/#Tools/jwt creator/generate_apple_jwt.js +0 -18
- package/#Tools/jwt creator/node_modules/.package-lock.json +0 -145
- package/#Tools/jwt creator/node_modules/buffer-equal-constant-time/.travis.yml +0 -4
- package/#Tools/jwt creator/node_modules/buffer-equal-constant-time/LICENSE.txt +0 -12
- package/#Tools/jwt creator/node_modules/buffer-equal-constant-time/README.md +0 -50
- package/#Tools/jwt creator/node_modules/buffer-equal-constant-time/index.js +0 -41
- package/#Tools/jwt creator/node_modules/buffer-equal-constant-time/package.json +0 -21
- package/#Tools/jwt creator/node_modules/buffer-equal-constant-time/test.js +0 -42
- package/#Tools/jwt creator/node_modules/ecdsa-sig-formatter/CODEOWNERS +0 -1
- package/#Tools/jwt creator/node_modules/ecdsa-sig-formatter/LICENSE +0 -201
- package/#Tools/jwt creator/node_modules/ecdsa-sig-formatter/README.md +0 -65
- package/#Tools/jwt creator/node_modules/ecdsa-sig-formatter/package.json +0 -46
- package/#Tools/jwt creator/node_modules/ecdsa-sig-formatter/src/ecdsa-sig-formatter.d.ts +0 -17
- package/#Tools/jwt creator/node_modules/ecdsa-sig-formatter/src/ecdsa-sig-formatter.js +0 -187
- package/#Tools/jwt creator/node_modules/ecdsa-sig-formatter/src/param-bytes-for-alg.js +0 -23
- package/#Tools/jwt creator/node_modules/jsonwebtoken/LICENSE +0 -21
- package/#Tools/jwt creator/node_modules/jsonwebtoken/README.md +0 -396
- package/#Tools/jwt creator/node_modules/jsonwebtoken/decode.js +0 -30
- package/#Tools/jwt creator/node_modules/jsonwebtoken/index.js +0 -8
- package/#Tools/jwt creator/node_modules/jsonwebtoken/lib/JsonWebTokenError.js +0 -14
- package/#Tools/jwt creator/node_modules/jsonwebtoken/lib/NotBeforeError.js +0 -13
- package/#Tools/jwt creator/node_modules/jsonwebtoken/lib/TokenExpiredError.js +0 -13
- package/#Tools/jwt creator/node_modules/jsonwebtoken/lib/asymmetricKeyDetailsSupported.js +0 -3
- package/#Tools/jwt creator/node_modules/jsonwebtoken/lib/psSupported.js +0 -3
- package/#Tools/jwt creator/node_modules/jsonwebtoken/lib/rsaPssKeyDetailsSupported.js +0 -3
- package/#Tools/jwt creator/node_modules/jsonwebtoken/lib/timespan.js +0 -18
- package/#Tools/jwt creator/node_modules/jsonwebtoken/lib/validateAsymmetricKey.js +0 -66
- package/#Tools/jwt creator/node_modules/jsonwebtoken/package.json +0 -71
- package/#Tools/jwt creator/node_modules/jsonwebtoken/sign.js +0 -253
- package/#Tools/jwt creator/node_modules/jsonwebtoken/verify.js +0 -263
- package/#Tools/jwt creator/node_modules/jwa/LICENSE +0 -17
- package/#Tools/jwt creator/node_modules/jwa/README.md +0 -150
- package/#Tools/jwt creator/node_modules/jwa/index.js +0 -266
- package/#Tools/jwt creator/node_modules/jwa/package.json +0 -37
- package/#Tools/jwt creator/node_modules/jws/CHANGELOG.md +0 -34
- package/#Tools/jwt creator/node_modules/jws/LICENSE +0 -17
- package/#Tools/jwt creator/node_modules/jws/index.js +0 -22
- package/#Tools/jwt creator/node_modules/jws/lib/data-stream.js +0 -55
- package/#Tools/jwt creator/node_modules/jws/lib/sign-stream.js +0 -78
- package/#Tools/jwt creator/node_modules/jws/lib/tostring.js +0 -10
- package/#Tools/jwt creator/node_modules/jws/lib/verify-stream.js +0 -120
- package/#Tools/jwt creator/node_modules/jws/package.json +0 -34
- package/#Tools/jwt creator/node_modules/jws/readme.md +0 -255
- package/#Tools/jwt creator/node_modules/lodash.includes/LICENSE +0 -47
- package/#Tools/jwt creator/node_modules/lodash.includes/README.md +0 -18
- package/#Tools/jwt creator/node_modules/lodash.includes/index.js +0 -745
- package/#Tools/jwt creator/node_modules/lodash.includes/package.json +0 -17
- package/#Tools/jwt creator/node_modules/lodash.isboolean/LICENSE +0 -22
- package/#Tools/jwt creator/node_modules/lodash.isboolean/README.md +0 -18
- package/#Tools/jwt creator/node_modules/lodash.isboolean/index.js +0 -70
- package/#Tools/jwt creator/node_modules/lodash.isboolean/package.json +0 -17
- package/#Tools/jwt creator/node_modules/lodash.isinteger/LICENSE +0 -47
- package/#Tools/jwt creator/node_modules/lodash.isinteger/README.md +0 -18
- package/#Tools/jwt creator/node_modules/lodash.isinteger/index.js +0 -265
- package/#Tools/jwt creator/node_modules/lodash.isinteger/package.json +0 -17
- package/#Tools/jwt creator/node_modules/lodash.isnumber/LICENSE +0 -22
- package/#Tools/jwt creator/node_modules/lodash.isnumber/README.md +0 -18
- package/#Tools/jwt creator/node_modules/lodash.isnumber/index.js +0 -79
- package/#Tools/jwt creator/node_modules/lodash.isnumber/package.json +0 -17
- package/#Tools/jwt creator/node_modules/lodash.isplainobject/LICENSE +0 -47
- package/#Tools/jwt creator/node_modules/lodash.isplainobject/README.md +0 -18
- package/#Tools/jwt creator/node_modules/lodash.isplainobject/index.js +0 -139
- package/#Tools/jwt creator/node_modules/lodash.isplainobject/package.json +0 -17
- package/#Tools/jwt creator/node_modules/lodash.isstring/LICENSE +0 -22
- package/#Tools/jwt creator/node_modules/lodash.isstring/README.md +0 -18
- package/#Tools/jwt creator/node_modules/lodash.isstring/index.js +0 -95
- package/#Tools/jwt creator/node_modules/lodash.isstring/package.json +0 -17
- package/#Tools/jwt creator/node_modules/lodash.once/LICENSE +0 -47
- package/#Tools/jwt creator/node_modules/lodash.once/README.md +0 -18
- package/#Tools/jwt creator/node_modules/lodash.once/index.js +0 -294
- package/#Tools/jwt creator/node_modules/lodash.once/package.json +0 -17
- package/#Tools/jwt creator/node_modules/ms/index.js +0 -162
- package/#Tools/jwt creator/node_modules/ms/license.md +0 -21
- package/#Tools/jwt creator/node_modules/ms/package.json +0 -38
- package/#Tools/jwt creator/node_modules/ms/readme.md +0 -59
- package/#Tools/jwt creator/node_modules/safe-buffer/LICENSE +0 -21
- package/#Tools/jwt creator/node_modules/safe-buffer/README.md +0 -584
- package/#Tools/jwt creator/node_modules/safe-buffer/index.d.ts +0 -187
- package/#Tools/jwt creator/node_modules/safe-buffer/index.js +0 -65
- package/#Tools/jwt creator/node_modules/safe-buffer/package.json +0 -51
- package/#Tools/jwt creator/node_modules/semver/LICENSE +0 -15
- package/#Tools/jwt creator/node_modules/semver/README.md +0 -664
- package/#Tools/jwt creator/node_modules/semver/bin/semver.js +0 -191
- package/#Tools/jwt creator/node_modules/semver/classes/comparator.js +0 -143
- package/#Tools/jwt creator/node_modules/semver/classes/index.js +0 -7
- package/#Tools/jwt creator/node_modules/semver/classes/range.js +0 -556
- package/#Tools/jwt creator/node_modules/semver/classes/semver.js +0 -319
- package/#Tools/jwt creator/node_modules/semver/functions/clean.js +0 -8
- package/#Tools/jwt creator/node_modules/semver/functions/cmp.js +0 -54
- package/#Tools/jwt creator/node_modules/semver/functions/coerce.js +0 -62
- package/#Tools/jwt creator/node_modules/semver/functions/compare-build.js +0 -9
- package/#Tools/jwt creator/node_modules/semver/functions/compare-loose.js +0 -5
- package/#Tools/jwt creator/node_modules/semver/functions/compare.js +0 -7
- package/#Tools/jwt creator/node_modules/semver/functions/diff.js +0 -60
- package/#Tools/jwt creator/node_modules/semver/functions/eq.js +0 -5
- package/#Tools/jwt creator/node_modules/semver/functions/gt.js +0 -5
- package/#Tools/jwt creator/node_modules/semver/functions/gte.js +0 -5
- package/#Tools/jwt creator/node_modules/semver/functions/inc.js +0 -21
- package/#Tools/jwt creator/node_modules/semver/functions/lt.js +0 -5
- package/#Tools/jwt creator/node_modules/semver/functions/lte.js +0 -5
- package/#Tools/jwt creator/node_modules/semver/functions/major.js +0 -5
- package/#Tools/jwt creator/node_modules/semver/functions/minor.js +0 -5
- package/#Tools/jwt creator/node_modules/semver/functions/neq.js +0 -5
- package/#Tools/jwt creator/node_modules/semver/functions/parse.js +0 -18
- package/#Tools/jwt creator/node_modules/semver/functions/patch.js +0 -5
- package/#Tools/jwt creator/node_modules/semver/functions/prerelease.js +0 -8
- package/#Tools/jwt creator/node_modules/semver/functions/rcompare.js +0 -5
- package/#Tools/jwt creator/node_modules/semver/functions/rsort.js +0 -5
- package/#Tools/jwt creator/node_modules/semver/functions/satisfies.js +0 -12
- package/#Tools/jwt creator/node_modules/semver/functions/sort.js +0 -5
- package/#Tools/jwt creator/node_modules/semver/functions/valid.js +0 -8
- package/#Tools/jwt creator/node_modules/semver/index.js +0 -91
- package/#Tools/jwt creator/node_modules/semver/internal/constants.js +0 -37
- package/#Tools/jwt creator/node_modules/semver/internal/debug.js +0 -11
- package/#Tools/jwt creator/node_modules/semver/internal/identifiers.js +0 -25
- package/#Tools/jwt creator/node_modules/semver/internal/lrucache.js +0 -42
- package/#Tools/jwt creator/node_modules/semver/internal/parse-options.js +0 -17
- package/#Tools/jwt creator/node_modules/semver/internal/re.js +0 -223
- package/#Tools/jwt creator/node_modules/semver/package.json +0 -78
- package/#Tools/jwt creator/node_modules/semver/preload.js +0 -4
- package/#Tools/jwt creator/node_modules/semver/range.bnf +0 -16
- package/#Tools/jwt creator/node_modules/semver/ranges/gtr.js +0 -6
- package/#Tools/jwt creator/node_modules/semver/ranges/intersects.js +0 -9
- package/#Tools/jwt creator/node_modules/semver/ranges/ltr.js +0 -6
- package/#Tools/jwt creator/node_modules/semver/ranges/max-satisfying.js +0 -27
- package/#Tools/jwt creator/node_modules/semver/ranges/min-satisfying.js +0 -26
- package/#Tools/jwt creator/node_modules/semver/ranges/min-version.js +0 -63
- package/#Tools/jwt creator/node_modules/semver/ranges/outside.js +0 -82
- package/#Tools/jwt creator/node_modules/semver/ranges/simplify.js +0 -49
- package/#Tools/jwt creator/node_modules/semver/ranges/subset.js +0 -249
- package/#Tools/jwt creator/node_modules/semver/ranges/to-comparators.js +0 -10
- package/#Tools/jwt creator/node_modules/semver/ranges/valid.js +0 -13
- package/#Tools/jwt creator/package-lock.json +0 -150
- package/#Tools/jwt creator/package.json +0 -5
- package/#Tools/package.json +0 -94
- package/SuperClaude/.claude/commands/analyze.md +0 -36
- package/SuperClaude/.claude/commands/build.md +0 -35
- package/SuperClaude/.claude/commands/cleanup.md +0 -46
- package/SuperClaude/.claude/commands/deploy.md +0 -55
- package/SuperClaude/.claude/commands/design.md +0 -91
- package/SuperClaude/.claude/commands/dev-setup.md +0 -37
- package/SuperClaude/.claude/commands/document.md +0 -99
- package/SuperClaude/.claude/commands/estimate.md +0 -69
- package/SuperClaude/.claude/commands/explain.md +0 -85
- package/SuperClaude/.claude/commands/git.md +0 -47
- package/SuperClaude/.claude/commands/improve.md +0 -162
- package/SuperClaude/.claude/commands/index.md +0 -159
- package/SuperClaude/.claude/commands/load.md +0 -65
- package/SuperClaude/.claude/commands/migrate.md +0 -76
- package/SuperClaude/.claude/commands/scan.md +0 -163
- package/SuperClaude/.claude/commands/shared/ambiguity-check.yml +0 -40
- package/SuperClaude/.claude/commands/shared/audit.yml +0 -21
- package/SuperClaude/.claude/commands/shared/checkpoint.yml +0 -28
- package/SuperClaude/.claude/commands/shared/cleanup-patterns.yml +0 -107
- package/SuperClaude/.claude/commands/shared/command-memory.yml +0 -52
- package/SuperClaude/.claude/commands/shared/command-templates.yml +0 -167
- package/SuperClaude/.claude/commands/shared/config-validator.yml +0 -206
- package/SuperClaude/.claude/commands/shared/constants.yml +0 -311
- package/SuperClaude/.claude/commands/shared/documentation-dirs.yml +0 -134
- package/SuperClaude/.claude/commands/shared/error-handling.yml +0 -341
- package/SuperClaude/.claude/commands/shared/evidence.yml +0 -75
- package/SuperClaude/.claude/commands/shared/execution-lifecycle.yml +0 -277
- package/SuperClaude/.claude/commands/shared/git-operations.yml +0 -217
- package/SuperClaude/.claude/commands/shared/git-workflow.yml +0 -37
- package/SuperClaude/.claude/commands/shared/implementation.yml +0 -199
- package/SuperClaude/.claude/commands/shared/loading-config.yml +0 -73
- package/SuperClaude/.claude/commands/shared/mcp-flags.yml +0 -109
- package/SuperClaude/.claude/commands/shared/migration-report.md +0 -165
- package/SuperClaude/.claude/commands/shared/patterns.yml +0 -155
- package/SuperClaude/.claude/commands/shared/performance.yml +0 -317
- package/SuperClaude/.claude/commands/shared/planning-mode.yml +0 -51
- package/SuperClaude/.claude/commands/shared/research-first.yml +0 -278
- package/SuperClaude/.claude/commands/shared/session-recovery.yml +0 -144
- package/SuperClaude/.claude/commands/shared/severity-levels.yml +0 -252
- package/SuperClaude/.claude/commands/shared/task-system.yml +0 -380
- package/SuperClaude/.claude/commands/shared/task-ultracompressed.yml +0 -88
- package/SuperClaude/.claude/commands/shared/templates/feature-template.md +0 -59
- package/SuperClaude/.claude/commands/shared/templates/task-ultracompressed.md +0 -26
- package/SuperClaude/.claude/commands/shared/thinking-modes.yml +0 -61
- package/SuperClaude/.claude/commands/shared/ultracompressed.yml +0 -97
- package/SuperClaude/.claude/commands/shared/user-experience.yml +0 -208
- package/SuperClaude/.claude/commands/shared/validation.yml +0 -54
- package/SuperClaude/.claude/commands/shared/workflow-chains.yml +0 -138
- package/SuperClaude/.claude/commands/spawn.md +0 -106
- package/SuperClaude/.claude/commands/task.md +0 -110
- package/SuperClaude/.claude/commands/test.md +0 -67
- package/SuperClaude/.claude/commands/troubleshoot.md +0 -93
- package/SuperClaude/.github/ISSUE_TEMPLATE/bug_report.yml +0 -114
- package/SuperClaude/.github/ISSUE_TEMPLATE/feature_request.yml +0 -119
- package/SuperClaude/.github/ISSUE_TEMPLATE/question.yml +0 -87
- package/SuperClaude/.github/pull_request_template.md +0 -62
- package/SuperClaude/CHANGELOG.md +0 -109
- package/SuperClaude/CLAUDE.md +0 -95
- package/SuperClaude/CODE_OF_CONDUCT.md +0 -45
- package/SuperClaude/CONTRIBUTING.md +0 -225
- package/SuperClaude/Commands_Cheat_Sheet.md +0 -160
- package/SuperClaude/LICENSE +0 -21
- package/SuperClaude/MCP.md +0 -117
- package/SuperClaude/PERSONAS.md +0 -115
- package/SuperClaude/README.md +0 -249
- package/SuperClaude/RULES.md +0 -220
- package/SuperClaude/SECURITY.md +0 -91
- package/SuperClaude/install.sh +0 -296
- package/appiq-mcp installer/How-to.md +0 -250
- package/appiq-mcp installer/LICENSE +0 -21
- package/appiq-mcp installer/README.md +0 -235
- package/appiq-mcp installer/index.js +0 -672
- package/appiq-mcp installer/my_mcps.md +0 -105
- package/appiq-mcp installer/package-lock.json +0 -708
- package/appiq-mcp installer/package.json +0 -46
- package/appiq-mcp installer/test-install.bat +0 -60
- package/appiq-mcp installer/test-install.sh +0 -55
- package/appiq-mcp installer/test-load.js +0 -40
- package/bmad-core/agent-teams/team-flutter-mobile.yaml +0 -114
- package/bmad-core/agents/bmad-smart-launcher.md +0 -170
- package/bmad-core/agents/init-flow-po.md +0 -143
- package/bmad-core/checklists/security-validation-checklist.md +0 -332
- package/bmad-core/core-config.yaml.bak +0 -20
- package/bmad-core/data/backend-services-integration.md +0 -686
- package/bmad-core/data/shadcn-ui-integration.md +0 -388
- package/bmad-core/tasks/create-flutter-story.md +0 -197
- package/bmad-core/tasks/intelligent-epic-creation.md +0 -234
- package/bmad-core/tasks/smart-project-analysis.md +0 -289
- package/bmad-core/templates/flutter-mobile-prd-tmpl.yaml +0 -330
- package/bmad-core/templates/flutter-story-tmpl.yaml +0 -376
- package/bmad-core/templates/flutter-ui-spec-tmpl.yaml +0 -415
- package/dist/expansion-packs/bmad-mobile-app-dev/agents/mobile-analytics.txt +0 -1530
- package/dist/expansion-packs/bmad-mobile-app-dev/agents/mobile-architect.txt +0 -322
- package/dist/expansion-packs/bmad-mobile-app-dev/agents/mobile-developer.txt +0 -692
- package/dist/expansion-packs/bmad-mobile-app-dev/agents/mobile-pm.txt +0 -382
- package/dist/expansion-packs/bmad-mobile-app-dev/agents/mobile-qa.txt +0 -665
- package/dist/expansion-packs/bmad-mobile-app-dev/agents/mobile-security.txt +0 -1070
- package/dist/expansion-packs/bmad-mobile-app-dev/agents/mobile-ux-expert.txt +0 -1494
- package/dist/expansion-packs/bmad-mobile-app-dev/teams/mobile-team-cross-platform.txt +0 -15613
- package/flutter/CLAUDE.md +0 -58
- package/flutter/punkt.claude/New Folder With Items/channel.md +0 -221
- package/flutter/punkt.claude/New Folder With Items/commands/add-translations.md +0 -12
- package/flutter/punkt.claude/New Folder With Items/commands/create-feature.md +0 -168
- package/flutter/punkt.claude/New Folder With Items/commands/explain-codebase.md +0 -1
- package/flutter/punkt.claude/New Folder With Items/commands/feature-business-logic.md +0 -551
- package/flutter/punkt.claude/New Folder With Items/commands/feature-data-layer.md +0 -924
- package/flutter/punkt.claude/New Folder With Items/commands/feature-domain-layer.md +0 -540
- package/flutter/punkt.claude/New Folder With Items/commands/feature-integration.md +0 -493
- package/flutter/punkt.claude/New Folder With Items/commands/feature-localization.md +0 -427
- package/flutter/punkt.claude/New Folder With Items/commands/feature-ui-layer.md +0 -300
- package/flutter/punkt.claude/New Folder With Items/commands/git-commit.md +0 -3
- package/flutter/punkt.claude/New Folder With Items/commands/search-web.md +0 -1
- package/flutter/punkt.claude/punkt.claude/channel.md +0 -225
- package/flutter/punkt.claude/punkt.claude/commands/add-translations.md +0 -60
- package/flutter/punkt.claude/punkt.claude/commands/bug-fix.md +0 -32
- package/flutter/punkt.claude/punkt.claude/commands/checkdev.md +0 -122
- package/flutter/punkt.claude/punkt.claude/commands/create-feature.md +0 -174
- package/flutter/punkt.claude/punkt.claude/commands/dev.md +0 -79
- package/flutter/punkt.claude/punkt.claude/commands/explain-codebase.md +0 -1
- package/flutter/punkt.claude/punkt.claude/commands/feature-business-logic.md +0 -430
- package/flutter/punkt.claude/punkt.claude/commands/feature-data-layer.md +0 -572
- package/flutter/punkt.claude/punkt.claude/commands/feature-domain-layer.md +0 -537
- package/flutter/punkt.claude/punkt.claude/commands/feature-integration.md +0 -493
- package/flutter/punkt.claude/punkt.claude/commands/feature-localization.md +0 -427
- package/flutter/punkt.claude/punkt.claude/commands/feature-ui-layer.md +0 -831
- package/flutter/punkt.claude/punkt.claude/commands/git-commit.md +0 -3
- package/flutter/punkt.claude/punkt.claude/commands/manage-translations.md +0 -433
- package/flutter/punkt.claude/punkt.claude/commands/search-web.md +0 -1
- package/flutter/punkt.claude/punkt.claude/developer_check.md +0 -121
- package/tools/appiq-installer.js +0 -2711
- package/tools/epic-solution-installer.js +0 -536
- package/tools/setup-ide-commands.js +0 -348
- package/tools/smart-installer.js +0 -592
- package/web/CLAUDE.md +0 -58
- /package/{#Tools/APPIQ-METHOD/.github → .github}/FUNDING.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/.github → .github}/ISSUE_TEMPLATE/bug_report.md +0 -0
- /package/{#Tools/APPIQ-METHOD/.github → .github}/ISSUE_TEMPLATE/feature_request.md +0 -0
- /package/{#Tools/APPIQ-METHOD/.github → .github}/workflows/release.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/.releaserc.json → .releaserc.json} +0 -0
- /package/{#Tools/APPIQ-METHOD/.vscode → .vscode}/settings.json +0 -0
- /package/{#Tools/APPIQ-METHOD/CHANGELOG.md → CHANGELOG.md} +0 -0
- /package/{#Tools/APPIQ-METHOD/CONTRIBUTING.md → CONTRIBUTING.md} +0 -0
- /package/{#Tools/APPIQ-METHOD/LICENSE → LICENSE} +0 -0
- /package/{#Tools/APPIQ-METHOD/common → common}/tasks/create-doc.md +0 -0
- /package/{#Tools/APPIQ-METHOD/common → common}/tasks/execute-checklist.md +0 -0
- /package/{#Tools/APPIQ-METHOD/common → common}/utils/bmad-doc-template.md +0 -0
- /package/{#Tools/APPIQ-METHOD/common → common}/utils/workflow-management.md +0 -0
- /package/{#Tools/APPIQ-METHOD/docs → docs}/GUIDING-PRINCIPLES.md +0 -0
- /package/{#Tools/APPIQ-METHOD/docs → docs}/core-architecture.md +0 -0
- /package/{#Tools/APPIQ-METHOD/docs → docs}/expansion-packs.md +0 -0
- /package/{#Tools/APPIQ-METHOD/docs → docs}/how-to-contribute-with-pull-requests.md +0 -0
- /package/{#Tools/APPIQ-METHOD/docs → docs}/versioning-and-releases.md +0 -0
- /package/{#Tools/APPIQ-METHOD/docs → docs}/versions.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/README.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs/bmad-flutter-mobile-dev → expansion-packs/appiq-flutter-mobile-dev}/agent-teams/flutter-mobile-team.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs/bmad-flutter-mobile-dev → expansion-packs/appiq-flutter-mobile-dev}/agents/flutter-cubit-agent.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs/bmad-flutter-mobile-dev → expansion-packs/appiq-flutter-mobile-dev}/agents/flutter-data-agent.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs/bmad-flutter-mobile-dev → expansion-packs/appiq-flutter-mobile-dev}/agents/flutter-domain-agent.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs/bmad-flutter-mobile-dev → expansion-packs/appiq-flutter-mobile-dev}/agents/flutter-ui-agent.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs/bmad-flutter-mobile-dev → expansion-packs/appiq-flutter-mobile-dev}/agents/shared-components-agent.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs/bmad-flutter-mobile-dev → expansion-packs/appiq-flutter-mobile-dev}/checklists/flutter-story-dod-checklist.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs/bmad-flutter-mobile-dev → expansion-packs/appiq-flutter-mobile-dev}/data/flutter-development-guidelines.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs/bmad-flutter-mobile-dev → expansion-packs/appiq-flutter-mobile-dev}/templates/flutter-mobile-architecture-tmpl.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs/bmad-flutter-mobile-dev → expansion-packs/appiq-flutter-mobile-dev}/workflows/flutter-ui-first-development.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-phaser-game-dev/agent-teams/phaser-2d-nodejs-game-team.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-phaser-game-dev/agents/game-designer.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-phaser-game-dev/agents/game-developer.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-phaser-game-dev/agents/game-sm.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-phaser-game-dev/checklists/game-design-checklist.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-phaser-game-dev/checklists/game-story-dod-checklist.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-phaser-game-dev/config.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-phaser-game-dev/data/bmad-kb.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-phaser-game-dev/data/development-guidelines.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-phaser-game-dev/tasks/advanced-elicitation.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-phaser-game-dev/tasks/create-game-story.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-phaser-game-dev/tasks/game-design-brainstorming.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-phaser-game-dev/templates/game-architecture-tmpl.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-phaser-game-dev/templates/game-brief-tmpl.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-phaser-game-dev/templates/game-design-doc-tmpl.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-phaser-game-dev/templates/game-story-tmpl.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-phaser-game-dev/templates/level-design-doc-tmpl.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-phaser-game-dev/workflows/game-dev-greenfield.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-phaser-game-dev/workflows/game-prototype.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/agent-teams/unity-2d-game-team.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/agents/game-architect.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/agents/game-designer.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/agents/game-developer.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/agents/game-sm.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/checklists/game-architect-checklist.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/checklists/game-change-checklist.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/checklists/game-design-checklist.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/checklists/game-story-dod-checklist.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/config.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/data/bmad-kb.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/data/development-guidelines.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/tasks/advanced-elicitation.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/tasks/correct-course-game.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/tasks/create-game-story.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/tasks/game-design-brainstorming.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/tasks/validate-game-story.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/templates/game-architecture-tmpl.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/templates/game-brief-tmpl.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/templates/game-design-doc-tmpl.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/templates/game-story-tmpl.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/templates/level-design-doc-tmpl.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/workflows/game-dev-greenfield.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-2d-unity-game-dev/workflows/game-prototype.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-infrastructure-devops/README.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-infrastructure-devops/agents/infra-devops-platform.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-infrastructure-devops/checklists/infrastructure-checklist.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-infrastructure-devops/config.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-infrastructure-devops/data/bmad-kb.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-infrastructure-devops/tasks/review-infrastructure.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-infrastructure-devops/tasks/validate-infrastructure.md +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-infrastructure-devops/templates/infrastructure-architecture-tmpl.yaml +0 -0
- /package/{#Tools/APPIQ-METHOD/expansion-packs → expansion-packs}/bmad-infrastructure-devops/templates/infrastructure-platform-from-arch-tmpl.yaml +0 -0
@@ -1,3295 +0,0 @@
|
|
1
|
-
// This is the main code file for the Claude MCP Figma plugin
|
2
|
-
// It handles Figma API commands
|
3
|
-
|
4
|
-
// Plugin state
|
5
|
-
const state = {
|
6
|
-
serverPort: 3055, // Default port
|
7
|
-
};
|
8
|
-
|
9
|
-
// Helper function for progress updates
|
10
|
-
function sendProgressUpdate(commandId, commandType, status, progress, totalItems, processedItems, message, payload = null) {
|
11
|
-
const update = {
|
12
|
-
type: 'command_progress',
|
13
|
-
commandId,
|
14
|
-
commandType,
|
15
|
-
status,
|
16
|
-
progress,
|
17
|
-
totalItems,
|
18
|
-
processedItems,
|
19
|
-
message,
|
20
|
-
timestamp: Date.now()
|
21
|
-
};
|
22
|
-
|
23
|
-
// Add optional chunk information if present
|
24
|
-
if (payload) {
|
25
|
-
if (payload.currentChunk !== undefined && payload.totalChunks !== undefined) {
|
26
|
-
update.currentChunk = payload.currentChunk;
|
27
|
-
update.totalChunks = payload.totalChunks;
|
28
|
-
update.chunkSize = payload.chunkSize;
|
29
|
-
}
|
30
|
-
update.payload = payload;
|
31
|
-
}
|
32
|
-
|
33
|
-
// Send to UI
|
34
|
-
figma.ui.postMessage(update);
|
35
|
-
console.log(`Progress update: ${status} - ${progress}% - ${message}`);
|
36
|
-
|
37
|
-
return update;
|
38
|
-
}
|
39
|
-
|
40
|
-
// Show UI
|
41
|
-
figma.showUI(__html__, { width: 350, height: 450 });
|
42
|
-
|
43
|
-
// Plugin commands from UI
|
44
|
-
figma.ui.onmessage = async (msg) => {
|
45
|
-
switch (msg.type) {
|
46
|
-
case "update-settings":
|
47
|
-
updateSettings(msg);
|
48
|
-
break;
|
49
|
-
case "notify":
|
50
|
-
figma.notify(msg.message);
|
51
|
-
break;
|
52
|
-
case "close-plugin":
|
53
|
-
figma.closePlugin();
|
54
|
-
break;
|
55
|
-
case "execute-command":
|
56
|
-
// Execute commands received from UI (which gets them from WebSocket)
|
57
|
-
try {
|
58
|
-
const result = await handleCommand(msg.command, msg.params);
|
59
|
-
// Send result back to UI
|
60
|
-
figma.ui.postMessage({
|
61
|
-
type: "command-result",
|
62
|
-
id: msg.id,
|
63
|
-
result,
|
64
|
-
});
|
65
|
-
} catch (error) {
|
66
|
-
figma.ui.postMessage({
|
67
|
-
type: "command-error",
|
68
|
-
id: msg.id,
|
69
|
-
error: error.message || "Error executing command",
|
70
|
-
});
|
71
|
-
}
|
72
|
-
break;
|
73
|
-
}
|
74
|
-
};
|
75
|
-
|
76
|
-
// Listen for plugin commands from menu
|
77
|
-
figma.on("run", ({ command }) => {
|
78
|
-
figma.ui.postMessage({ type: "auto-connect" });
|
79
|
-
});
|
80
|
-
|
81
|
-
// Update plugin settings
|
82
|
-
function updateSettings(settings) {
|
83
|
-
if (settings.serverPort) {
|
84
|
-
state.serverPort = settings.serverPort;
|
85
|
-
}
|
86
|
-
|
87
|
-
figma.clientStorage.setAsync("settings", {
|
88
|
-
serverPort: state.serverPort,
|
89
|
-
});
|
90
|
-
}
|
91
|
-
|
92
|
-
// Handle commands from UI
|
93
|
-
async function handleCommand(command, params) {
|
94
|
-
switch (command) {
|
95
|
-
case "get_document_info":
|
96
|
-
return await getDocumentInfo();
|
97
|
-
case "get_selection":
|
98
|
-
return await getSelection();
|
99
|
-
case "get_node_info":
|
100
|
-
if (!params || !params.nodeId) {
|
101
|
-
throw new Error("Missing nodeId parameter");
|
102
|
-
}
|
103
|
-
return await getNodeInfo(params.nodeId);
|
104
|
-
case "get_nodes_info":
|
105
|
-
if (!params || !params.nodeIds || !Array.isArray(params.nodeIds)) {
|
106
|
-
throw new Error("Missing or invalid nodeIds parameter");
|
107
|
-
}
|
108
|
-
return await getNodesInfo(params.nodeIds);
|
109
|
-
case "create_rectangle":
|
110
|
-
return await createRectangle(params);
|
111
|
-
case "create_frame":
|
112
|
-
return await createFrame(params);
|
113
|
-
case "create_text":
|
114
|
-
return await createText(params);
|
115
|
-
case "set_fill_color":
|
116
|
-
return await setFillColor(params);
|
117
|
-
case "set_stroke_color":
|
118
|
-
return await setStrokeColor(params);
|
119
|
-
case "move_node":
|
120
|
-
return await moveNode(params);
|
121
|
-
case "resize_node":
|
122
|
-
return await resizeNode(params);
|
123
|
-
case "delete_node":
|
124
|
-
return await deleteNode(params);
|
125
|
-
case "get_styles":
|
126
|
-
return await getStyles();
|
127
|
-
case "get_local_components":
|
128
|
-
return await getLocalComponents();
|
129
|
-
// case "get_team_components":
|
130
|
-
// return await getTeamComponents();
|
131
|
-
case "create_component_instance":
|
132
|
-
return await createComponentInstance(params);
|
133
|
-
case "export_node_as_image":
|
134
|
-
return await exportNodeAsImage(params);
|
135
|
-
case "set_corner_radius":
|
136
|
-
return await setCornerRadius(params);
|
137
|
-
case "set_text_content":
|
138
|
-
return await setTextContent(params);
|
139
|
-
case "clone_node":
|
140
|
-
return await cloneNode(params);
|
141
|
-
case "scan_text_nodes":
|
142
|
-
return await scanTextNodes(params);
|
143
|
-
case "set_multiple_text_contents":
|
144
|
-
return await setMultipleTextContents(params);
|
145
|
-
case "set_auto_layout":
|
146
|
-
return await setAutoLayout(params);
|
147
|
-
// Nuevos comandos para propiedades de texto
|
148
|
-
case "set_font_name":
|
149
|
-
return await setFontName(params);
|
150
|
-
case "set_font_size":
|
151
|
-
return await setFontSize(params);
|
152
|
-
case "set_font_weight":
|
153
|
-
return await setFontWeight(params);
|
154
|
-
case "set_letter_spacing":
|
155
|
-
return await setLetterSpacing(params);
|
156
|
-
case "set_line_height":
|
157
|
-
return await setLineHeight(params);
|
158
|
-
case "set_paragraph_spacing":
|
159
|
-
return await setParagraphSpacing(params);
|
160
|
-
case "set_text_case":
|
161
|
-
return await setTextCase(params);
|
162
|
-
case "set_text_decoration":
|
163
|
-
return await setTextDecoration(params);
|
164
|
-
case "get_styled_text_segments":
|
165
|
-
return await getStyledTextSegments(params);
|
166
|
-
case "load_font_async":
|
167
|
-
return await loadFontAsyncWrapper(params);
|
168
|
-
case "get_remote_components":
|
169
|
-
return await getRemoteComponents(params);
|
170
|
-
case "set_effects":
|
171
|
-
return await setEffects(params);
|
172
|
-
case "set_effect_style_id":
|
173
|
-
return await setEffectStyleId(params);
|
174
|
-
case "group_nodes":
|
175
|
-
return await groupNodes(params);
|
176
|
-
case "ungroup_nodes":
|
177
|
-
return await ungroupNodes(params);
|
178
|
-
case "flatten_node":
|
179
|
-
return await flattenNode(params);
|
180
|
-
case "insert_child":
|
181
|
-
return await insertChild(params);
|
182
|
-
case "create_ellipse":
|
183
|
-
return await createEllipse(params);
|
184
|
-
case "create_polygon":
|
185
|
-
return await createPolygon(params);
|
186
|
-
case "create_star":
|
187
|
-
return await createStar(params);
|
188
|
-
case "create_vector":
|
189
|
-
return await createVector(params);
|
190
|
-
case "create_line":
|
191
|
-
return await createLine(params);
|
192
|
-
default:
|
193
|
-
throw new Error(`Unknown command: ${command}`);
|
194
|
-
}
|
195
|
-
}
|
196
|
-
|
197
|
-
// Command implementations
|
198
|
-
|
199
|
-
async function getDocumentInfo() {
|
200
|
-
await figma.currentPage.loadAsync();
|
201
|
-
const page = figma.currentPage;
|
202
|
-
return {
|
203
|
-
name: page.name,
|
204
|
-
id: page.id,
|
205
|
-
type: page.type,
|
206
|
-
children: page.children.map((node) => ({
|
207
|
-
id: node.id,
|
208
|
-
name: node.name,
|
209
|
-
type: node.type,
|
210
|
-
})),
|
211
|
-
currentPage: {
|
212
|
-
id: page.id,
|
213
|
-
name: page.name,
|
214
|
-
childCount: page.children.length,
|
215
|
-
},
|
216
|
-
pages: [
|
217
|
-
{
|
218
|
-
id: page.id,
|
219
|
-
name: page.name,
|
220
|
-
childCount: page.children.length,
|
221
|
-
},
|
222
|
-
],
|
223
|
-
};
|
224
|
-
}
|
225
|
-
|
226
|
-
async function getSelection() {
|
227
|
-
return {
|
228
|
-
selectionCount: figma.currentPage.selection.length,
|
229
|
-
selection: figma.currentPage.selection.map((node) => ({
|
230
|
-
id: node.id,
|
231
|
-
name: node.name,
|
232
|
-
type: node.type,
|
233
|
-
visible: node.visible,
|
234
|
-
})),
|
235
|
-
};
|
236
|
-
}
|
237
|
-
|
238
|
-
async function getNodeInfo(nodeId) {
|
239
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
240
|
-
|
241
|
-
if (!node) {
|
242
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
243
|
-
}
|
244
|
-
|
245
|
-
const response = await node.exportAsync({
|
246
|
-
format: "JSON_REST_V1",
|
247
|
-
});
|
248
|
-
|
249
|
-
return response.document;
|
250
|
-
}
|
251
|
-
|
252
|
-
async function getNodesInfo(nodeIds) {
|
253
|
-
try {
|
254
|
-
// Load all nodes in parallel
|
255
|
-
const nodes = await Promise.all(
|
256
|
-
nodeIds.map((id) => figma.getNodeByIdAsync(id))
|
257
|
-
);
|
258
|
-
|
259
|
-
// Filter out any null values (nodes that weren't found)
|
260
|
-
const validNodes = nodes.filter((node) => node !== null);
|
261
|
-
|
262
|
-
// Export all valid nodes in parallel
|
263
|
-
const responses = await Promise.all(
|
264
|
-
validNodes.map(async (node) => {
|
265
|
-
const response = await node.exportAsync({
|
266
|
-
format: "JSON_REST_V1",
|
267
|
-
});
|
268
|
-
return {
|
269
|
-
nodeId: node.id,
|
270
|
-
document: response.document,
|
271
|
-
};
|
272
|
-
})
|
273
|
-
);
|
274
|
-
|
275
|
-
return responses;
|
276
|
-
} catch (error) {
|
277
|
-
throw new Error(`Error getting nodes info: ${error.message}`);
|
278
|
-
}
|
279
|
-
}
|
280
|
-
|
281
|
-
async function createRectangle(params) {
|
282
|
-
const {
|
283
|
-
x = 0,
|
284
|
-
y = 0,
|
285
|
-
width = 100,
|
286
|
-
height = 100,
|
287
|
-
name = "Rectangle",
|
288
|
-
parentId,
|
289
|
-
} = params || {};
|
290
|
-
|
291
|
-
const rect = figma.createRectangle();
|
292
|
-
rect.x = x;
|
293
|
-
rect.y = y;
|
294
|
-
rect.resize(width, height);
|
295
|
-
rect.name = name;
|
296
|
-
|
297
|
-
// If parentId is provided, append to that node, otherwise append to current page
|
298
|
-
if (parentId) {
|
299
|
-
const parentNode = await figma.getNodeByIdAsync(parentId);
|
300
|
-
if (!parentNode) {
|
301
|
-
throw new Error(`Parent node not found with ID: ${parentId}`);
|
302
|
-
}
|
303
|
-
if (!("appendChild" in parentNode)) {
|
304
|
-
throw new Error(`Parent node does not support children: ${parentId}`);
|
305
|
-
}
|
306
|
-
parentNode.appendChild(rect);
|
307
|
-
} else {
|
308
|
-
figma.currentPage.appendChild(rect);
|
309
|
-
}
|
310
|
-
|
311
|
-
return {
|
312
|
-
id: rect.id,
|
313
|
-
name: rect.name,
|
314
|
-
x: rect.x,
|
315
|
-
y: rect.y,
|
316
|
-
width: rect.width,
|
317
|
-
height: rect.height,
|
318
|
-
parentId: rect.parent ? rect.parent.id : undefined,
|
319
|
-
};
|
320
|
-
}
|
321
|
-
|
322
|
-
async function createFrame(params) {
|
323
|
-
const {
|
324
|
-
x = 0,
|
325
|
-
y = 0,
|
326
|
-
width = 100,
|
327
|
-
height = 100,
|
328
|
-
name = "Frame",
|
329
|
-
parentId,
|
330
|
-
fillColor,
|
331
|
-
strokeColor,
|
332
|
-
strokeWeight,
|
333
|
-
} = params || {};
|
334
|
-
|
335
|
-
const frame = figma.createFrame();
|
336
|
-
frame.x = x;
|
337
|
-
frame.y = y;
|
338
|
-
frame.resize(width, height);
|
339
|
-
frame.name = name;
|
340
|
-
|
341
|
-
// Set fill color if provided
|
342
|
-
if (fillColor) {
|
343
|
-
const paintStyle = {
|
344
|
-
type: "SOLID",
|
345
|
-
color: {
|
346
|
-
r: parseFloat(fillColor.r) || 0,
|
347
|
-
g: parseFloat(fillColor.g) || 0,
|
348
|
-
b: parseFloat(fillColor.b) || 0,
|
349
|
-
},
|
350
|
-
opacity: parseFloat(fillColor.a) || 1,
|
351
|
-
};
|
352
|
-
frame.fills = [paintStyle];
|
353
|
-
}
|
354
|
-
|
355
|
-
// Set stroke color and weight if provided
|
356
|
-
if (strokeColor) {
|
357
|
-
const strokeStyle = {
|
358
|
-
type: "SOLID",
|
359
|
-
color: {
|
360
|
-
r: parseFloat(strokeColor.r) || 0,
|
361
|
-
g: parseFloat(strokeColor.g) || 0,
|
362
|
-
b: parseFloat(strokeColor.b) || 0,
|
363
|
-
},
|
364
|
-
opacity: parseFloat(strokeColor.a) || 1,
|
365
|
-
};
|
366
|
-
frame.strokes = [strokeStyle];
|
367
|
-
}
|
368
|
-
|
369
|
-
// Set stroke weight if provided
|
370
|
-
if (strokeWeight !== undefined) {
|
371
|
-
frame.strokeWeight = strokeWeight;
|
372
|
-
}
|
373
|
-
|
374
|
-
// If parentId is provided, append to that node, otherwise append to current page
|
375
|
-
if (parentId) {
|
376
|
-
const parentNode = await figma.getNodeByIdAsync(parentId);
|
377
|
-
if (!parentNode) {
|
378
|
-
throw new Error(`Parent node not found with ID: ${parentId}`);
|
379
|
-
}
|
380
|
-
if (!("appendChild" in parentNode)) {
|
381
|
-
throw new Error(`Parent node does not support children: ${parentId}`);
|
382
|
-
}
|
383
|
-
parentNode.appendChild(frame);
|
384
|
-
} else {
|
385
|
-
figma.currentPage.appendChild(frame);
|
386
|
-
}
|
387
|
-
|
388
|
-
return {
|
389
|
-
id: frame.id,
|
390
|
-
name: frame.name,
|
391
|
-
x: frame.x,
|
392
|
-
y: frame.y,
|
393
|
-
width: frame.width,
|
394
|
-
height: frame.height,
|
395
|
-
fills: frame.fills,
|
396
|
-
strokes: frame.strokes,
|
397
|
-
strokeWeight: frame.strokeWeight,
|
398
|
-
parentId: frame.parent ? frame.parent.id : undefined,
|
399
|
-
};
|
400
|
-
}
|
401
|
-
|
402
|
-
async function createText(params) {
|
403
|
-
const {
|
404
|
-
x = 0,
|
405
|
-
y = 0,
|
406
|
-
text = "Text",
|
407
|
-
fontSize = 14,
|
408
|
-
fontWeight = 400,
|
409
|
-
fontColor = { r: 0, g: 0, b: 0, a: 1 }, // Default to black
|
410
|
-
name = "Text",
|
411
|
-
parentId,
|
412
|
-
} = params || {};
|
413
|
-
|
414
|
-
// Map common font weights to Figma font styles
|
415
|
-
const getFontStyle = (weight) => {
|
416
|
-
switch (weight) {
|
417
|
-
case 100:
|
418
|
-
return "Thin";
|
419
|
-
case 200:
|
420
|
-
return "Extra Light";
|
421
|
-
case 300:
|
422
|
-
return "Light";
|
423
|
-
case 400:
|
424
|
-
return "Regular";
|
425
|
-
case 500:
|
426
|
-
return "Medium";
|
427
|
-
case 600:
|
428
|
-
return "Semi Bold";
|
429
|
-
case 700:
|
430
|
-
return "Bold";
|
431
|
-
case 800:
|
432
|
-
return "Extra Bold";
|
433
|
-
case 900:
|
434
|
-
return "Black";
|
435
|
-
default:
|
436
|
-
return "Regular";
|
437
|
-
}
|
438
|
-
};
|
439
|
-
|
440
|
-
const textNode = figma.createText();
|
441
|
-
textNode.x = x;
|
442
|
-
textNode.y = y;
|
443
|
-
textNode.name = name;
|
444
|
-
try {
|
445
|
-
await figma.loadFontAsync({
|
446
|
-
family: "Inter",
|
447
|
-
style: getFontStyle(fontWeight),
|
448
|
-
});
|
449
|
-
textNode.fontName = { family: "Inter", style: getFontStyle(fontWeight) };
|
450
|
-
textNode.fontSize = parseInt(fontSize);
|
451
|
-
} catch (error) {
|
452
|
-
console.error("Error setting font size", error);
|
453
|
-
}
|
454
|
-
setCharacters(textNode, text);
|
455
|
-
|
456
|
-
// Set text color
|
457
|
-
const paintStyle = {
|
458
|
-
type: "SOLID",
|
459
|
-
color: {
|
460
|
-
r: parseFloat(fontColor.r) || 0,
|
461
|
-
g: parseFloat(fontColor.g) || 0,
|
462
|
-
b: parseFloat(fontColor.b) || 0,
|
463
|
-
},
|
464
|
-
opacity: parseFloat(fontColor.a) || 1,
|
465
|
-
};
|
466
|
-
textNode.fills = [paintStyle];
|
467
|
-
|
468
|
-
// If parentId is provided, append to that node, otherwise append to current page
|
469
|
-
if (parentId) {
|
470
|
-
const parentNode = await figma.getNodeByIdAsync(parentId);
|
471
|
-
if (!parentNode) {
|
472
|
-
throw new Error(`Parent node not found with ID: ${parentId}`);
|
473
|
-
}
|
474
|
-
if (!("appendChild" in parentNode)) {
|
475
|
-
throw new Error(`Parent node does not support children: ${parentId}`);
|
476
|
-
}
|
477
|
-
parentNode.appendChild(textNode);
|
478
|
-
} else {
|
479
|
-
figma.currentPage.appendChild(textNode);
|
480
|
-
}
|
481
|
-
|
482
|
-
return {
|
483
|
-
id: textNode.id,
|
484
|
-
name: textNode.name,
|
485
|
-
x: textNode.x,
|
486
|
-
y: textNode.y,
|
487
|
-
width: textNode.width,
|
488
|
-
height: textNode.height,
|
489
|
-
characters: textNode.characters,
|
490
|
-
fontSize: textNode.fontSize,
|
491
|
-
fontWeight: fontWeight,
|
492
|
-
fontColor: fontColor,
|
493
|
-
fontName: textNode.fontName,
|
494
|
-
fills: textNode.fills,
|
495
|
-
parentId: textNode.parent ? textNode.parent.id : undefined,
|
496
|
-
};
|
497
|
-
}
|
498
|
-
|
499
|
-
async function setFillColor(params) {
|
500
|
-
console.log("setFillColor", params);
|
501
|
-
const {
|
502
|
-
nodeId,
|
503
|
-
color: { r, g, b, a },
|
504
|
-
} = params || {};
|
505
|
-
|
506
|
-
if (!nodeId) {
|
507
|
-
throw new Error("Missing nodeId parameter");
|
508
|
-
}
|
509
|
-
|
510
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
511
|
-
if (!node) {
|
512
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
513
|
-
}
|
514
|
-
|
515
|
-
if (!("fills" in node)) {
|
516
|
-
throw new Error(`Node does not support fills: ${nodeId}`);
|
517
|
-
}
|
518
|
-
|
519
|
-
// Validate that MCP layer provided complete data
|
520
|
-
if (r === undefined || g === undefined || b === undefined || a === undefined) {
|
521
|
-
throw new Error("Incomplete color data received from MCP layer. All RGBA components must be provided.");
|
522
|
-
}
|
523
|
-
|
524
|
-
// Parse values - no defaults, just format conversion
|
525
|
-
const rgbColor = {
|
526
|
-
r: parseFloat(r),
|
527
|
-
g: parseFloat(g),
|
528
|
-
b: parseFloat(b),
|
529
|
-
a: parseFloat(a)
|
530
|
-
};
|
531
|
-
|
532
|
-
// Validate parsing succeeded
|
533
|
-
if (isNaN(rgbColor.r) || isNaN(rgbColor.g) || isNaN(rgbColor.b) || isNaN(rgbColor.a)) {
|
534
|
-
throw new Error("Invalid color values received - all components must be valid numbers");
|
535
|
-
}
|
536
|
-
|
537
|
-
// Set fill - pure translation to Figma API format
|
538
|
-
const paintStyle = {
|
539
|
-
type: "SOLID",
|
540
|
-
color: {
|
541
|
-
r: rgbColor.r,
|
542
|
-
g: rgbColor.g,
|
543
|
-
b: rgbColor.b,
|
544
|
-
},
|
545
|
-
opacity: rgbColor.a,
|
546
|
-
};
|
547
|
-
|
548
|
-
console.log("paintStyle", paintStyle);
|
549
|
-
|
550
|
-
node.fills = [paintStyle];
|
551
|
-
|
552
|
-
return {
|
553
|
-
id: node.id,
|
554
|
-
name: node.name,
|
555
|
-
fills: [paintStyle],
|
556
|
-
};
|
557
|
-
}
|
558
|
-
|
559
|
-
async function setStrokeColor(params) {
|
560
|
-
const {
|
561
|
-
nodeId,
|
562
|
-
color: { r, g, b, a },
|
563
|
-
strokeWeight,
|
564
|
-
} = params || {};
|
565
|
-
|
566
|
-
if (!nodeId) {
|
567
|
-
throw new Error("Missing nodeId parameter");
|
568
|
-
}
|
569
|
-
|
570
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
571
|
-
if (!node) {
|
572
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
573
|
-
}
|
574
|
-
|
575
|
-
if (!("strokes" in node)) {
|
576
|
-
throw new Error(`Node does not support strokes: ${nodeId}`);
|
577
|
-
}
|
578
|
-
|
579
|
-
if (r === undefined || g === undefined || b === undefined || a === undefined) {
|
580
|
-
throw new Error("Incomplete color data received from MCP layer. All RGBA components must be provided.");
|
581
|
-
}
|
582
|
-
|
583
|
-
if (strokeWeight === undefined) {
|
584
|
-
throw new Error("Stroke weight must be provided by MCP layer.");
|
585
|
-
}
|
586
|
-
|
587
|
-
const rgbColor = {
|
588
|
-
r: parseFloat(r),
|
589
|
-
g: parseFloat(g),
|
590
|
-
b: parseFloat(b),
|
591
|
-
a: parseFloat(a)
|
592
|
-
};
|
593
|
-
const strokeWeightParsed = parseFloat(strokeWeight);
|
594
|
-
|
595
|
-
if (isNaN(rgbColor.r) || isNaN(rgbColor.g) || isNaN(rgbColor.b) || isNaN(rgbColor.a)) {
|
596
|
-
throw new Error("Invalid color values received - all components must be valid numbers");
|
597
|
-
}
|
598
|
-
|
599
|
-
if (isNaN(strokeWeightParsed)) {
|
600
|
-
throw new Error("Invalid stroke weight - must be a valid number");
|
601
|
-
}
|
602
|
-
|
603
|
-
const paintStyle = {
|
604
|
-
type: "SOLID",
|
605
|
-
color: {
|
606
|
-
r: rgbColor.r,
|
607
|
-
g: rgbColor.g,
|
608
|
-
b: rgbColor.b,
|
609
|
-
},
|
610
|
-
opacity: rgbColor.a,
|
611
|
-
};
|
612
|
-
|
613
|
-
node.strokes = [paintStyle];
|
614
|
-
|
615
|
-
// Set stroke weight if available
|
616
|
-
if ("strokeWeight" in node) {
|
617
|
-
node.strokeWeight = strokeWeightParsed;
|
618
|
-
}
|
619
|
-
|
620
|
-
return {
|
621
|
-
id: node.id,
|
622
|
-
name: node.name,
|
623
|
-
strokes: node.strokes,
|
624
|
-
strokeWeight: "strokeWeight" in node ? node.strokeWeight : undefined,
|
625
|
-
};
|
626
|
-
}
|
627
|
-
|
628
|
-
async function moveNode(params) {
|
629
|
-
const { nodeId, x, y } = params || {};
|
630
|
-
|
631
|
-
if (!nodeId) {
|
632
|
-
throw new Error("Missing nodeId parameter");
|
633
|
-
}
|
634
|
-
|
635
|
-
if (x === undefined || y === undefined) {
|
636
|
-
throw new Error("Missing x or y parameters");
|
637
|
-
}
|
638
|
-
|
639
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
640
|
-
if (!node) {
|
641
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
642
|
-
}
|
643
|
-
|
644
|
-
if (!("x" in node) || !("y" in node)) {
|
645
|
-
throw new Error(`Node does not support position: ${nodeId}`);
|
646
|
-
}
|
647
|
-
|
648
|
-
node.x = x;
|
649
|
-
node.y = y;
|
650
|
-
|
651
|
-
return {
|
652
|
-
id: node.id,
|
653
|
-
name: node.name,
|
654
|
-
x: node.x,
|
655
|
-
y: node.y,
|
656
|
-
};
|
657
|
-
}
|
658
|
-
|
659
|
-
async function resizeNode(params) {
|
660
|
-
const { nodeId, width, height } = params || {};
|
661
|
-
|
662
|
-
if (!nodeId) {
|
663
|
-
throw new Error("Missing nodeId parameter");
|
664
|
-
}
|
665
|
-
|
666
|
-
if (width === undefined || height === undefined) {
|
667
|
-
throw new Error("Missing width or height parameters");
|
668
|
-
}
|
669
|
-
|
670
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
671
|
-
if (!node) {
|
672
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
673
|
-
}
|
674
|
-
|
675
|
-
if (!("resize" in node)) {
|
676
|
-
throw new Error(`Node does not support resizing: ${nodeId}`);
|
677
|
-
}
|
678
|
-
|
679
|
-
node.resize(width, height);
|
680
|
-
|
681
|
-
return {
|
682
|
-
id: node.id,
|
683
|
-
name: node.name,
|
684
|
-
width: node.width,
|
685
|
-
height: node.height,
|
686
|
-
};
|
687
|
-
}
|
688
|
-
|
689
|
-
async function deleteNode(params) {
|
690
|
-
const { nodeId } = params || {};
|
691
|
-
|
692
|
-
if (!nodeId) {
|
693
|
-
throw new Error("Missing nodeId parameter");
|
694
|
-
}
|
695
|
-
|
696
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
697
|
-
if (!node) {
|
698
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
699
|
-
}
|
700
|
-
|
701
|
-
// Save node info before deleting
|
702
|
-
const nodeInfo = {
|
703
|
-
id: node.id,
|
704
|
-
name: node.name,
|
705
|
-
type: node.type,
|
706
|
-
};
|
707
|
-
|
708
|
-
node.remove();
|
709
|
-
|
710
|
-
return nodeInfo;
|
711
|
-
}
|
712
|
-
|
713
|
-
async function getStyles() {
|
714
|
-
const styles = {
|
715
|
-
colors: await figma.getLocalPaintStylesAsync(),
|
716
|
-
texts: await figma.getLocalTextStylesAsync(),
|
717
|
-
effects: await figma.getLocalEffectStylesAsync(),
|
718
|
-
grids: await figma.getLocalGridStylesAsync(),
|
719
|
-
};
|
720
|
-
|
721
|
-
return {
|
722
|
-
colors: styles.colors.map((style) => ({
|
723
|
-
id: style.id,
|
724
|
-
name: style.name,
|
725
|
-
key: style.key,
|
726
|
-
paint: style.paints[0],
|
727
|
-
})),
|
728
|
-
texts: styles.texts.map((style) => ({
|
729
|
-
id: style.id,
|
730
|
-
name: style.name,
|
731
|
-
key: style.key,
|
732
|
-
fontSize: style.fontSize,
|
733
|
-
fontName: style.fontName,
|
734
|
-
})),
|
735
|
-
effects: styles.effects.map((style) => ({
|
736
|
-
id: style.id,
|
737
|
-
name: style.name,
|
738
|
-
key: style.key,
|
739
|
-
})),
|
740
|
-
grids: styles.grids.map((style) => ({
|
741
|
-
id: style.id,
|
742
|
-
name: style.name,
|
743
|
-
key: style.key,
|
744
|
-
})),
|
745
|
-
};
|
746
|
-
}
|
747
|
-
|
748
|
-
async function getLocalComponents() {
|
749
|
-
await figma.loadAllPagesAsync();
|
750
|
-
|
751
|
-
const components = figma.root.findAllWithCriteria({
|
752
|
-
types: ["COMPONENT"],
|
753
|
-
});
|
754
|
-
|
755
|
-
return {
|
756
|
-
count: components.length,
|
757
|
-
components: components.map((component) => ({
|
758
|
-
id: component.id,
|
759
|
-
name: component.name,
|
760
|
-
key: "key" in component ? component.key : null,
|
761
|
-
})),
|
762
|
-
};
|
763
|
-
}
|
764
|
-
|
765
|
-
// async function getTeamComponents() {
|
766
|
-
// try {
|
767
|
-
// const teamComponents =
|
768
|
-
// await figma.teamLibrary.getAvailableComponentsAsync();
|
769
|
-
|
770
|
-
// return {
|
771
|
-
// count: teamComponents.length,
|
772
|
-
// components: teamComponents.map((component) => ({
|
773
|
-
// key: component.key,
|
774
|
-
// name: component.name,
|
775
|
-
// description: component.description,
|
776
|
-
// libraryName: component.libraryName,
|
777
|
-
// })),
|
778
|
-
// };
|
779
|
-
// } catch (error) {
|
780
|
-
// throw new Error(`Error getting team components: ${error.message}`);
|
781
|
-
// }
|
782
|
-
// }
|
783
|
-
|
784
|
-
async function createComponentInstance(params) {
|
785
|
-
const { componentKey, x = 0, y = 0 } = params || {};
|
786
|
-
|
787
|
-
if (!componentKey) {
|
788
|
-
throw new Error("Missing componentKey parameter");
|
789
|
-
}
|
790
|
-
|
791
|
-
try {
|
792
|
-
// Set up a manual timeout to detect long operations
|
793
|
-
let timeoutId;
|
794
|
-
const timeoutPromise = new Promise((_, reject) => {
|
795
|
-
timeoutId = setTimeout(() => {
|
796
|
-
reject(new Error("Timeout while creating component instance (10s). The component may be too complex or unavailable."));
|
797
|
-
}, 10000); // 10 seconds timeout
|
798
|
-
});
|
799
|
-
|
800
|
-
console.log(`Starting component import for key: ${componentKey}...`);
|
801
|
-
|
802
|
-
// Execute the import with a timeout
|
803
|
-
const importPromise = figma.importComponentByKeyAsync(componentKey);
|
804
|
-
|
805
|
-
// Use Promise.race to implement the timeout
|
806
|
-
const component = await Promise.race([importPromise, timeoutPromise])
|
807
|
-
.finally(() => {
|
808
|
-
clearTimeout(timeoutId); // Clear the timeout to prevent memory leaks
|
809
|
-
});
|
810
|
-
|
811
|
-
// Add progress logging
|
812
|
-
console.log(`Component imported successfully, creating instance...`);
|
813
|
-
|
814
|
-
// Create instance and set properties in a separate try block to handle errors specifically from this step
|
815
|
-
try {
|
816
|
-
const instance = component.createInstance();
|
817
|
-
instance.x = x;
|
818
|
-
instance.y = y;
|
819
|
-
|
820
|
-
figma.currentPage.appendChild(instance);
|
821
|
-
|
822
|
-
console.log(`Component instance created and added to page successfully`);
|
823
|
-
|
824
|
-
return {
|
825
|
-
id: instance.id,
|
826
|
-
name: instance.name,
|
827
|
-
x: instance.x,
|
828
|
-
y: instance.y,
|
829
|
-
width: instance.width,
|
830
|
-
height: instance.height,
|
831
|
-
componentId: instance.componentId,
|
832
|
-
};
|
833
|
-
} catch (instanceError) {
|
834
|
-
console.error(`Error creating component instance: ${instanceError.message}`);
|
835
|
-
throw new Error(`Error creating component instance: ${instanceError.message}`);
|
836
|
-
}
|
837
|
-
} catch (error) {
|
838
|
-
console.error(`Detailed error creating component instance: ${error.message || "Unknown error"}`);
|
839
|
-
console.error(`Stack trace: ${error.stack || "Not available"}`);
|
840
|
-
|
841
|
-
// Provide more helpful error messages for common failure scenarios
|
842
|
-
if (error.message.includes("timeout") || error.message.includes("Timeout")) {
|
843
|
-
throw new Error(`The component import timed out after 10 seconds. This usually happens with complex remote components or network issues. Try again later or use a simpler component.`);
|
844
|
-
} else if (error.message.includes("not found") || error.message.includes("Not found")) {
|
845
|
-
throw new Error(`Component with key "${componentKey}" not found. Make sure the component exists and is accessible in your document or team libraries.`);
|
846
|
-
} else if (error.message.includes("permission") || error.message.includes("Permission")) {
|
847
|
-
throw new Error(`You don't have permission to use this component. Make sure you have access to the team library containing this component.`);
|
848
|
-
} else {
|
849
|
-
throw new Error(`Error creating component instance: ${error.message}`);
|
850
|
-
}
|
851
|
-
}
|
852
|
-
}
|
853
|
-
|
854
|
-
async function exportNodeAsImage(params) {
|
855
|
-
const { nodeId, scale = 1 } = params || {};
|
856
|
-
|
857
|
-
const format = "PNG";
|
858
|
-
|
859
|
-
if (!nodeId) {
|
860
|
-
throw new Error("Missing nodeId parameter");
|
861
|
-
}
|
862
|
-
|
863
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
864
|
-
if (!node) {
|
865
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
866
|
-
}
|
867
|
-
|
868
|
-
if (!("exportAsync" in node)) {
|
869
|
-
throw new Error(`Node does not support exporting: ${nodeId}`);
|
870
|
-
}
|
871
|
-
|
872
|
-
try {
|
873
|
-
const settings = {
|
874
|
-
format: format,
|
875
|
-
constraint: { type: "SCALE", value: scale },
|
876
|
-
};
|
877
|
-
|
878
|
-
const bytes = await node.exportAsync(settings);
|
879
|
-
|
880
|
-
let mimeType;
|
881
|
-
switch (format) {
|
882
|
-
case "PNG":
|
883
|
-
mimeType = "image/png";
|
884
|
-
break;
|
885
|
-
case "JPG":
|
886
|
-
mimeType = "image/jpeg";
|
887
|
-
break;
|
888
|
-
case "SVG":
|
889
|
-
mimeType = "image/svg+xml";
|
890
|
-
break;
|
891
|
-
case "PDF":
|
892
|
-
mimeType = "application/pdf";
|
893
|
-
break;
|
894
|
-
default:
|
895
|
-
mimeType = "application/octet-stream";
|
896
|
-
}
|
897
|
-
|
898
|
-
// Proper way to convert Uint8Array to base64
|
899
|
-
const base64 = customBase64Encode(bytes);
|
900
|
-
// const imageData = `data:${mimeType};base64,${base64}`;
|
901
|
-
|
902
|
-
return {
|
903
|
-
nodeId,
|
904
|
-
format,
|
905
|
-
scale,
|
906
|
-
mimeType,
|
907
|
-
imageData: base64,
|
908
|
-
};
|
909
|
-
} catch (error) {
|
910
|
-
throw new Error(`Error exporting node as image: ${error.message}`);
|
911
|
-
}
|
912
|
-
}
|
913
|
-
function customBase64Encode(bytes) {
|
914
|
-
const chars =
|
915
|
-
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
916
|
-
let base64 = "";
|
917
|
-
|
918
|
-
const byteLength = bytes.byteLength;
|
919
|
-
const byteRemainder = byteLength % 3;
|
920
|
-
const mainLength = byteLength - byteRemainder;
|
921
|
-
|
922
|
-
let a, b, c, d;
|
923
|
-
let chunk;
|
924
|
-
|
925
|
-
// Main loop deals with bytes in chunks of 3
|
926
|
-
for (let i = 0; i < mainLength; i = i + 3) {
|
927
|
-
// Combine the three bytes into a single integer
|
928
|
-
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
|
929
|
-
|
930
|
-
// Use bitmasks to extract 6-bit segments from the triplet
|
931
|
-
a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
|
932
|
-
b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12
|
933
|
-
c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6
|
934
|
-
d = chunk & 63; // 63 = 2^6 - 1
|
935
|
-
|
936
|
-
// Convert the raw binary segments to the appropriate ASCII encoding
|
937
|
-
base64 += chars[a] + chars[b] + chars[c] + chars[d];
|
938
|
-
}
|
939
|
-
|
940
|
-
// Deal with the remaining bytes and padding
|
941
|
-
if (byteRemainder === 1) {
|
942
|
-
chunk = bytes[mainLength];
|
943
|
-
|
944
|
-
a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
|
945
|
-
|
946
|
-
// Set the 4 least significant bits to zero
|
947
|
-
b = (chunk & 3) << 4; // 3 = 2^2 - 1
|
948
|
-
|
949
|
-
base64 += chars[a] + chars[b] + "==";
|
950
|
-
} else if (byteRemainder === 2) {
|
951
|
-
chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
|
952
|
-
|
953
|
-
a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
|
954
|
-
b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4
|
955
|
-
|
956
|
-
// Set the 2 least significant bits to zero
|
957
|
-
c = (chunk & 15) << 2; // 15 = 2^4 - 1
|
958
|
-
|
959
|
-
base64 += chars[a] + chars[b] + chars[c] + "=";
|
960
|
-
}
|
961
|
-
|
962
|
-
return base64;
|
963
|
-
}
|
964
|
-
|
965
|
-
async function setCornerRadius(params) {
|
966
|
-
const { nodeId, radius, corners } = params || {};
|
967
|
-
|
968
|
-
if (!nodeId) {
|
969
|
-
throw new Error("Missing nodeId parameter");
|
970
|
-
}
|
971
|
-
|
972
|
-
if (radius === undefined) {
|
973
|
-
throw new Error("Missing radius parameter");
|
974
|
-
}
|
975
|
-
|
976
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
977
|
-
if (!node) {
|
978
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
979
|
-
}
|
980
|
-
|
981
|
-
// Check if node supports corner radius
|
982
|
-
if (!("cornerRadius" in node)) {
|
983
|
-
throw new Error(`Node does not support corner radius: ${nodeId}`);
|
984
|
-
}
|
985
|
-
|
986
|
-
// If corners array is provided, set individual corner radii
|
987
|
-
if (corners && Array.isArray(corners) && corners.length === 4) {
|
988
|
-
if ("topLeftRadius" in node) {
|
989
|
-
// Node supports individual corner radii
|
990
|
-
if (corners[0]) node.topLeftRadius = radius;
|
991
|
-
if (corners[1]) node.topRightRadius = radius;
|
992
|
-
if (corners[2]) node.bottomRightRadius = radius;
|
993
|
-
if (corners[3]) node.bottomLeftRadius = radius;
|
994
|
-
} else {
|
995
|
-
// Node only supports uniform corner radius
|
996
|
-
node.cornerRadius = radius;
|
997
|
-
}
|
998
|
-
} else {
|
999
|
-
// Set uniform corner radius
|
1000
|
-
node.cornerRadius = radius;
|
1001
|
-
}
|
1002
|
-
|
1003
|
-
return {
|
1004
|
-
id: node.id,
|
1005
|
-
name: node.name,
|
1006
|
-
cornerRadius: "cornerRadius" in node ? node.cornerRadius : undefined,
|
1007
|
-
topLeftRadius: "topLeftRadius" in node ? node.topLeftRadius : undefined,
|
1008
|
-
topRightRadius: "topRightRadius" in node ? node.topRightRadius : undefined,
|
1009
|
-
bottomRightRadius:
|
1010
|
-
"bottomRightRadius" in node ? node.bottomRightRadius : undefined,
|
1011
|
-
bottomLeftRadius:
|
1012
|
-
"bottomLeftRadius" in node ? node.bottomLeftRadius : undefined,
|
1013
|
-
};
|
1014
|
-
}
|
1015
|
-
|
1016
|
-
async function setTextContent(params) {
|
1017
|
-
const { nodeId, text } = params || {};
|
1018
|
-
|
1019
|
-
if (!nodeId) {
|
1020
|
-
throw new Error("Missing nodeId parameter");
|
1021
|
-
}
|
1022
|
-
|
1023
|
-
if (text === undefined) {
|
1024
|
-
throw new Error("Missing text parameter");
|
1025
|
-
}
|
1026
|
-
|
1027
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
1028
|
-
if (!node) {
|
1029
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
1030
|
-
}
|
1031
|
-
|
1032
|
-
if (node.type !== "TEXT") {
|
1033
|
-
throw new Error(`Node is not a text node: ${nodeId}`);
|
1034
|
-
}
|
1035
|
-
|
1036
|
-
try {
|
1037
|
-
await figma.loadFontAsync(node.fontName);
|
1038
|
-
|
1039
|
-
await setCharacters(node, text);
|
1040
|
-
|
1041
|
-
return {
|
1042
|
-
id: node.id,
|
1043
|
-
name: node.name,
|
1044
|
-
characters: node.characters,
|
1045
|
-
fontName: node.fontName,
|
1046
|
-
};
|
1047
|
-
} catch (error) {
|
1048
|
-
throw new Error(`Error setting text content: ${error.message}`);
|
1049
|
-
}
|
1050
|
-
}
|
1051
|
-
|
1052
|
-
// Initialize settings on load
|
1053
|
-
(async function initializePlugin() {
|
1054
|
-
try {
|
1055
|
-
const savedSettings = await figma.clientStorage.getAsync("settings");
|
1056
|
-
if (savedSettings) {
|
1057
|
-
if (savedSettings.serverPort) {
|
1058
|
-
state.serverPort = savedSettings.serverPort;
|
1059
|
-
}
|
1060
|
-
}
|
1061
|
-
|
1062
|
-
// Send initial settings to UI
|
1063
|
-
figma.ui.postMessage({
|
1064
|
-
type: "init-settings",
|
1065
|
-
settings: {
|
1066
|
-
serverPort: state.serverPort,
|
1067
|
-
},
|
1068
|
-
});
|
1069
|
-
} catch (error) {
|
1070
|
-
console.error("Error loading settings:", error);
|
1071
|
-
}
|
1072
|
-
})();
|
1073
|
-
|
1074
|
-
function uniqBy(arr, predicate) {
|
1075
|
-
const cb = typeof predicate === "function" ? predicate : (o) => o[predicate];
|
1076
|
-
return [
|
1077
|
-
...arr
|
1078
|
-
.reduce((map, item) => {
|
1079
|
-
const key = item === null || item === undefined ? item : cb(item);
|
1080
|
-
|
1081
|
-
map.has(key) || map.set(key, item);
|
1082
|
-
|
1083
|
-
return map;
|
1084
|
-
}, new Map())
|
1085
|
-
.values(),
|
1086
|
-
];
|
1087
|
-
}
|
1088
|
-
const setCharacters = async (node, characters, options) => {
|
1089
|
-
const fallbackFont = (options && options.fallbackFont) || {
|
1090
|
-
family: "Inter",
|
1091
|
-
style: "Regular",
|
1092
|
-
};
|
1093
|
-
try {
|
1094
|
-
if (node.fontName === figma.mixed) {
|
1095
|
-
if (options && options.smartStrategy === "prevail") {
|
1096
|
-
const fontHashTree = {};
|
1097
|
-
for (let i = 1; i < node.characters.length; i++) {
|
1098
|
-
const charFont = node.getRangeFontName(i - 1, i);
|
1099
|
-
const key = `${charFont.family}::${charFont.style}`;
|
1100
|
-
fontHashTree[key] = fontHashTree[key] ? fontHashTree[key] + 1 : 1;
|
1101
|
-
}
|
1102
|
-
const prevailedTreeItem = Object.entries(fontHashTree).sort(
|
1103
|
-
(a, b) => b[1] - a[1]
|
1104
|
-
)[0];
|
1105
|
-
const [family, style] = prevailedTreeItem[0].split("::");
|
1106
|
-
const prevailedFont = {
|
1107
|
-
family,
|
1108
|
-
style,
|
1109
|
-
};
|
1110
|
-
await figma.loadFontAsync(prevailedFont);
|
1111
|
-
node.fontName = prevailedFont;
|
1112
|
-
} else if (options && options.smartStrategy === "strict") {
|
1113
|
-
return setCharactersWithStrictMatchFont(node, characters, fallbackFont);
|
1114
|
-
} else if (options && options.smartStrategy === "experimental") {
|
1115
|
-
return setCharactersWithSmartMatchFont(node, characters, fallbackFont);
|
1116
|
-
} else {
|
1117
|
-
const firstCharFont = node.getRangeFontName(0, 1);
|
1118
|
-
await figma.loadFontAsync(firstCharFont);
|
1119
|
-
node.fontName = firstCharFont;
|
1120
|
-
}
|
1121
|
-
} else {
|
1122
|
-
await figma.loadFontAsync({
|
1123
|
-
family: node.fontName.family,
|
1124
|
-
style: node.fontName.style,
|
1125
|
-
});
|
1126
|
-
}
|
1127
|
-
} catch (err) {
|
1128
|
-
console.warn(
|
1129
|
-
`Failed to load "${node.fontName["family"]} ${node.fontName["style"]}" font and replaced with fallback "${fallbackFont.family} ${fallbackFont.style}"`,
|
1130
|
-
err
|
1131
|
-
);
|
1132
|
-
await figma.loadFontAsync(fallbackFont);
|
1133
|
-
node.fontName = fallbackFont;
|
1134
|
-
}
|
1135
|
-
try {
|
1136
|
-
node.characters = characters;
|
1137
|
-
return true;
|
1138
|
-
} catch (err) {
|
1139
|
-
console.warn(`Failed to set characters. Skipped.`, err);
|
1140
|
-
return false;
|
1141
|
-
}
|
1142
|
-
};
|
1143
|
-
|
1144
|
-
const setCharactersWithStrictMatchFont = async (
|
1145
|
-
node,
|
1146
|
-
characters,
|
1147
|
-
fallbackFont
|
1148
|
-
) => {
|
1149
|
-
const fontHashTree = {};
|
1150
|
-
for (let i = 1; i < node.characters.length; i++) {
|
1151
|
-
const startIdx = i - 1;
|
1152
|
-
const startCharFont = node.getRangeFontName(startIdx, i);
|
1153
|
-
const startCharFontVal = `${startCharFont.family}::${startCharFont.style}`;
|
1154
|
-
while (i < node.characters.length) {
|
1155
|
-
i++;
|
1156
|
-
const charFont = node.getRangeFontName(i - 1, i);
|
1157
|
-
if (startCharFontVal !== `${charFont.family}::${charFont.style}`) {
|
1158
|
-
break;
|
1159
|
-
}
|
1160
|
-
}
|
1161
|
-
fontHashTree[`${startIdx}_${i}`] = startCharFontVal;
|
1162
|
-
}
|
1163
|
-
await figma.loadFontAsync(fallbackFont);
|
1164
|
-
node.fontName = fallbackFont;
|
1165
|
-
node.characters = characters;
|
1166
|
-
console.log(fontHashTree);
|
1167
|
-
await Promise.all(
|
1168
|
-
Object.keys(fontHashTree).map(async (range) => {
|
1169
|
-
console.log(range, fontHashTree[range]);
|
1170
|
-
const [start, end] = range.split("_");
|
1171
|
-
const [family, style] = fontHashTree[range].split("::");
|
1172
|
-
const matchedFont = {
|
1173
|
-
family,
|
1174
|
-
style,
|
1175
|
-
};
|
1176
|
-
await figma.loadFontAsync(matchedFont);
|
1177
|
-
return node.setRangeFontName(Number(start), Number(end), matchedFont);
|
1178
|
-
})
|
1179
|
-
);
|
1180
|
-
return true;
|
1181
|
-
};
|
1182
|
-
|
1183
|
-
const getDelimiterPos = (str, delimiter, startIdx = 0, endIdx = str.length) => {
|
1184
|
-
const indices = [];
|
1185
|
-
let temp = startIdx;
|
1186
|
-
for (let i = startIdx; i < endIdx; i++) {
|
1187
|
-
if (
|
1188
|
-
str[i] === delimiter &&
|
1189
|
-
i + startIdx !== endIdx &&
|
1190
|
-
temp !== i + startIdx
|
1191
|
-
) {
|
1192
|
-
indices.push([temp, i + startIdx]);
|
1193
|
-
temp = i + startIdx + 1;
|
1194
|
-
}
|
1195
|
-
}
|
1196
|
-
temp !== endIdx && indices.push([temp, endIdx]);
|
1197
|
-
return indices.filter(Boolean);
|
1198
|
-
};
|
1199
|
-
|
1200
|
-
const buildLinearOrder = (node) => {
|
1201
|
-
const fontTree = [];
|
1202
|
-
const newLinesPos = getDelimiterPos(node.characters, "\n");
|
1203
|
-
newLinesPos.forEach(([newLinesRangeStart, newLinesRangeEnd], n) => {
|
1204
|
-
const newLinesRangeFont = node.getRangeFontName(
|
1205
|
-
newLinesRangeStart,
|
1206
|
-
newLinesRangeEnd
|
1207
|
-
);
|
1208
|
-
if (newLinesRangeFont === figma.mixed) {
|
1209
|
-
const spacesPos = getDelimiterPos(
|
1210
|
-
node.characters,
|
1211
|
-
" ",
|
1212
|
-
newLinesRangeStart,
|
1213
|
-
newLinesRangeEnd
|
1214
|
-
);
|
1215
|
-
spacesPos.forEach(([spacesRangeStart, spacesRangeEnd], s) => {
|
1216
|
-
const spacesRangeFont = node.getRangeFontName(
|
1217
|
-
spacesRangeStart,
|
1218
|
-
spacesRangeEnd
|
1219
|
-
);
|
1220
|
-
if (spacesRangeFont === figma.mixed) {
|
1221
|
-
const spacesRangeFont = node.getRangeFontName(
|
1222
|
-
spacesRangeStart,
|
1223
|
-
spacesRangeStart[0]
|
1224
|
-
);
|
1225
|
-
fontTree.push({
|
1226
|
-
start: spacesRangeStart,
|
1227
|
-
delimiter: " ",
|
1228
|
-
family: spacesRangeFont.family,
|
1229
|
-
style: spacesRangeFont.style,
|
1230
|
-
});
|
1231
|
-
} else {
|
1232
|
-
fontTree.push({
|
1233
|
-
start: spacesRangeStart,
|
1234
|
-
delimiter: " ",
|
1235
|
-
family: spacesRangeFont.family,
|
1236
|
-
style: spacesRangeFont.style,
|
1237
|
-
});
|
1238
|
-
}
|
1239
|
-
});
|
1240
|
-
} else {
|
1241
|
-
fontTree.push({
|
1242
|
-
start: newLinesRangeStart,
|
1243
|
-
delimiter: "\n",
|
1244
|
-
family: newLinesRangeFont.family,
|
1245
|
-
style: newLinesRangeFont.style,
|
1246
|
-
});
|
1247
|
-
}
|
1248
|
-
});
|
1249
|
-
return fontTree
|
1250
|
-
.sort((a, b) => +a.start - +b.start)
|
1251
|
-
.map(({ family, style, delimiter }) => ({ family, style, delimiter }));
|
1252
|
-
};
|
1253
|
-
|
1254
|
-
const setCharactersWithSmartMatchFont = async (
|
1255
|
-
node,
|
1256
|
-
characters,
|
1257
|
-
fallbackFont
|
1258
|
-
) => {
|
1259
|
-
const rangeTree = buildLinearOrder(node);
|
1260
|
-
const fontsToLoad = uniqBy(
|
1261
|
-
rangeTree,
|
1262
|
-
({ family, style }) => `${family}::${style}`
|
1263
|
-
).map(({ family, style }) => ({
|
1264
|
-
family,
|
1265
|
-
style,
|
1266
|
-
}));
|
1267
|
-
|
1268
|
-
await Promise.all([...fontsToLoad, fallbackFont].map(figma.loadFontAsync));
|
1269
|
-
|
1270
|
-
node.fontName = fallbackFont;
|
1271
|
-
node.characters = characters;
|
1272
|
-
|
1273
|
-
let prevPos = 0;
|
1274
|
-
rangeTree.forEach(({ family, style, delimiter }) => {
|
1275
|
-
if (prevPos < node.characters.length) {
|
1276
|
-
const delimeterPos = node.characters.indexOf(delimiter, prevPos);
|
1277
|
-
const endPos =
|
1278
|
-
delimeterPos > prevPos ? delimeterPos : node.characters.length;
|
1279
|
-
const matchedFont = {
|
1280
|
-
family,
|
1281
|
-
style,
|
1282
|
-
};
|
1283
|
-
node.setRangeFontName(prevPos, endPos, matchedFont);
|
1284
|
-
prevPos = endPos + 1;
|
1285
|
-
}
|
1286
|
-
});
|
1287
|
-
return true;
|
1288
|
-
};
|
1289
|
-
|
1290
|
-
// Add the cloneNode function implementation
|
1291
|
-
async function cloneNode(params) {
|
1292
|
-
const { nodeId, x, y } = params || {};
|
1293
|
-
|
1294
|
-
if (!nodeId) {
|
1295
|
-
throw new Error("Missing nodeId parameter");
|
1296
|
-
}
|
1297
|
-
|
1298
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
1299
|
-
if (!node) {
|
1300
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
1301
|
-
}
|
1302
|
-
|
1303
|
-
// Clone the node
|
1304
|
-
const clone = node.clone();
|
1305
|
-
|
1306
|
-
// If x and y are provided, move the clone to that position
|
1307
|
-
if (x !== undefined && y !== undefined) {
|
1308
|
-
if (!("x" in clone) || !("y" in clone)) {
|
1309
|
-
throw new Error(`Cloned node does not support position: ${nodeId}`);
|
1310
|
-
}
|
1311
|
-
clone.x = x;
|
1312
|
-
clone.y = y;
|
1313
|
-
}
|
1314
|
-
|
1315
|
-
// Add the clone to the same parent as the original node
|
1316
|
-
if (node.parent) {
|
1317
|
-
node.parent.appendChild(clone);
|
1318
|
-
} else {
|
1319
|
-
figma.currentPage.appendChild(clone);
|
1320
|
-
}
|
1321
|
-
|
1322
|
-
return {
|
1323
|
-
id: clone.id,
|
1324
|
-
name: clone.name,
|
1325
|
-
x: "x" in clone ? clone.x : undefined,
|
1326
|
-
y: "y" in clone ? clone.y : undefined,
|
1327
|
-
width: "width" in clone ? clone.width : undefined,
|
1328
|
-
height: "height" in clone ? clone.height : undefined,
|
1329
|
-
};
|
1330
|
-
}
|
1331
|
-
|
1332
|
-
async function scanTextNodes(params) {
|
1333
|
-
console.log(`Starting to scan text nodes from node ID: ${params.nodeId}`);
|
1334
|
-
const { nodeId, useChunking = true, chunkSize = 10, commandId = generateCommandId() } = params || {};
|
1335
|
-
|
1336
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
1337
|
-
|
1338
|
-
if (!node) {
|
1339
|
-
console.error(`Node with ID ${nodeId} not found`);
|
1340
|
-
// Send error progress update
|
1341
|
-
sendProgressUpdate(
|
1342
|
-
commandId,
|
1343
|
-
'scan_text_nodes',
|
1344
|
-
'error',
|
1345
|
-
0,
|
1346
|
-
0,
|
1347
|
-
0,
|
1348
|
-
`Node with ID ${nodeId} not found`,
|
1349
|
-
{ error: `Node not found: ${nodeId}` }
|
1350
|
-
);
|
1351
|
-
throw new Error(`Node with ID ${nodeId} not found`);
|
1352
|
-
}
|
1353
|
-
|
1354
|
-
// If chunking is not enabled, use the original implementation
|
1355
|
-
if (!useChunking) {
|
1356
|
-
const textNodes = [];
|
1357
|
-
try {
|
1358
|
-
// Send started progress update
|
1359
|
-
sendProgressUpdate(
|
1360
|
-
commandId,
|
1361
|
-
'scan_text_nodes',
|
1362
|
-
'started',
|
1363
|
-
0,
|
1364
|
-
1, // Not known yet how many nodes there are
|
1365
|
-
0,
|
1366
|
-
`Starting scan of node "${node.name || nodeId}" without chunking`,
|
1367
|
-
null
|
1368
|
-
);
|
1369
|
-
|
1370
|
-
await findTextNodes(node, [], 0, textNodes);
|
1371
|
-
|
1372
|
-
// Send completed progress update
|
1373
|
-
sendProgressUpdate(
|
1374
|
-
commandId,
|
1375
|
-
'scan_text_nodes',
|
1376
|
-
'completed',
|
1377
|
-
100,
|
1378
|
-
textNodes.length,
|
1379
|
-
textNodes.length,
|
1380
|
-
`Scan complete. Found ${textNodes.length} text nodes.`,
|
1381
|
-
{ textNodes }
|
1382
|
-
);
|
1383
|
-
|
1384
|
-
return {
|
1385
|
-
success: true,
|
1386
|
-
message: `Scanned ${textNodes.length} text nodes.`,
|
1387
|
-
count: textNodes.length,
|
1388
|
-
textNodes: textNodes,
|
1389
|
-
commandId
|
1390
|
-
};
|
1391
|
-
} catch (error) {
|
1392
|
-
console.error("Error scanning text nodes:", error);
|
1393
|
-
|
1394
|
-
// Send error progress update
|
1395
|
-
sendProgressUpdate(
|
1396
|
-
commandId,
|
1397
|
-
'scan_text_nodes',
|
1398
|
-
'error',
|
1399
|
-
0,
|
1400
|
-
0,
|
1401
|
-
0,
|
1402
|
-
`Error scanning text nodes: ${error.message}`,
|
1403
|
-
{ error: error.message }
|
1404
|
-
);
|
1405
|
-
|
1406
|
-
throw new Error(`Error scanning text nodes: ${error.message}`);
|
1407
|
-
}
|
1408
|
-
}
|
1409
|
-
|
1410
|
-
// Chunked implementation
|
1411
|
-
console.log(`Using chunked scanning with chunk size: ${chunkSize}`);
|
1412
|
-
|
1413
|
-
// First, collect all nodes to process (without processing them yet)
|
1414
|
-
const nodesToProcess = [];
|
1415
|
-
|
1416
|
-
// Send started progress update
|
1417
|
-
sendProgressUpdate(
|
1418
|
-
commandId,
|
1419
|
-
'scan_text_nodes',
|
1420
|
-
'started',
|
1421
|
-
0,
|
1422
|
-
0, // Not known yet how many nodes there are
|
1423
|
-
0,
|
1424
|
-
`Starting chunked scan of node "${node.name || nodeId}"`,
|
1425
|
-
{ chunkSize }
|
1426
|
-
);
|
1427
|
-
|
1428
|
-
await collectNodesToProcess(node, [], 0, nodesToProcess);
|
1429
|
-
|
1430
|
-
const totalNodes = nodesToProcess.length;
|
1431
|
-
console.log(`Found ${totalNodes} total nodes to process`);
|
1432
|
-
|
1433
|
-
// Calculate number of chunks needed
|
1434
|
-
const totalChunks = Math.ceil(totalNodes / chunkSize);
|
1435
|
-
console.log(`Will process in ${totalChunks} chunks`);
|
1436
|
-
|
1437
|
-
// Send update after node collection
|
1438
|
-
sendProgressUpdate(
|
1439
|
-
commandId,
|
1440
|
-
'scan_text_nodes',
|
1441
|
-
'in_progress',
|
1442
|
-
5, // 5% progress for collection phase
|
1443
|
-
totalNodes,
|
1444
|
-
0,
|
1445
|
-
`Found ${totalNodes} nodes to scan. Will process in ${totalChunks} chunks.`,
|
1446
|
-
{
|
1447
|
-
totalNodes,
|
1448
|
-
totalChunks,
|
1449
|
-
chunkSize
|
1450
|
-
}
|
1451
|
-
);
|
1452
|
-
|
1453
|
-
// Process nodes in chunks
|
1454
|
-
const allTextNodes = [];
|
1455
|
-
let processedNodes = 0;
|
1456
|
-
let chunksProcessed = 0;
|
1457
|
-
|
1458
|
-
for (let i = 0; i < totalNodes; i += chunkSize) {
|
1459
|
-
const chunkEnd = Math.min(i + chunkSize, totalNodes);
|
1460
|
-
console.log(`Processing chunk ${chunksProcessed + 1}/${totalChunks} (nodes ${i} to ${chunkEnd - 1})`);
|
1461
|
-
|
1462
|
-
// Send update before processing chunk
|
1463
|
-
sendProgressUpdate(
|
1464
|
-
commandId,
|
1465
|
-
'scan_text_nodes',
|
1466
|
-
'in_progress',
|
1467
|
-
Math.round(5 + ((chunksProcessed / totalChunks) * 90)), // 5-95% for processing
|
1468
|
-
totalNodes,
|
1469
|
-
processedNodes,
|
1470
|
-
`Processing chunk ${chunksProcessed + 1}/${totalChunks}`,
|
1471
|
-
{
|
1472
|
-
currentChunk: chunksProcessed + 1,
|
1473
|
-
totalChunks,
|
1474
|
-
textNodesFound: allTextNodes.length
|
1475
|
-
}
|
1476
|
-
);
|
1477
|
-
|
1478
|
-
const chunkNodes = nodesToProcess.slice(i, chunkEnd);
|
1479
|
-
const chunkTextNodes = [];
|
1480
|
-
|
1481
|
-
// Process each node in this chunk
|
1482
|
-
for (const nodeInfo of chunkNodes) {
|
1483
|
-
if (nodeInfo.node.type === "TEXT") {
|
1484
|
-
try {
|
1485
|
-
const textNodeInfo = await processTextNode(nodeInfo.node, nodeInfo.parentPath, nodeInfo.depth);
|
1486
|
-
if (textNodeInfo) {
|
1487
|
-
chunkTextNodes.push(textNodeInfo);
|
1488
|
-
}
|
1489
|
-
} catch (error) {
|
1490
|
-
console.error(`Error processing text node: ${error.message}`);
|
1491
|
-
// Continue with other nodes
|
1492
|
-
}
|
1493
|
-
}
|
1494
|
-
|
1495
|
-
// Brief delay to allow UI updates and prevent freezing
|
1496
|
-
await delay(5);
|
1497
|
-
}
|
1498
|
-
|
1499
|
-
// Add results from this chunk
|
1500
|
-
allTextNodes.push(...chunkTextNodes);
|
1501
|
-
processedNodes += chunkNodes.length;
|
1502
|
-
chunksProcessed++;
|
1503
|
-
|
1504
|
-
// Send update after processing chunk
|
1505
|
-
sendProgressUpdate(
|
1506
|
-
commandId,
|
1507
|
-
'scan_text_nodes',
|
1508
|
-
'in_progress',
|
1509
|
-
Math.round(5 + ((chunksProcessed / totalChunks) * 90)), // 5-95% for processing
|
1510
|
-
totalNodes,
|
1511
|
-
processedNodes,
|
1512
|
-
`Processed chunk ${chunksProcessed}/${totalChunks}. Found ${allTextNodes.length} text nodes so far.`,
|
1513
|
-
{
|
1514
|
-
currentChunk: chunksProcessed,
|
1515
|
-
totalChunks,
|
1516
|
-
processedNodes,
|
1517
|
-
textNodesFound: allTextNodes.length,
|
1518
|
-
chunkResult: chunkTextNodes
|
1519
|
-
}
|
1520
|
-
);
|
1521
|
-
|
1522
|
-
// Small delay between chunks to prevent UI freezing
|
1523
|
-
if (i + chunkSize < totalNodes) {
|
1524
|
-
await delay(50);
|
1525
|
-
}
|
1526
|
-
}
|
1527
|
-
|
1528
|
-
// Send completed progress update
|
1529
|
-
sendProgressUpdate(
|
1530
|
-
commandId,
|
1531
|
-
'scan_text_nodes',
|
1532
|
-
'completed',
|
1533
|
-
100,
|
1534
|
-
totalNodes,
|
1535
|
-
processedNodes,
|
1536
|
-
`Scan complete. Found ${allTextNodes.length} text nodes.`,
|
1537
|
-
{
|
1538
|
-
textNodes: allTextNodes,
|
1539
|
-
processedNodes,
|
1540
|
-
chunks: chunksProcessed
|
1541
|
-
}
|
1542
|
-
);
|
1543
|
-
|
1544
|
-
return {
|
1545
|
-
success: true,
|
1546
|
-
message: `Chunked scan complete. Found ${allTextNodes.length} text nodes.`,
|
1547
|
-
totalNodes: allTextNodes.length,
|
1548
|
-
processedNodes: processedNodes,
|
1549
|
-
chunks: chunksProcessed,
|
1550
|
-
textNodes: allTextNodes,
|
1551
|
-
commandId
|
1552
|
-
};
|
1553
|
-
}
|
1554
|
-
|
1555
|
-
// Helper function to collect all nodes that need to be processed
|
1556
|
-
async function collectNodesToProcess(node, parentPath = [], depth = 0, nodesToProcess = []) {
|
1557
|
-
// Skip invisible nodes
|
1558
|
-
if (node.visible === false) return;
|
1559
|
-
|
1560
|
-
// Get the path to this node
|
1561
|
-
const nodePath = [...parentPath, node.name || `Unnamed ${node.type}`];
|
1562
|
-
|
1563
|
-
// Add this node to the processing list
|
1564
|
-
nodesToProcess.push({
|
1565
|
-
node: node,
|
1566
|
-
parentPath: nodePath,
|
1567
|
-
depth: depth
|
1568
|
-
});
|
1569
|
-
|
1570
|
-
// Recursively add children
|
1571
|
-
if ("children" in node) {
|
1572
|
-
for (const child of node.children) {
|
1573
|
-
await collectNodesToProcess(child, nodePath, depth + 1, nodesToProcess);
|
1574
|
-
}
|
1575
|
-
}
|
1576
|
-
}
|
1577
|
-
|
1578
|
-
// Process a single text node
|
1579
|
-
async function processTextNode(node, parentPath, depth) {
|
1580
|
-
if (node.type !== "TEXT") return null;
|
1581
|
-
|
1582
|
-
try {
|
1583
|
-
// Safely extract font information
|
1584
|
-
let fontFamily = "";
|
1585
|
-
let fontStyle = "";
|
1586
|
-
|
1587
|
-
if (node.fontName) {
|
1588
|
-
if (typeof node.fontName === "object") {
|
1589
|
-
if ("family" in node.fontName) fontFamily = node.fontName.family;
|
1590
|
-
if ("style" in node.fontName) fontStyle = node.fontName.style;
|
1591
|
-
}
|
1592
|
-
}
|
1593
|
-
|
1594
|
-
// Create a safe representation of the text node
|
1595
|
-
const safeTextNode = {
|
1596
|
-
id: node.id,
|
1597
|
-
name: node.name || "Text",
|
1598
|
-
type: node.type,
|
1599
|
-
characters: node.characters,
|
1600
|
-
fontSize: typeof node.fontSize === "number" ? node.fontSize : 0,
|
1601
|
-
fontFamily: fontFamily,
|
1602
|
-
fontStyle: fontStyle,
|
1603
|
-
x: typeof node.x === "number" ? node.x : 0,
|
1604
|
-
y: typeof node.y === "number" ? node.y : 0,
|
1605
|
-
width: typeof node.width === "number" ? node.width : 0,
|
1606
|
-
height: typeof node.height === "number" ? node.height : 0,
|
1607
|
-
path: parentPath.join(" > "),
|
1608
|
-
depth: depth,
|
1609
|
-
};
|
1610
|
-
|
1611
|
-
// Highlight the node briefly (optional visual feedback)
|
1612
|
-
try {
|
1613
|
-
const originalFills = JSON.parse(JSON.stringify(node.fills));
|
1614
|
-
node.fills = [
|
1615
|
-
{
|
1616
|
-
type: "SOLID",
|
1617
|
-
color: { r: 1, g: 0.5, b: 0 },
|
1618
|
-
opacity: 0.3,
|
1619
|
-
},
|
1620
|
-
];
|
1621
|
-
|
1622
|
-
// Brief delay for the highlight to be visible
|
1623
|
-
await delay(100);
|
1624
|
-
|
1625
|
-
try {
|
1626
|
-
node.fills = originalFills;
|
1627
|
-
} catch (err) {
|
1628
|
-
console.error("Error resetting fills:", err);
|
1629
|
-
}
|
1630
|
-
} catch (highlightErr) {
|
1631
|
-
console.error("Error highlighting text node:", highlightErr);
|
1632
|
-
// Continue anyway, highlighting is just visual feedback
|
1633
|
-
}
|
1634
|
-
|
1635
|
-
return safeTextNode;
|
1636
|
-
} catch (nodeErr) {
|
1637
|
-
console.error("Error processing text node:", nodeErr);
|
1638
|
-
return null;
|
1639
|
-
}
|
1640
|
-
}
|
1641
|
-
|
1642
|
-
// A delay function that returns a promise
|
1643
|
-
function delay(ms) {
|
1644
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
1645
|
-
}
|
1646
|
-
|
1647
|
-
// Keep the original findTextNodes for backward compatibility
|
1648
|
-
async function findTextNodes(node, parentPath = [], depth = 0, textNodes = []) {
|
1649
|
-
// Skip invisible nodes
|
1650
|
-
if (node.visible === false) return;
|
1651
|
-
|
1652
|
-
// Get the path to this node including its name
|
1653
|
-
const nodePath = [...parentPath, node.name || `Unnamed ${node.type}`];
|
1654
|
-
|
1655
|
-
if (node.type === "TEXT") {
|
1656
|
-
try {
|
1657
|
-
// Safely extract font information to avoid Symbol serialization issues
|
1658
|
-
let fontFamily = "";
|
1659
|
-
let fontStyle = "";
|
1660
|
-
|
1661
|
-
if (node.fontName) {
|
1662
|
-
if (typeof node.fontName === "object") {
|
1663
|
-
if ("family" in node.fontName) fontFamily = node.fontName.family;
|
1664
|
-
if ("style" in node.fontName) fontStyle = node.fontName.style;
|
1665
|
-
}
|
1666
|
-
}
|
1667
|
-
|
1668
|
-
// Create a safe representation of the text node with only serializable properties
|
1669
|
-
const safeTextNode = {
|
1670
|
-
id: node.id,
|
1671
|
-
name: node.name || "Text",
|
1672
|
-
type: node.type,
|
1673
|
-
characters: node.characters,
|
1674
|
-
fontSize: typeof node.fontSize === "number" ? node.fontSize : 0,
|
1675
|
-
fontFamily: fontFamily,
|
1676
|
-
fontStyle: fontStyle,
|
1677
|
-
x: typeof node.x === "number" ? node.x : 0,
|
1678
|
-
y: typeof node.y === "number" ? node.y : 0,
|
1679
|
-
width: typeof node.width === "number" ? node.width : 0,
|
1680
|
-
height: typeof node.height === "number" ? node.height : 0,
|
1681
|
-
path: nodePath.join(" > "),
|
1682
|
-
depth: depth,
|
1683
|
-
};
|
1684
|
-
|
1685
|
-
// Only highlight the node if it's not being done via API
|
1686
|
-
try {
|
1687
|
-
// Safe way to create a temporary highlight without causing serialization issues
|
1688
|
-
const originalFills = JSON.parse(JSON.stringify(node.fills));
|
1689
|
-
node.fills = [
|
1690
|
-
{
|
1691
|
-
type: "SOLID",
|
1692
|
-
color: { r: 1, g: 0.5, b: 0 },
|
1693
|
-
opacity: 0.3,
|
1694
|
-
},
|
1695
|
-
];
|
1696
|
-
|
1697
|
-
// Promise-based delay instead of setTimeout
|
1698
|
-
await delay(500);
|
1699
|
-
|
1700
|
-
try {
|
1701
|
-
node.fills = originalFills;
|
1702
|
-
} catch (err) {
|
1703
|
-
console.error("Error resetting fills:", err);
|
1704
|
-
}
|
1705
|
-
} catch (highlightErr) {
|
1706
|
-
console.error("Error highlighting text node:", highlightErr);
|
1707
|
-
// Continue anyway, highlighting is just visual feedback
|
1708
|
-
}
|
1709
|
-
|
1710
|
-
textNodes.push(safeTextNode);
|
1711
|
-
} catch (nodeErr) {
|
1712
|
-
console.error("Error processing text node:", nodeErr);
|
1713
|
-
// Skip this node but continue with others
|
1714
|
-
}
|
1715
|
-
}
|
1716
|
-
|
1717
|
-
// Recursively process children of container nodes
|
1718
|
-
if ("children" in node) {
|
1719
|
-
for (const child of node.children) {
|
1720
|
-
await findTextNodes(child, nodePath, depth + 1, textNodes);
|
1721
|
-
}
|
1722
|
-
}
|
1723
|
-
}
|
1724
|
-
|
1725
|
-
// Replace text in a specific node
|
1726
|
-
async function setMultipleTextContents(params) {
|
1727
|
-
const { nodeId, text } = params || {};
|
1728
|
-
const commandId = params.commandId || generateCommandId();
|
1729
|
-
|
1730
|
-
if (!nodeId || !text || !Array.isArray(text)) {
|
1731
|
-
const errorMsg = "Missing required parameters: nodeId and text array";
|
1732
|
-
|
1733
|
-
// Send error progress update
|
1734
|
-
sendProgressUpdate(
|
1735
|
-
commandId,
|
1736
|
-
'set_multiple_text_contents',
|
1737
|
-
'error',
|
1738
|
-
0,
|
1739
|
-
0,
|
1740
|
-
0,
|
1741
|
-
errorMsg,
|
1742
|
-
{ error: errorMsg }
|
1743
|
-
);
|
1744
|
-
|
1745
|
-
throw new Error(errorMsg);
|
1746
|
-
}
|
1747
|
-
|
1748
|
-
console.log(
|
1749
|
-
`Starting text replacement for node: ${nodeId} with ${text.length} text replacements`
|
1750
|
-
);
|
1751
|
-
|
1752
|
-
// Send started progress update
|
1753
|
-
sendProgressUpdate(
|
1754
|
-
commandId,
|
1755
|
-
'set_multiple_text_contents',
|
1756
|
-
'started',
|
1757
|
-
0,
|
1758
|
-
text.length,
|
1759
|
-
0,
|
1760
|
-
`Starting text replacement for ${text.length} nodes`,
|
1761
|
-
{ totalReplacements: text.length }
|
1762
|
-
);
|
1763
|
-
|
1764
|
-
// Define the results array and counters
|
1765
|
-
const results = [];
|
1766
|
-
let successCount = 0;
|
1767
|
-
let failureCount = 0;
|
1768
|
-
|
1769
|
-
// Split text replacements into chunks of 5
|
1770
|
-
const CHUNK_SIZE = 5;
|
1771
|
-
const chunks = [];
|
1772
|
-
|
1773
|
-
for (let i = 0; i < text.length; i += CHUNK_SIZE) {
|
1774
|
-
chunks.push(text.slice(i, i + CHUNK_SIZE));
|
1775
|
-
}
|
1776
|
-
|
1777
|
-
console.log(`Split ${text.length} replacements into ${chunks.length} chunks`);
|
1778
|
-
|
1779
|
-
// Send chunking info update
|
1780
|
-
sendProgressUpdate(
|
1781
|
-
commandId,
|
1782
|
-
'set_multiple_text_contents',
|
1783
|
-
'in_progress',
|
1784
|
-
5, // 5% progress for planning phase
|
1785
|
-
text.length,
|
1786
|
-
0,
|
1787
|
-
`Preparing to replace text in ${text.length} nodes using ${chunks.length} chunks`,
|
1788
|
-
{
|
1789
|
-
totalReplacements: text.length,
|
1790
|
-
chunks: chunks.length,
|
1791
|
-
chunkSize: CHUNK_SIZE
|
1792
|
-
}
|
1793
|
-
);
|
1794
|
-
|
1795
|
-
// Process each chunk sequentially
|
1796
|
-
for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
|
1797
|
-
const chunk = chunks[chunkIndex];
|
1798
|
-
console.log(`Processing chunk ${chunkIndex + 1}/${chunks.length} with ${chunk.length} replacements`);
|
1799
|
-
|
1800
|
-
// Send chunk processing start update
|
1801
|
-
sendProgressUpdate(
|
1802
|
-
commandId,
|
1803
|
-
'set_multiple_text_contents',
|
1804
|
-
'in_progress',
|
1805
|
-
Math.round(5 + ((chunkIndex / chunks.length) * 90)), // 5-95% for processing
|
1806
|
-
text.length,
|
1807
|
-
successCount + failureCount,
|
1808
|
-
`Processing text replacements chunk ${chunkIndex + 1}/${chunks.length}`,
|
1809
|
-
{
|
1810
|
-
currentChunk: chunkIndex + 1,
|
1811
|
-
totalChunks: chunks.length,
|
1812
|
-
successCount,
|
1813
|
-
failureCount
|
1814
|
-
}
|
1815
|
-
);
|
1816
|
-
|
1817
|
-
// Process replacements within a chunk in parallel
|
1818
|
-
const chunkPromises = chunk.map(async (replacement) => {
|
1819
|
-
if (!replacement.nodeId || replacement.text === undefined) {
|
1820
|
-
console.error(`Missing nodeId or text for replacement`);
|
1821
|
-
return {
|
1822
|
-
success: false,
|
1823
|
-
nodeId: replacement.nodeId || "unknown",
|
1824
|
-
error: "Missing nodeId or text in replacement entry"
|
1825
|
-
};
|
1826
|
-
}
|
1827
|
-
|
1828
|
-
try {
|
1829
|
-
console.log(`Attempting to replace text in node: ${replacement.nodeId}`);
|
1830
|
-
|
1831
|
-
// Get the text node to update (just to check it exists and get original text)
|
1832
|
-
const textNode = await figma.getNodeByIdAsync(replacement.nodeId);
|
1833
|
-
|
1834
|
-
if (!textNode) {
|
1835
|
-
console.error(`Text node not found: ${replacement.nodeId}`);
|
1836
|
-
return {
|
1837
|
-
success: false,
|
1838
|
-
nodeId: replacement.nodeId,
|
1839
|
-
error: `Node not found: ${replacement.nodeId}`
|
1840
|
-
};
|
1841
|
-
}
|
1842
|
-
|
1843
|
-
if (textNode.type !== "TEXT") {
|
1844
|
-
console.error(`Node is not a text node: ${replacement.nodeId} (type: ${textNode.type})`);
|
1845
|
-
return {
|
1846
|
-
success: false,
|
1847
|
-
nodeId: replacement.nodeId,
|
1848
|
-
error: `Node is not a text node: ${replacement.nodeId} (type: ${textNode.type})`
|
1849
|
-
};
|
1850
|
-
}
|
1851
|
-
|
1852
|
-
// Save original text for the result
|
1853
|
-
const originalText = textNode.characters;
|
1854
|
-
console.log(`Original text: "${originalText}"`);
|
1855
|
-
console.log(`Will translate to: "${replacement.text}"`);
|
1856
|
-
|
1857
|
-
// Highlight the node before changing text
|
1858
|
-
let originalFills;
|
1859
|
-
try {
|
1860
|
-
// Save original fills for restoration later
|
1861
|
-
originalFills = JSON.parse(JSON.stringify(textNode.fills));
|
1862
|
-
// Apply highlight color (orange with 30% opacity)
|
1863
|
-
textNode.fills = [
|
1864
|
-
{
|
1865
|
-
type: "SOLID",
|
1866
|
-
color: { r: 1, g: 0.5, b: 0 },
|
1867
|
-
opacity: 0.3,
|
1868
|
-
},
|
1869
|
-
];
|
1870
|
-
} catch (highlightErr) {
|
1871
|
-
console.error(`Error highlighting text node: ${highlightErr.message}`);
|
1872
|
-
// Continue anyway, highlighting is just visual feedback
|
1873
|
-
}
|
1874
|
-
|
1875
|
-
// Use the existing setTextContent function to handle font loading and text setting
|
1876
|
-
await setTextContent({
|
1877
|
-
nodeId: replacement.nodeId,
|
1878
|
-
text: replacement.text
|
1879
|
-
});
|
1880
|
-
|
1881
|
-
// Keep highlight for a moment after text change, then restore original fills
|
1882
|
-
if (originalFills) {
|
1883
|
-
try {
|
1884
|
-
// Use delay function for consistent timing
|
1885
|
-
await delay(500);
|
1886
|
-
textNode.fills = originalFills;
|
1887
|
-
} catch (restoreErr) {
|
1888
|
-
console.error(`Error restoring fills: ${restoreErr.message}`);
|
1889
|
-
}
|
1890
|
-
}
|
1891
|
-
|
1892
|
-
console.log(`Successfully replaced text in node: ${replacement.nodeId}`);
|
1893
|
-
return {
|
1894
|
-
success: true,
|
1895
|
-
nodeId: replacement.nodeId,
|
1896
|
-
originalText: originalText,
|
1897
|
-
translatedText: replacement.text
|
1898
|
-
};
|
1899
|
-
} catch (error) {
|
1900
|
-
console.error(`Error replacing text in node ${replacement.nodeId}: ${error.message}`);
|
1901
|
-
return {
|
1902
|
-
success: false,
|
1903
|
-
nodeId: replacement.nodeId,
|
1904
|
-
error: `Error applying replacement: ${error.message}`
|
1905
|
-
};
|
1906
|
-
}
|
1907
|
-
});
|
1908
|
-
|
1909
|
-
// Wait for all replacements in this chunk to complete
|
1910
|
-
const chunkResults = await Promise.all(chunkPromises);
|
1911
|
-
|
1912
|
-
// Process results for this chunk
|
1913
|
-
chunkResults.forEach(result => {
|
1914
|
-
if (result.success) {
|
1915
|
-
successCount++;
|
1916
|
-
} else {
|
1917
|
-
failureCount++;
|
1918
|
-
}
|
1919
|
-
results.push(result);
|
1920
|
-
});
|
1921
|
-
|
1922
|
-
// Send chunk processing complete update with partial results
|
1923
|
-
sendProgressUpdate(
|
1924
|
-
commandId,
|
1925
|
-
'set_multiple_text_contents',
|
1926
|
-
'in_progress',
|
1927
|
-
Math.round(5 + (((chunkIndex + 1) / chunks.length) * 90)), // 5-95% for processing
|
1928
|
-
text.length,
|
1929
|
-
successCount + failureCount,
|
1930
|
-
`Completed chunk ${chunkIndex + 1}/${chunks.length}. ${successCount} successful, ${failureCount} failed so far.`,
|
1931
|
-
{
|
1932
|
-
currentChunk: chunkIndex + 1,
|
1933
|
-
totalChunks: chunks.length,
|
1934
|
-
successCount,
|
1935
|
-
failureCount,
|
1936
|
-
chunkResults: chunkResults
|
1937
|
-
}
|
1938
|
-
);
|
1939
|
-
|
1940
|
-
// Add a small delay between chunks to avoid overloading Figma
|
1941
|
-
if (chunkIndex < chunks.length - 1) {
|
1942
|
-
console.log('Pausing between chunks to avoid overloading Figma...');
|
1943
|
-
await delay(1000); // 1 second delay between chunks
|
1944
|
-
}
|
1945
|
-
}
|
1946
|
-
|
1947
|
-
console.log(
|
1948
|
-
`Replacement complete: ${successCount} successful, ${failureCount} failed`
|
1949
|
-
);
|
1950
|
-
|
1951
|
-
// Send completed progress update
|
1952
|
-
sendProgressUpdate(
|
1953
|
-
commandId,
|
1954
|
-
'set_multiple_text_contents',
|
1955
|
-
'completed',
|
1956
|
-
100,
|
1957
|
-
text.length,
|
1958
|
-
successCount + failureCount,
|
1959
|
-
`Text replacement complete: ${successCount} successful, ${failureCount} failed`,
|
1960
|
-
{
|
1961
|
-
totalReplacements: text.length,
|
1962
|
-
replacementsApplied: successCount,
|
1963
|
-
replacementsFailed: failureCount,
|
1964
|
-
completedInChunks: chunks.length,
|
1965
|
-
results: results
|
1966
|
-
}
|
1967
|
-
);
|
1968
|
-
|
1969
|
-
return {
|
1970
|
-
success: successCount > 0,
|
1971
|
-
nodeId: nodeId,
|
1972
|
-
replacementsApplied: successCount,
|
1973
|
-
replacementsFailed: failureCount,
|
1974
|
-
totalReplacements: text.length,
|
1975
|
-
results: results,
|
1976
|
-
completedInChunks: chunks.length,
|
1977
|
-
commandId
|
1978
|
-
};
|
1979
|
-
}
|
1980
|
-
|
1981
|
-
// Function to generate simple UUIDs for command IDs
|
1982
|
-
function generateCommandId() {
|
1983
|
-
return 'cmd_' + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
1984
|
-
}
|
1985
|
-
|
1986
|
-
async function setAutoLayout(params) {
|
1987
|
-
const {
|
1988
|
-
nodeId,
|
1989
|
-
layoutMode,
|
1990
|
-
paddingTop,
|
1991
|
-
paddingBottom,
|
1992
|
-
paddingLeft,
|
1993
|
-
paddingRight,
|
1994
|
-
itemSpacing,
|
1995
|
-
primaryAxisAlignItems,
|
1996
|
-
counterAxisAlignItems,
|
1997
|
-
layoutWrap,
|
1998
|
-
strokesIncludedInLayout
|
1999
|
-
} = params || {};
|
2000
|
-
|
2001
|
-
if (!nodeId) {
|
2002
|
-
throw new Error("Missing nodeId parameter");
|
2003
|
-
}
|
2004
|
-
|
2005
|
-
if (!layoutMode) {
|
2006
|
-
throw new Error("Missing layoutMode parameter");
|
2007
|
-
}
|
2008
|
-
|
2009
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
2010
|
-
if (!node) {
|
2011
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
2012
|
-
}
|
2013
|
-
|
2014
|
-
// Check if the node is a frame or group
|
2015
|
-
if (!("layoutMode" in node)) {
|
2016
|
-
throw new Error(`Node does not support auto layout: ${nodeId}`);
|
2017
|
-
}
|
2018
|
-
|
2019
|
-
// Configure layout mode
|
2020
|
-
if (layoutMode === "NONE") {
|
2021
|
-
node.layoutMode = "NONE";
|
2022
|
-
} else {
|
2023
|
-
// Set auto layout properties
|
2024
|
-
node.layoutMode = layoutMode;
|
2025
|
-
|
2026
|
-
// Configure padding if provided
|
2027
|
-
if (paddingTop !== undefined) node.paddingTop = paddingTop;
|
2028
|
-
if (paddingBottom !== undefined) node.paddingBottom = paddingBottom;
|
2029
|
-
if (paddingLeft !== undefined) node.paddingLeft = paddingLeft;
|
2030
|
-
if (paddingRight !== undefined) node.paddingRight = paddingRight;
|
2031
|
-
|
2032
|
-
// Configure item spacing
|
2033
|
-
if (itemSpacing !== undefined) node.itemSpacing = itemSpacing;
|
2034
|
-
|
2035
|
-
// Configure alignment
|
2036
|
-
if (primaryAxisAlignItems !== undefined) {
|
2037
|
-
node.primaryAxisAlignItems = primaryAxisAlignItems;
|
2038
|
-
}
|
2039
|
-
|
2040
|
-
if (counterAxisAlignItems !== undefined) {
|
2041
|
-
node.counterAxisAlignItems = counterAxisAlignItems;
|
2042
|
-
}
|
2043
|
-
|
2044
|
-
// Configure wrap
|
2045
|
-
if (layoutWrap !== undefined) {
|
2046
|
-
node.layoutWrap = layoutWrap;
|
2047
|
-
}
|
2048
|
-
|
2049
|
-
// Configure stroke inclusion
|
2050
|
-
if (strokesIncludedInLayout !== undefined) {
|
2051
|
-
node.strokesIncludedInLayout = strokesIncludedInLayout;
|
2052
|
-
}
|
2053
|
-
}
|
2054
|
-
|
2055
|
-
return {
|
2056
|
-
id: node.id,
|
2057
|
-
name: node.name,
|
2058
|
-
layoutMode: node.layoutMode,
|
2059
|
-
paddingTop: node.paddingTop,
|
2060
|
-
paddingBottom: node.paddingBottom,
|
2061
|
-
paddingLeft: node.paddingLeft,
|
2062
|
-
paddingRight: node.paddingRight,
|
2063
|
-
itemSpacing: node.itemSpacing,
|
2064
|
-
primaryAxisAlignItems: node.primaryAxisAlignItems,
|
2065
|
-
counterAxisAlignItems: node.counterAxisAlignItems,
|
2066
|
-
layoutWrap: node.layoutWrap,
|
2067
|
-
strokesIncludedInLayout: node.strokesIncludedInLayout
|
2068
|
-
};
|
2069
|
-
}
|
2070
|
-
|
2071
|
-
// Nuevas funciones para propiedades de texto
|
2072
|
-
|
2073
|
-
async function setFontName(params) {
|
2074
|
-
const { nodeId, family, style } = params || {};
|
2075
|
-
if (!nodeId || !family) {
|
2076
|
-
throw new Error("Missing nodeId or font family");
|
2077
|
-
}
|
2078
|
-
|
2079
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
2080
|
-
if (!node) {
|
2081
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
2082
|
-
}
|
2083
|
-
|
2084
|
-
if (node.type !== "TEXT") {
|
2085
|
-
throw new Error(`Node is not a text node: ${nodeId}`);
|
2086
|
-
}
|
2087
|
-
|
2088
|
-
try {
|
2089
|
-
await figma.loadFontAsync({ family, style: style || "Regular" });
|
2090
|
-
node.fontName = { family, style: style || "Regular" };
|
2091
|
-
return {
|
2092
|
-
id: node.id,
|
2093
|
-
name: node.name,
|
2094
|
-
fontName: node.fontName
|
2095
|
-
};
|
2096
|
-
} catch (error) {
|
2097
|
-
throw new Error(`Error setting font name: ${error.message}`);
|
2098
|
-
}
|
2099
|
-
}
|
2100
|
-
|
2101
|
-
async function setFontSize(params) {
|
2102
|
-
const { nodeId, fontSize } = params || {};
|
2103
|
-
if (!nodeId || fontSize === undefined) {
|
2104
|
-
throw new Error("Missing nodeId or fontSize");
|
2105
|
-
}
|
2106
|
-
|
2107
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
2108
|
-
if (!node) {
|
2109
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
2110
|
-
}
|
2111
|
-
|
2112
|
-
if (node.type !== "TEXT") {
|
2113
|
-
throw new Error(`Node is not a text node: ${nodeId}`);
|
2114
|
-
}
|
2115
|
-
|
2116
|
-
try {
|
2117
|
-
await figma.loadFontAsync(node.fontName);
|
2118
|
-
node.fontSize = fontSize;
|
2119
|
-
return {
|
2120
|
-
id: node.id,
|
2121
|
-
name: node.name,
|
2122
|
-
fontSize: node.fontSize
|
2123
|
-
};
|
2124
|
-
} catch (error) {
|
2125
|
-
throw new Error(`Error setting font size: ${error.message}`);
|
2126
|
-
}
|
2127
|
-
}
|
2128
|
-
|
2129
|
-
async function setFontWeight(params) {
|
2130
|
-
const { nodeId, weight } = params || {};
|
2131
|
-
if (!nodeId || weight === undefined) {
|
2132
|
-
throw new Error("Missing nodeId or weight");
|
2133
|
-
}
|
2134
|
-
|
2135
|
-
// Map weight to font style
|
2136
|
-
const getFontStyle = (weight) => {
|
2137
|
-
switch (weight) {
|
2138
|
-
case 100: return "Thin";
|
2139
|
-
case 200: return "Extra Light";
|
2140
|
-
case 300: return "Light";
|
2141
|
-
case 400: return "Regular";
|
2142
|
-
case 500: return "Medium";
|
2143
|
-
case 600: return "Semi Bold";
|
2144
|
-
case 700: return "Bold";
|
2145
|
-
case 800: return "Extra Bold";
|
2146
|
-
case 900: return "Black";
|
2147
|
-
default: return "Regular";
|
2148
|
-
}
|
2149
|
-
};
|
2150
|
-
|
2151
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
2152
|
-
if (!node) {
|
2153
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
2154
|
-
}
|
2155
|
-
|
2156
|
-
if (node.type !== "TEXT") {
|
2157
|
-
throw new Error(`Node is not a text node: ${nodeId}`);
|
2158
|
-
}
|
2159
|
-
|
2160
|
-
try {
|
2161
|
-
const family = node.fontName.family;
|
2162
|
-
const style = getFontStyle(weight);
|
2163
|
-
await figma.loadFontAsync({ family, style });
|
2164
|
-
node.fontName = { family, style };
|
2165
|
-
return {
|
2166
|
-
id: node.id,
|
2167
|
-
name: node.name,
|
2168
|
-
fontName: node.fontName,
|
2169
|
-
weight: weight
|
2170
|
-
};
|
2171
|
-
} catch (error) {
|
2172
|
-
throw new Error(`Error setting font weight: ${error.message}`);
|
2173
|
-
}
|
2174
|
-
}
|
2175
|
-
|
2176
|
-
async function setLetterSpacing(params) {
|
2177
|
-
const { nodeId, letterSpacing, unit = "PIXELS" } = params || {};
|
2178
|
-
if (!nodeId || letterSpacing === undefined) {
|
2179
|
-
throw new Error("Missing nodeId or letterSpacing");
|
2180
|
-
}
|
2181
|
-
|
2182
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
2183
|
-
if (!node) {
|
2184
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
2185
|
-
}
|
2186
|
-
|
2187
|
-
if (node.type !== "TEXT") {
|
2188
|
-
throw new Error(`Node is not a text node: ${nodeId}`);
|
2189
|
-
}
|
2190
|
-
|
2191
|
-
try {
|
2192
|
-
await figma.loadFontAsync(node.fontName);
|
2193
|
-
node.letterSpacing = { value: letterSpacing, unit };
|
2194
|
-
return {
|
2195
|
-
id: node.id,
|
2196
|
-
name: node.name,
|
2197
|
-
letterSpacing: node.letterSpacing
|
2198
|
-
};
|
2199
|
-
} catch (error) {
|
2200
|
-
throw new Error(`Error setting letter spacing: ${error.message}`);
|
2201
|
-
}
|
2202
|
-
}
|
2203
|
-
|
2204
|
-
async function setLineHeight(params) {
|
2205
|
-
const { nodeId, lineHeight, unit = "PIXELS" } = params || {};
|
2206
|
-
if (!nodeId || lineHeight === undefined) {
|
2207
|
-
throw new Error("Missing nodeId or lineHeight");
|
2208
|
-
}
|
2209
|
-
|
2210
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
2211
|
-
if (!node) {
|
2212
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
2213
|
-
}
|
2214
|
-
|
2215
|
-
if (node.type !== "TEXT") {
|
2216
|
-
throw new Error(`Node is not a text node: ${nodeId}`);
|
2217
|
-
}
|
2218
|
-
|
2219
|
-
try {
|
2220
|
-
await figma.loadFontAsync(node.fontName);
|
2221
|
-
node.lineHeight = { value: lineHeight, unit };
|
2222
|
-
return {
|
2223
|
-
id: node.id,
|
2224
|
-
name: node.name,
|
2225
|
-
lineHeight: node.lineHeight
|
2226
|
-
};
|
2227
|
-
} catch (error) {
|
2228
|
-
throw new Error(`Error setting line height: ${error.message}`);
|
2229
|
-
}
|
2230
|
-
}
|
2231
|
-
|
2232
|
-
async function setParagraphSpacing(params) {
|
2233
|
-
const { nodeId, paragraphSpacing } = params || {};
|
2234
|
-
if (!nodeId || paragraphSpacing === undefined) {
|
2235
|
-
throw new Error("Missing nodeId or paragraphSpacing");
|
2236
|
-
}
|
2237
|
-
|
2238
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
2239
|
-
if (!node) {
|
2240
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
2241
|
-
}
|
2242
|
-
|
2243
|
-
if (node.type !== "TEXT") {
|
2244
|
-
throw new Error(`Node is not a text node: ${nodeId}`);
|
2245
|
-
}
|
2246
|
-
|
2247
|
-
try {
|
2248
|
-
await figma.loadFontAsync(node.fontName);
|
2249
|
-
node.paragraphSpacing = paragraphSpacing;
|
2250
|
-
return {
|
2251
|
-
id: node.id,
|
2252
|
-
name: node.name,
|
2253
|
-
paragraphSpacing: node.paragraphSpacing
|
2254
|
-
};
|
2255
|
-
} catch (error) {
|
2256
|
-
throw new Error(`Error setting paragraph spacing: ${error.message}`);
|
2257
|
-
}
|
2258
|
-
}
|
2259
|
-
|
2260
|
-
async function setTextCase(params) {
|
2261
|
-
const { nodeId, textCase } = params || {};
|
2262
|
-
if (!nodeId || textCase === undefined) {
|
2263
|
-
throw new Error("Missing nodeId or textCase");
|
2264
|
-
}
|
2265
|
-
|
2266
|
-
// Valid textCase values: "ORIGINAL", "UPPER", "LOWER", "TITLE"
|
2267
|
-
if (!["ORIGINAL", "UPPER", "LOWER", "TITLE"].includes(textCase)) {
|
2268
|
-
throw new Error("Invalid textCase value. Must be one of: ORIGINAL, UPPER, LOWER, TITLE");
|
2269
|
-
}
|
2270
|
-
|
2271
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
2272
|
-
if (!node) {
|
2273
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
2274
|
-
}
|
2275
|
-
|
2276
|
-
if (node.type !== "TEXT") {
|
2277
|
-
throw new Error(`Node is not a text node: ${nodeId}`);
|
2278
|
-
}
|
2279
|
-
|
2280
|
-
try {
|
2281
|
-
await figma.loadFontAsync(node.fontName);
|
2282
|
-
node.textCase = textCase;
|
2283
|
-
return {
|
2284
|
-
id: node.id,
|
2285
|
-
name: node.name,
|
2286
|
-
textCase: node.textCase
|
2287
|
-
};
|
2288
|
-
} catch (error) {
|
2289
|
-
throw new Error(`Error setting text case: ${error.message}`);
|
2290
|
-
}
|
2291
|
-
}
|
2292
|
-
|
2293
|
-
async function setTextDecoration(params) {
|
2294
|
-
const { nodeId, textDecoration } = params || {};
|
2295
|
-
if (!nodeId || textDecoration === undefined) {
|
2296
|
-
throw new Error("Missing nodeId or textDecoration");
|
2297
|
-
}
|
2298
|
-
|
2299
|
-
// Valid textDecoration values: "NONE", "UNDERLINE", "STRIKETHROUGH"
|
2300
|
-
if (!["NONE", "UNDERLINE", "STRIKETHROUGH"].includes(textDecoration)) {
|
2301
|
-
throw new Error("Invalid textDecoration value. Must be one of: NONE, UNDERLINE, STRIKETHROUGH");
|
2302
|
-
}
|
2303
|
-
|
2304
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
2305
|
-
if (!node) {
|
2306
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
2307
|
-
}
|
2308
|
-
|
2309
|
-
if (node.type !== "TEXT") {
|
2310
|
-
throw new Error(`Node is not a text node: ${nodeId}`);
|
2311
|
-
}
|
2312
|
-
|
2313
|
-
try {
|
2314
|
-
await figma.loadFontAsync(node.fontName);
|
2315
|
-
node.textDecoration = textDecoration;
|
2316
|
-
return {
|
2317
|
-
id: node.id,
|
2318
|
-
name: node.name,
|
2319
|
-
textDecoration: node.textDecoration
|
2320
|
-
};
|
2321
|
-
} catch (error) {
|
2322
|
-
throw new Error(`Error setting text decoration: ${error.message}`);
|
2323
|
-
}
|
2324
|
-
}
|
2325
|
-
|
2326
|
-
async function getStyledTextSegments(params) {
|
2327
|
-
const { nodeId, property } = params || {};
|
2328
|
-
if (!nodeId || !property) {
|
2329
|
-
throw new Error("Missing nodeId or property");
|
2330
|
-
}
|
2331
|
-
|
2332
|
-
// Valid properties: "fillStyleId", "fontName", "fontSize", "textCase",
|
2333
|
-
// "textDecoration", "textStyleId", "fills", "letterSpacing", "lineHeight", "fontWeight"
|
2334
|
-
const validProperties = [
|
2335
|
-
"fillStyleId", "fontName", "fontSize", "textCase",
|
2336
|
-
"textDecoration", "textStyleId", "fills", "letterSpacing",
|
2337
|
-
"lineHeight", "fontWeight"
|
2338
|
-
];
|
2339
|
-
|
2340
|
-
if (!validProperties.includes(property)) {
|
2341
|
-
throw new Error(`Invalid property. Must be one of: ${validProperties.join(", ")}`);
|
2342
|
-
}
|
2343
|
-
|
2344
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
2345
|
-
if (!node) {
|
2346
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
2347
|
-
}
|
2348
|
-
|
2349
|
-
if (node.type !== "TEXT") {
|
2350
|
-
throw new Error(`Node is not a text node: ${nodeId}`);
|
2351
|
-
}
|
2352
|
-
|
2353
|
-
try {
|
2354
|
-
const segments = node.getStyledTextSegments([property]);
|
2355
|
-
|
2356
|
-
// Prepare segments data in a format safe for serialization
|
2357
|
-
const safeSegments = segments.map(segment => {
|
2358
|
-
const safeSegment = {
|
2359
|
-
characters: segment.characters,
|
2360
|
-
start: segment.start,
|
2361
|
-
end: segment.end
|
2362
|
-
};
|
2363
|
-
|
2364
|
-
// Handle different property types for safe serialization
|
2365
|
-
if (property === "fontName") {
|
2366
|
-
if (segment[property] && typeof segment[property] === "object") {
|
2367
|
-
safeSegment[property] = {
|
2368
|
-
family: segment[property].family || "",
|
2369
|
-
style: segment[property].style || ""
|
2370
|
-
};
|
2371
|
-
} else {
|
2372
|
-
safeSegment[property] = { family: "", style: "" };
|
2373
|
-
}
|
2374
|
-
} else if (property === "letterSpacing" || property === "lineHeight") {
|
2375
|
-
// Handle spacing properties which have a value and unit
|
2376
|
-
if (segment[property] && typeof segment[property] === "object") {
|
2377
|
-
safeSegment[property] = {
|
2378
|
-
value: segment[property].value || 0,
|
2379
|
-
unit: segment[property].unit || "PIXELS"
|
2380
|
-
};
|
2381
|
-
} else {
|
2382
|
-
safeSegment[property] = { value: 0, unit: "PIXELS" };
|
2383
|
-
}
|
2384
|
-
} else if (property === "fills") {
|
2385
|
-
// Handle fills which can be complex
|
2386
|
-
safeSegment[property] = segment[property] ? JSON.parse(JSON.stringify(segment[property])) : [];
|
2387
|
-
} else {
|
2388
|
-
// Handle simple properties
|
2389
|
-
safeSegment[property] = segment[property];
|
2390
|
-
}
|
2391
|
-
|
2392
|
-
return safeSegment;
|
2393
|
-
});
|
2394
|
-
|
2395
|
-
return {
|
2396
|
-
id: node.id,
|
2397
|
-
name: node.name,
|
2398
|
-
property: property,
|
2399
|
-
segments: safeSegments
|
2400
|
-
};
|
2401
|
-
} catch (error) {
|
2402
|
-
throw new Error(`Error getting styled text segments: ${error.message}`);
|
2403
|
-
}
|
2404
|
-
}
|
2405
|
-
|
2406
|
-
async function loadFontAsyncWrapper(params) {
|
2407
|
-
const { family, style = "Regular" } = params || {};
|
2408
|
-
if (!family) {
|
2409
|
-
throw new Error("Missing font family");
|
2410
|
-
}
|
2411
|
-
|
2412
|
-
try {
|
2413
|
-
await figma.loadFontAsync({ family, style });
|
2414
|
-
return {
|
2415
|
-
success: true,
|
2416
|
-
family: family,
|
2417
|
-
style: style,
|
2418
|
-
message: `Successfully loaded ${family} ${style}`
|
2419
|
-
};
|
2420
|
-
} catch (error) {
|
2421
|
-
throw new Error(`Error loading font: ${error.message}`);
|
2422
|
-
}
|
2423
|
-
}
|
2424
|
-
|
2425
|
-
async function getRemoteComponents() {
|
2426
|
-
try {
|
2427
|
-
// Check if figma.teamLibrary is available
|
2428
|
-
if (!figma.teamLibrary) {
|
2429
|
-
console.error("Error: figma.teamLibrary API is not available");
|
2430
|
-
throw new Error("The figma.teamLibrary API is not available in this context");
|
2431
|
-
}
|
2432
|
-
|
2433
|
-
// Check if figma.teamLibrary.getAvailableComponentsAsync exists
|
2434
|
-
if (!figma.teamLibrary.getAvailableComponentsAsync) {
|
2435
|
-
console.error("Error: figma.teamLibrary.getAvailableComponentsAsync is not available");
|
2436
|
-
throw new Error("The getAvailableComponentsAsync method is not available");
|
2437
|
-
}
|
2438
|
-
|
2439
|
-
console.log("Starting remote components retrieval...");
|
2440
|
-
|
2441
|
-
// Set up a manual timeout to detect deadlocks
|
2442
|
-
let timeoutId;
|
2443
|
-
const timeoutPromise = new Promise((_, reject) => {
|
2444
|
-
timeoutId = setTimeout(() => {
|
2445
|
-
reject(new Error("Internal timeout while retrieving remote components (15s)"));
|
2446
|
-
}, 15000); // 15 seconds internal timeout
|
2447
|
-
});
|
2448
|
-
|
2449
|
-
// Execute the request with a manual timeout
|
2450
|
-
const fetchPromise = figma.teamLibrary.getAvailableComponentsAsync();
|
2451
|
-
|
2452
|
-
// Use Promise.race to implement the timeout
|
2453
|
-
const teamComponents = await Promise.race([fetchPromise, timeoutPromise])
|
2454
|
-
.finally(() => {
|
2455
|
-
clearTimeout(timeoutId); // Clear the timeout
|
2456
|
-
});
|
2457
|
-
|
2458
|
-
console.log(`Retrieved ${teamComponents.length} remote components`);
|
2459
|
-
|
2460
|
-
return {
|
2461
|
-
success: true,
|
2462
|
-
count: teamComponents.length,
|
2463
|
-
components: teamComponents.map(component => ({
|
2464
|
-
key: component.key,
|
2465
|
-
name: component.name,
|
2466
|
-
description: component.description || "",
|
2467
|
-
libraryName: component.libraryName
|
2468
|
-
}))
|
2469
|
-
};
|
2470
|
-
} catch (error) {
|
2471
|
-
console.error(`Detailed error retrieving remote components: ${error.message || "Unknown error"}`);
|
2472
|
-
console.error(`Stack trace: ${error.stack || "Not available"}`);
|
2473
|
-
|
2474
|
-
// Instead of returning an error object, throw an exception with the error message
|
2475
|
-
throw new Error(`Error retrieving remote components: ${error.message}`);
|
2476
|
-
}
|
2477
|
-
}
|
2478
|
-
|
2479
|
-
// Set Effects Tool
|
2480
|
-
async function setEffects(params) {
|
2481
|
-
const { nodeId, effects } = params || {};
|
2482
|
-
|
2483
|
-
if (!nodeId) {
|
2484
|
-
throw new Error("Missing nodeId parameter");
|
2485
|
-
}
|
2486
|
-
|
2487
|
-
if (!effects || !Array.isArray(effects)) {
|
2488
|
-
throw new Error("Missing or invalid effects parameter. Must be an array.");
|
2489
|
-
}
|
2490
|
-
|
2491
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
2492
|
-
if (!node) {
|
2493
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
2494
|
-
}
|
2495
|
-
|
2496
|
-
if (!("effects" in node)) {
|
2497
|
-
throw new Error(`Node does not support effects: ${nodeId}`);
|
2498
|
-
}
|
2499
|
-
|
2500
|
-
try {
|
2501
|
-
// Convert incoming effects to valid Figma effects
|
2502
|
-
const validEffects = effects.map(effect => {
|
2503
|
-
// Ensure all effects have the required properties
|
2504
|
-
if (!effect.type) {
|
2505
|
-
throw new Error("Each effect must have a type property");
|
2506
|
-
}
|
2507
|
-
|
2508
|
-
// Create a clean effect object based on type
|
2509
|
-
switch (effect.type) {
|
2510
|
-
case "DROP_SHADOW":
|
2511
|
-
case "INNER_SHADOW":
|
2512
|
-
return {
|
2513
|
-
type: effect.type,
|
2514
|
-
color: effect.color || { r: 0, g: 0, b: 0, a: 0.5 },
|
2515
|
-
offset: effect.offset || { x: 0, y: 0 },
|
2516
|
-
radius: effect.radius || 5,
|
2517
|
-
spread: effect.spread || 0,
|
2518
|
-
visible: effect.visible !== undefined ? effect.visible : true,
|
2519
|
-
blendMode: effect.blendMode || "NORMAL"
|
2520
|
-
};
|
2521
|
-
case "LAYER_BLUR":
|
2522
|
-
case "BACKGROUND_BLUR":
|
2523
|
-
return {
|
2524
|
-
type: effect.type,
|
2525
|
-
radius: effect.radius || 5,
|
2526
|
-
visible: effect.visible !== undefined ? effect.visible : true
|
2527
|
-
};
|
2528
|
-
default:
|
2529
|
-
throw new Error(`Unsupported effect type: ${effect.type}`);
|
2530
|
-
}
|
2531
|
-
});
|
2532
|
-
|
2533
|
-
// Apply the effects to the node
|
2534
|
-
node.effects = validEffects;
|
2535
|
-
|
2536
|
-
return {
|
2537
|
-
id: node.id,
|
2538
|
-
name: node.name,
|
2539
|
-
effects: node.effects
|
2540
|
-
};
|
2541
|
-
} catch (error) {
|
2542
|
-
throw new Error(`Error setting effects: ${error.message}`);
|
2543
|
-
}
|
2544
|
-
}
|
2545
|
-
|
2546
|
-
// Set Effect Style ID Tool
|
2547
|
-
async function setEffectStyleId(params) {
|
2548
|
-
const { nodeId, effectStyleId } = params || {};
|
2549
|
-
|
2550
|
-
if (!nodeId) {
|
2551
|
-
throw new Error("Missing nodeId parameter");
|
2552
|
-
}
|
2553
|
-
|
2554
|
-
if (!effectStyleId) {
|
2555
|
-
throw new Error("Missing effectStyleId parameter");
|
2556
|
-
}
|
2557
|
-
|
2558
|
-
try {
|
2559
|
-
// Set up a manual timeout to detect long operations
|
2560
|
-
let timeoutId;
|
2561
|
-
const timeoutPromise = new Promise((_, reject) => {
|
2562
|
-
timeoutId = setTimeout(() => {
|
2563
|
-
reject(new Error("Timeout while setting effect style ID (8s). The operation took too long to complete."));
|
2564
|
-
}, 8000); // 8 seconds timeout
|
2565
|
-
});
|
2566
|
-
|
2567
|
-
console.log(`Starting to set effect style ID ${effectStyleId} on node ${nodeId}...`);
|
2568
|
-
|
2569
|
-
// Get node and validate in a promise
|
2570
|
-
const nodePromise = (async () => {
|
2571
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
2572
|
-
if (!node) {
|
2573
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
2574
|
-
}
|
2575
|
-
|
2576
|
-
if (!("effectStyleId" in node)) {
|
2577
|
-
throw new Error(`Node with ID ${nodeId} does not support effect styles`);
|
2578
|
-
}
|
2579
|
-
|
2580
|
-
// Try to validate the effect style exists before applying
|
2581
|
-
console.log(`Fetching effect styles to validate style ID: ${effectStyleId}`);
|
2582
|
-
const effectStyles = await figma.getLocalEffectStylesAsync();
|
2583
|
-
const foundStyle = effectStyles.find(style => style.id === effectStyleId);
|
2584
|
-
|
2585
|
-
if (!foundStyle) {
|
2586
|
-
throw new Error(`Effect style not found with ID: ${effectStyleId}. Available styles: ${effectStyles.length}`);
|
2587
|
-
}
|
2588
|
-
|
2589
|
-
console.log(`Effect style found, applying to node...`);
|
2590
|
-
|
2591
|
-
// Apply the effect style to the node
|
2592
|
-
node.effectStyleId = effectStyleId;
|
2593
|
-
|
2594
|
-
return {
|
2595
|
-
id: node.id,
|
2596
|
-
name: node.name,
|
2597
|
-
effectStyleId: node.effectStyleId,
|
2598
|
-
appliedEffects: node.effects
|
2599
|
-
};
|
2600
|
-
})();
|
2601
|
-
|
2602
|
-
// Race between the node operation and the timeout
|
2603
|
-
const result = await Promise.race([nodePromise, timeoutPromise])
|
2604
|
-
.finally(() => {
|
2605
|
-
// Clear the timeout to prevent memory leaks
|
2606
|
-
clearTimeout(timeoutId);
|
2607
|
-
});
|
2608
|
-
|
2609
|
-
console.log(`Successfully set effect style ID on node ${nodeId}`);
|
2610
|
-
return result;
|
2611
|
-
} catch (error) {
|
2612
|
-
console.error(`Error setting effect style ID: ${error.message || "Unknown error"}`);
|
2613
|
-
console.error(`Stack trace: ${error.stack || "Not available"}`);
|
2614
|
-
|
2615
|
-
// Proporcionar mensajes de error específicos para diferentes casos
|
2616
|
-
if (error.message.includes("timeout") || error.message.includes("Timeout")) {
|
2617
|
-
throw new Error(`The operation timed out after 8 seconds. This could happen with complex nodes or effects. Try with a simpler node or effect style.`);
|
2618
|
-
} else if (error.message.includes("not found") && error.message.includes("Node")) {
|
2619
|
-
throw new Error(`Node with ID "${nodeId}" not found. Make sure the node exists in the current document.`);
|
2620
|
-
} else if (error.message.includes("not found") && error.message.includes("style")) {
|
2621
|
-
throw new Error(`Effect style with ID "${effectStyleId}" not found. Make sure the style exists in your local styles.`);
|
2622
|
-
} else if (error.message.includes("does not support")) {
|
2623
|
-
throw new Error(`The selected node type does not support effect styles. Only certain node types like frames, components, and instances can have effect styles.`);
|
2624
|
-
} else {
|
2625
|
-
throw new Error(`Error setting effect style ID: ${error.message}`);
|
2626
|
-
}
|
2627
|
-
}
|
2628
|
-
}
|
2629
|
-
|
2630
|
-
// Function to group nodes
|
2631
|
-
async function groupNodes(params) {
|
2632
|
-
const { nodeIds, name } = params || {};
|
2633
|
-
|
2634
|
-
if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length < 2) {
|
2635
|
-
throw new Error("Must provide at least two nodeIds to group");
|
2636
|
-
}
|
2637
|
-
|
2638
|
-
try {
|
2639
|
-
// Get all nodes to be grouped
|
2640
|
-
const nodesToGroup = [];
|
2641
|
-
for (const nodeId of nodeIds) {
|
2642
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
2643
|
-
if (!node) {
|
2644
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
2645
|
-
}
|
2646
|
-
nodesToGroup.push(node);
|
2647
|
-
}
|
2648
|
-
|
2649
|
-
// Verify that all nodes have the same parent
|
2650
|
-
const parent = nodesToGroup[0].parent;
|
2651
|
-
for (const node of nodesToGroup) {
|
2652
|
-
if (node.parent !== parent) {
|
2653
|
-
throw new Error("All nodes must have the same parent to be grouped");
|
2654
|
-
}
|
2655
|
-
}
|
2656
|
-
|
2657
|
-
// Create a group and add the nodes to it
|
2658
|
-
const group = figma.group(nodesToGroup, parent);
|
2659
|
-
|
2660
|
-
// Optionally set a name for the group
|
2661
|
-
if (name) {
|
2662
|
-
group.name = name;
|
2663
|
-
}
|
2664
|
-
|
2665
|
-
return {
|
2666
|
-
id: group.id,
|
2667
|
-
name: group.name,
|
2668
|
-
type: group.type,
|
2669
|
-
children: group.children.map(child => ({ id: child.id, name: child.name, type: child.type }))
|
2670
|
-
};
|
2671
|
-
} catch (error) {
|
2672
|
-
throw new Error(`Error grouping nodes: ${error.message}`);
|
2673
|
-
}
|
2674
|
-
}
|
2675
|
-
|
2676
|
-
// Function to ungroup nodes
|
2677
|
-
async function ungroupNodes(params) {
|
2678
|
-
const { nodeId } = params || {};
|
2679
|
-
|
2680
|
-
if (!nodeId) {
|
2681
|
-
throw new Error("Missing nodeId parameter");
|
2682
|
-
}
|
2683
|
-
|
2684
|
-
try {
|
2685
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
2686
|
-
if (!node) {
|
2687
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
2688
|
-
}
|
2689
|
-
|
2690
|
-
// Verify that the node is a group or a frame
|
2691
|
-
if (node.type !== "GROUP" && node.type !== "FRAME") {
|
2692
|
-
throw new Error(`Node with ID ${nodeId} is not a GROUP or FRAME`);
|
2693
|
-
}
|
2694
|
-
|
2695
|
-
// Get the parent and children before ungrouping
|
2696
|
-
const parent = node.parent;
|
2697
|
-
const children = [...node.children];
|
2698
|
-
|
2699
|
-
// Ungroup the node
|
2700
|
-
const ungroupedItems = figma.ungroup(node);
|
2701
|
-
|
2702
|
-
return {
|
2703
|
-
success: true,
|
2704
|
-
ungroupedCount: ungroupedItems.length,
|
2705
|
-
items: ungroupedItems.map(item => ({ id: item.id, name: item.name, type: item.type }))
|
2706
|
-
};
|
2707
|
-
} catch (error) {
|
2708
|
-
throw new Error(`Error ungrouping node: ${error.message}`);
|
2709
|
-
}
|
2710
|
-
}
|
2711
|
-
|
2712
|
-
// Function to flatten nodes (e.g., boolean operations, convert to path)
|
2713
|
-
async function flattenNode(params) {
|
2714
|
-
const { nodeId } = params || {};
|
2715
|
-
|
2716
|
-
if (!nodeId) {
|
2717
|
-
throw new Error("Missing nodeId parameter");
|
2718
|
-
}
|
2719
|
-
|
2720
|
-
try {
|
2721
|
-
const node = await figma.getNodeByIdAsync(nodeId);
|
2722
|
-
if (!node) {
|
2723
|
-
throw new Error(`Node not found with ID: ${nodeId}`);
|
2724
|
-
}
|
2725
|
-
|
2726
|
-
// Check for specific node types that can be flattened
|
2727
|
-
const flattenableTypes = ["VECTOR", "BOOLEAN_OPERATION", "STAR", "POLYGON", "ELLIPSE", "RECTANGLE"];
|
2728
|
-
|
2729
|
-
if (!flattenableTypes.includes(node.type)) {
|
2730
|
-
throw new Error(`Node with ID ${nodeId} and type ${node.type} cannot be flattened. Only vector-based nodes can be flattened.`);
|
2731
|
-
}
|
2732
|
-
|
2733
|
-
// Verify the node has the flatten method before calling it
|
2734
|
-
if (typeof node.flatten !== 'function') {
|
2735
|
-
throw new Error(`Node with ID ${nodeId} does not support the flatten operation.`);
|
2736
|
-
}
|
2737
|
-
|
2738
|
-
// Implement a timeout mechanism
|
2739
|
-
let timeoutId;
|
2740
|
-
const timeoutPromise = new Promise((_, reject) => {
|
2741
|
-
timeoutId = setTimeout(() => {
|
2742
|
-
reject(new Error("Flatten operation timed out after 8 seconds. The node may be too complex."));
|
2743
|
-
}, 8000); // 8 seconds timeout
|
2744
|
-
});
|
2745
|
-
|
2746
|
-
// Execute the flatten operation in a promise
|
2747
|
-
const flattenPromise = new Promise((resolve, reject) => {
|
2748
|
-
// Execute in the next tick to allow UI updates
|
2749
|
-
setTimeout(() => {
|
2750
|
-
try {
|
2751
|
-
console.log(`Starting flatten operation for node ID ${nodeId}...`);
|
2752
|
-
const flattened = node.flatten();
|
2753
|
-
console.log(`Flatten operation completed successfully for node ID ${nodeId}`);
|
2754
|
-
resolve(flattened);
|
2755
|
-
} catch (err) {
|
2756
|
-
console.error(`Error during flatten operation: ${err.message}`);
|
2757
|
-
reject(err);
|
2758
|
-
}
|
2759
|
-
}, 0);
|
2760
|
-
});
|
2761
|
-
|
2762
|
-
// Race between the timeout and the operation
|
2763
|
-
const flattened = await Promise.race([flattenPromise, timeoutPromise])
|
2764
|
-
.finally(() => {
|
2765
|
-
// Clear the timeout to prevent memory leaks
|
2766
|
-
clearTimeout(timeoutId);
|
2767
|
-
});
|
2768
|
-
|
2769
|
-
return {
|
2770
|
-
id: flattened.id,
|
2771
|
-
name: flattened.name,
|
2772
|
-
type: flattened.type
|
2773
|
-
};
|
2774
|
-
} catch (error) {
|
2775
|
-
console.error(`Error in flattenNode: ${error.message}`);
|
2776
|
-
if (error.message.includes("timed out")) {
|
2777
|
-
// Provide a more helpful message for timeout errors
|
2778
|
-
throw new Error(`The flatten operation timed out. This usually happens with complex nodes. Try simplifying the node first or breaking it into smaller parts.`);
|
2779
|
-
} else {
|
2780
|
-
throw new Error(`Error flattening node: ${error.message}`);
|
2781
|
-
}
|
2782
|
-
}
|
2783
|
-
}
|
2784
|
-
|
2785
|
-
// Function to insert a child into a parent node
|
2786
|
-
async function insertChild(params) {
|
2787
|
-
const { parentId, childId, index } = params || {};
|
2788
|
-
|
2789
|
-
if (!parentId) {
|
2790
|
-
throw new Error("Missing parentId parameter");
|
2791
|
-
}
|
2792
|
-
|
2793
|
-
if (!childId) {
|
2794
|
-
throw new Error("Missing childId parameter");
|
2795
|
-
}
|
2796
|
-
|
2797
|
-
try {
|
2798
|
-
// Get the parent and child nodes
|
2799
|
-
const parent = await figma.getNodeByIdAsync(parentId);
|
2800
|
-
if (!parent) {
|
2801
|
-
throw new Error(`Parent node not found with ID: ${parentId}`);
|
2802
|
-
}
|
2803
|
-
|
2804
|
-
const child = await figma.getNodeByIdAsync(childId);
|
2805
|
-
if (!child) {
|
2806
|
-
throw new Error(`Child node not found with ID: ${childId}`);
|
2807
|
-
}
|
2808
|
-
|
2809
|
-
// Check if the parent can have children
|
2810
|
-
if (!("appendChild" in parent)) {
|
2811
|
-
throw new Error(`Parent node with ID ${parentId} cannot have children`);
|
2812
|
-
}
|
2813
|
-
|
2814
|
-
// Save child's current parent for proper handling
|
2815
|
-
const originalParent = child.parent;
|
2816
|
-
|
2817
|
-
// Insert the child at the specified index or append it
|
2818
|
-
if (index !== undefined && index >= 0 && index <= parent.children.length) {
|
2819
|
-
parent.insertChild(index, child);
|
2820
|
-
} else {
|
2821
|
-
parent.appendChild(child);
|
2822
|
-
}
|
2823
|
-
|
2824
|
-
// Verify that the insertion worked
|
2825
|
-
const newIndex = parent.children.indexOf(child);
|
2826
|
-
|
2827
|
-
return {
|
2828
|
-
parentId: parent.id,
|
2829
|
-
childId: child.id,
|
2830
|
-
index: newIndex,
|
2831
|
-
success: newIndex !== -1,
|
2832
|
-
previousParentId: originalParent ? originalParent.id : null
|
2833
|
-
};
|
2834
|
-
} catch (error) {
|
2835
|
-
console.error(`Error inserting child: ${error.message}`, error);
|
2836
|
-
throw new Error(`Error inserting child: ${error.message}`);
|
2837
|
-
}
|
2838
|
-
}
|
2839
|
-
|
2840
|
-
async function createEllipse(params) {
|
2841
|
-
const {
|
2842
|
-
x = 0,
|
2843
|
-
y = 0,
|
2844
|
-
width = 100,
|
2845
|
-
height = 100,
|
2846
|
-
name = "Ellipse",
|
2847
|
-
parentId,
|
2848
|
-
fillColor = { r: 0.8, g: 0.8, b: 0.8, a: 1 },
|
2849
|
-
strokeColor,
|
2850
|
-
strokeWeight
|
2851
|
-
} = params || {};
|
2852
|
-
|
2853
|
-
// Create a new ellipse node
|
2854
|
-
const ellipse = figma.createEllipse();
|
2855
|
-
ellipse.name = name;
|
2856
|
-
|
2857
|
-
// Position and size the ellipse
|
2858
|
-
ellipse.x = x;
|
2859
|
-
ellipse.y = y;
|
2860
|
-
ellipse.resize(width, height);
|
2861
|
-
|
2862
|
-
// Set fill color if provided
|
2863
|
-
if (fillColor) {
|
2864
|
-
const fillStyle = {
|
2865
|
-
type: "SOLID",
|
2866
|
-
color: {
|
2867
|
-
r: parseFloat(fillColor.r) || 0,
|
2868
|
-
g: parseFloat(fillColor.g) || 0,
|
2869
|
-
b: parseFloat(fillColor.b) || 0,
|
2870
|
-
},
|
2871
|
-
opacity: parseFloat(fillColor.a) || 1
|
2872
|
-
};
|
2873
|
-
ellipse.fills = [fillStyle];
|
2874
|
-
}
|
2875
|
-
|
2876
|
-
// Set stroke color and weight if provided
|
2877
|
-
if (strokeColor) {
|
2878
|
-
const strokeStyle = {
|
2879
|
-
type: "SOLID",
|
2880
|
-
color: {
|
2881
|
-
r: parseFloat(strokeColor.r) || 0,
|
2882
|
-
g: parseFloat(strokeColor.g) || 0,
|
2883
|
-
b: parseFloat(strokeColor.b) || 0,
|
2884
|
-
},
|
2885
|
-
opacity: parseFloat(strokeColor.a) || 1
|
2886
|
-
};
|
2887
|
-
ellipse.strokes = [strokeStyle];
|
2888
|
-
|
2889
|
-
if (strokeWeight) {
|
2890
|
-
ellipse.strokeWeight = strokeWeight;
|
2891
|
-
}
|
2892
|
-
}
|
2893
|
-
|
2894
|
-
// If parentId is provided, append to that node, otherwise append to current page
|
2895
|
-
if (parentId) {
|
2896
|
-
const parentNode = await figma.getNodeByIdAsync(parentId);
|
2897
|
-
if (!parentNode) {
|
2898
|
-
throw new Error(`Parent node not found with ID: ${parentId}`);
|
2899
|
-
}
|
2900
|
-
if (!("appendChild" in parentNode)) {
|
2901
|
-
throw new Error(`Parent node does not support children: ${parentId}`);
|
2902
|
-
}
|
2903
|
-
parentNode.appendChild(ellipse);
|
2904
|
-
} else {
|
2905
|
-
figma.currentPage.appendChild(ellipse);
|
2906
|
-
}
|
2907
|
-
|
2908
|
-
return {
|
2909
|
-
id: ellipse.id,
|
2910
|
-
name: ellipse.name,
|
2911
|
-
type: ellipse.type,
|
2912
|
-
x: ellipse.x,
|
2913
|
-
y: ellipse.y,
|
2914
|
-
width: ellipse.width,
|
2915
|
-
height: ellipse.height
|
2916
|
-
};
|
2917
|
-
}
|
2918
|
-
|
2919
|
-
async function createPolygon(params) {
|
2920
|
-
const {
|
2921
|
-
x = 0,
|
2922
|
-
y = 0,
|
2923
|
-
width = 100,
|
2924
|
-
height = 100,
|
2925
|
-
sides = 6,
|
2926
|
-
name = "Polygon",
|
2927
|
-
parentId,
|
2928
|
-
fillColor,
|
2929
|
-
strokeColor,
|
2930
|
-
strokeWeight
|
2931
|
-
} = params || {};
|
2932
|
-
|
2933
|
-
// Create the polygon
|
2934
|
-
const polygon = figma.createPolygon();
|
2935
|
-
polygon.x = x;
|
2936
|
-
polygon.y = y;
|
2937
|
-
polygon.resize(width, height);
|
2938
|
-
polygon.name = name;
|
2939
|
-
|
2940
|
-
// Set the number of sides
|
2941
|
-
if (sides >= 3) {
|
2942
|
-
polygon.pointCount = sides;
|
2943
|
-
}
|
2944
|
-
|
2945
|
-
// Set fill color if provided
|
2946
|
-
if (fillColor) {
|
2947
|
-
const paintStyle = {
|
2948
|
-
type: "SOLID",
|
2949
|
-
color: {
|
2950
|
-
r: parseFloat(fillColor.r) || 0,
|
2951
|
-
g: parseFloat(fillColor.g) || 0,
|
2952
|
-
b: parseFloat(fillColor.b) || 0,
|
2953
|
-
},
|
2954
|
-
opacity: parseFloat(fillColor.a) || 1,
|
2955
|
-
};
|
2956
|
-
polygon.fills = [paintStyle];
|
2957
|
-
}
|
2958
|
-
|
2959
|
-
// Set stroke color and weight if provided
|
2960
|
-
if (strokeColor) {
|
2961
|
-
const strokeStyle = {
|
2962
|
-
type: "SOLID",
|
2963
|
-
color: {
|
2964
|
-
r: parseFloat(strokeColor.r) || 0,
|
2965
|
-
g: parseFloat(strokeColor.g) || 0,
|
2966
|
-
b: parseFloat(strokeColor.b) || 0,
|
2967
|
-
},
|
2968
|
-
opacity: parseFloat(strokeColor.a) || 1,
|
2969
|
-
};
|
2970
|
-
polygon.strokes = [strokeStyle];
|
2971
|
-
}
|
2972
|
-
|
2973
|
-
// Set stroke weight if provided
|
2974
|
-
if (strokeWeight !== undefined) {
|
2975
|
-
polygon.strokeWeight = strokeWeight;
|
2976
|
-
}
|
2977
|
-
|
2978
|
-
// If parentId is provided, append to that node, otherwise append to current page
|
2979
|
-
if (parentId) {
|
2980
|
-
const parentNode = await figma.getNodeByIdAsync(parentId);
|
2981
|
-
if (!parentNode) {
|
2982
|
-
throw new Error(`Parent node not found with ID: ${parentId}`);
|
2983
|
-
}
|
2984
|
-
if (!("appendChild" in parentNode)) {
|
2985
|
-
throw new Error(`Parent node does not support children: ${parentId}`);
|
2986
|
-
}
|
2987
|
-
parentNode.appendChild(polygon);
|
2988
|
-
} else {
|
2989
|
-
figma.currentPage.appendChild(polygon);
|
2990
|
-
}
|
2991
|
-
|
2992
|
-
return {
|
2993
|
-
id: polygon.id,
|
2994
|
-
name: polygon.name,
|
2995
|
-
type: polygon.type,
|
2996
|
-
x: polygon.x,
|
2997
|
-
y: polygon.y,
|
2998
|
-
width: polygon.width,
|
2999
|
-
height: polygon.height,
|
3000
|
-
pointCount: polygon.pointCount,
|
3001
|
-
fills: polygon.fills,
|
3002
|
-
strokes: polygon.strokes,
|
3003
|
-
strokeWeight: polygon.strokeWeight,
|
3004
|
-
parentId: polygon.parent ? polygon.parent.id : undefined,
|
3005
|
-
};
|
3006
|
-
}
|
3007
|
-
|
3008
|
-
async function createStar(params) {
|
3009
|
-
const {
|
3010
|
-
x = 0,
|
3011
|
-
y = 0,
|
3012
|
-
width = 100,
|
3013
|
-
height = 100,
|
3014
|
-
points = 5,
|
3015
|
-
innerRadius = 0.5, // As a proportion of the outer radius
|
3016
|
-
name = "Star",
|
3017
|
-
parentId,
|
3018
|
-
fillColor,
|
3019
|
-
strokeColor,
|
3020
|
-
strokeWeight
|
3021
|
-
} = params || {};
|
3022
|
-
|
3023
|
-
// Create the star
|
3024
|
-
const star = figma.createStar();
|
3025
|
-
star.x = x;
|
3026
|
-
star.y = y;
|
3027
|
-
star.resize(width, height);
|
3028
|
-
star.name = name;
|
3029
|
-
|
3030
|
-
// Set the number of points
|
3031
|
-
if (points >= 3) {
|
3032
|
-
star.pointCount = points;
|
3033
|
-
}
|
3034
|
-
|
3035
|
-
// Set the inner radius ratio
|
3036
|
-
if (innerRadius > 0 && innerRadius < 1) {
|
3037
|
-
star.innerRadius = innerRadius;
|
3038
|
-
}
|
3039
|
-
|
3040
|
-
// Set fill color if provided
|
3041
|
-
if (fillColor) {
|
3042
|
-
const paintStyle = {
|
3043
|
-
type: "SOLID",
|
3044
|
-
color: {
|
3045
|
-
r: parseFloat(fillColor.r) || 0,
|
3046
|
-
g: parseFloat(fillColor.g) || 0,
|
3047
|
-
b: parseFloat(fillColor.b) || 0,
|
3048
|
-
},
|
3049
|
-
opacity: parseFloat(fillColor.a) || 1,
|
3050
|
-
};
|
3051
|
-
star.fills = [paintStyle];
|
3052
|
-
}
|
3053
|
-
|
3054
|
-
// Set stroke color and weight if provided
|
3055
|
-
if (strokeColor) {
|
3056
|
-
const strokeStyle = {
|
3057
|
-
type: "SOLID",
|
3058
|
-
color: {
|
3059
|
-
r: parseFloat(strokeColor.r) || 0,
|
3060
|
-
g: parseFloat(strokeColor.g) || 0,
|
3061
|
-
b: parseFloat(strokeColor.b) || 0,
|
3062
|
-
},
|
3063
|
-
opacity: parseFloat(strokeColor.a) || 1,
|
3064
|
-
};
|
3065
|
-
star.strokes = [strokeStyle];
|
3066
|
-
}
|
3067
|
-
|
3068
|
-
// Set stroke weight if provided
|
3069
|
-
if (strokeWeight !== undefined) {
|
3070
|
-
star.strokeWeight = strokeWeight;
|
3071
|
-
}
|
3072
|
-
|
3073
|
-
// If parentId is provided, append to that node, otherwise append to current page
|
3074
|
-
if (parentId) {
|
3075
|
-
const parentNode = await figma.getNodeByIdAsync(parentId);
|
3076
|
-
if (!parentNode) {
|
3077
|
-
throw new Error(`Parent node not found with ID: ${parentId}`);
|
3078
|
-
}
|
3079
|
-
if (!("appendChild" in parentNode)) {
|
3080
|
-
throw new Error(`Parent node does not support children: ${parentId}`);
|
3081
|
-
}
|
3082
|
-
parentNode.appendChild(star);
|
3083
|
-
} else {
|
3084
|
-
figma.currentPage.appendChild(star);
|
3085
|
-
}
|
3086
|
-
|
3087
|
-
return {
|
3088
|
-
id: star.id,
|
3089
|
-
name: star.name,
|
3090
|
-
type: star.type,
|
3091
|
-
x: star.x,
|
3092
|
-
y: star.y,
|
3093
|
-
width: star.width,
|
3094
|
-
height: star.height,
|
3095
|
-
pointCount: star.pointCount,
|
3096
|
-
innerRadius: star.innerRadius,
|
3097
|
-
fills: star.fills,
|
3098
|
-
strokes: star.strokes,
|
3099
|
-
strokeWeight: star.strokeWeight,
|
3100
|
-
parentId: star.parent ? star.parent.id : undefined,
|
3101
|
-
};
|
3102
|
-
}
|
3103
|
-
|
3104
|
-
async function createVector(params) {
|
3105
|
-
const {
|
3106
|
-
x = 0,
|
3107
|
-
y = 0,
|
3108
|
-
width = 100,
|
3109
|
-
height = 100,
|
3110
|
-
name = "Vector",
|
3111
|
-
parentId,
|
3112
|
-
vectorPaths = [],
|
3113
|
-
fillColor,
|
3114
|
-
strokeColor,
|
3115
|
-
strokeWeight
|
3116
|
-
} = params || {};
|
3117
|
-
|
3118
|
-
// Create the vector
|
3119
|
-
const vector = figma.createVector();
|
3120
|
-
vector.x = x;
|
3121
|
-
vector.y = y;
|
3122
|
-
vector.resize(width, height);
|
3123
|
-
vector.name = name;
|
3124
|
-
|
3125
|
-
// Set vector paths if provided
|
3126
|
-
if (vectorPaths && vectorPaths.length > 0) {
|
3127
|
-
vector.vectorPaths = vectorPaths.map(path => {
|
3128
|
-
return {
|
3129
|
-
windingRule: path.windingRule || "EVENODD",
|
3130
|
-
data: path.data || ""
|
3131
|
-
};
|
3132
|
-
});
|
3133
|
-
}
|
3134
|
-
|
3135
|
-
// Set fill color if provided
|
3136
|
-
if (fillColor) {
|
3137
|
-
const paintStyle = {
|
3138
|
-
type: "SOLID",
|
3139
|
-
color: {
|
3140
|
-
r: parseFloat(fillColor.r) || 0,
|
3141
|
-
g: parseFloat(fillColor.g) || 0,
|
3142
|
-
b: parseFloat(fillColor.b) || 0,
|
3143
|
-
},
|
3144
|
-
opacity: parseFloat(fillColor.a) || 1,
|
3145
|
-
};
|
3146
|
-
vector.fills = [paintStyle];
|
3147
|
-
}
|
3148
|
-
|
3149
|
-
// Set stroke color and weight if provided
|
3150
|
-
if (strokeColor) {
|
3151
|
-
const strokeStyle = {
|
3152
|
-
type: "SOLID",
|
3153
|
-
color: {
|
3154
|
-
r: parseFloat(strokeColor.r) || 0,
|
3155
|
-
g: parseFloat(strokeColor.g) || 0,
|
3156
|
-
b: parseFloat(strokeColor.b) || 0,
|
3157
|
-
},
|
3158
|
-
opacity: parseFloat(strokeColor.a) || 1,
|
3159
|
-
};
|
3160
|
-
vector.strokes = [strokeStyle];
|
3161
|
-
}
|
3162
|
-
|
3163
|
-
// Set stroke weight if provided
|
3164
|
-
if (strokeWeight !== undefined) {
|
3165
|
-
vector.strokeWeight = strokeWeight;
|
3166
|
-
}
|
3167
|
-
|
3168
|
-
// If parentId is provided, append to that node, otherwise append to current page
|
3169
|
-
if (parentId) {
|
3170
|
-
const parentNode = await figma.getNodeByIdAsync(parentId);
|
3171
|
-
if (!parentNode) {
|
3172
|
-
throw new Error(`Parent node not found with ID: ${parentId}`);
|
3173
|
-
}
|
3174
|
-
if (!("appendChild" in parentNode)) {
|
3175
|
-
throw new Error(`Parent node does not support children: ${parentId}`);
|
3176
|
-
}
|
3177
|
-
parentNode.appendChild(vector);
|
3178
|
-
} else {
|
3179
|
-
figma.currentPage.appendChild(vector);
|
3180
|
-
}
|
3181
|
-
|
3182
|
-
return {
|
3183
|
-
id: vector.id,
|
3184
|
-
name: vector.name,
|
3185
|
-
type: vector.type,
|
3186
|
-
x: vector.x,
|
3187
|
-
y: vector.y,
|
3188
|
-
width: vector.width,
|
3189
|
-
height: vector.height,
|
3190
|
-
vectorNetwork: vector.vectorNetwork,
|
3191
|
-
fills: vector.fills,
|
3192
|
-
strokes: vector.strokes,
|
3193
|
-
strokeWeight: vector.strokeWeight,
|
3194
|
-
parentId: vector.parent ? vector.parent.id : undefined,
|
3195
|
-
};
|
3196
|
-
}
|
3197
|
-
|
3198
|
-
async function createLine(params) {
|
3199
|
-
const {
|
3200
|
-
x1 = 0,
|
3201
|
-
y1 = 0,
|
3202
|
-
x2 = 100,
|
3203
|
-
y2 = 0,
|
3204
|
-
name = "Line",
|
3205
|
-
parentId,
|
3206
|
-
strokeColor = { r: 0, g: 0, b: 0, a: 1 },
|
3207
|
-
strokeWeight = 1,
|
3208
|
-
strokeCap = "NONE" // Can be "NONE", "ROUND", "SQUARE", "ARROW_LINES", or "ARROW_EQUILATERAL"
|
3209
|
-
} = params || {};
|
3210
|
-
|
3211
|
-
// Create a vector node to represent the line
|
3212
|
-
const line = figma.createVector();
|
3213
|
-
line.name = name;
|
3214
|
-
|
3215
|
-
// Position the line at the starting point
|
3216
|
-
line.x = x1;
|
3217
|
-
line.y = y1;
|
3218
|
-
|
3219
|
-
// Calculate the vector size
|
3220
|
-
const width = Math.abs(x2 - x1);
|
3221
|
-
const height = Math.abs(y2 - y1);
|
3222
|
-
line.resize(width > 0 ? width : 1, height > 0 ? height : 1);
|
3223
|
-
|
3224
|
-
// Create vector path data for a straight line
|
3225
|
-
// SVG path data format: M (move to) starting point, L (line to) ending point
|
3226
|
-
const dx = x2 - x1;
|
3227
|
-
const dy = y2 - y1;
|
3228
|
-
|
3229
|
-
// Calculate relative endpoint coordinates in the vector's local coordinate system
|
3230
|
-
const endX = dx > 0 ? width : 0;
|
3231
|
-
const endY = dy > 0 ? height : 0;
|
3232
|
-
const startX = dx > 0 ? 0 : width;
|
3233
|
-
const startY = dy > 0 ? 0 : height;
|
3234
|
-
|
3235
|
-
// Generate SVG path data for the line
|
3236
|
-
const pathData = `M ${startX} ${startY} L ${endX} ${endY}`;
|
3237
|
-
|
3238
|
-
// Set vector paths
|
3239
|
-
line.vectorPaths = [{
|
3240
|
-
windingRule: "NONZERO",
|
3241
|
-
data: pathData
|
3242
|
-
}];
|
3243
|
-
|
3244
|
-
// Set stroke color
|
3245
|
-
const strokeStyle = {
|
3246
|
-
type: "SOLID",
|
3247
|
-
color: {
|
3248
|
-
r: parseFloat(strokeColor.r) || 0,
|
3249
|
-
g: parseFloat(strokeColor.g) || 0,
|
3250
|
-
b: parseFloat(strokeColor.b) || 0,
|
3251
|
-
},
|
3252
|
-
opacity: parseFloat(strokeColor.a) || 1
|
3253
|
-
};
|
3254
|
-
line.strokes = [strokeStyle];
|
3255
|
-
|
3256
|
-
// Set stroke weight
|
3257
|
-
line.strokeWeight = strokeWeight;
|
3258
|
-
|
3259
|
-
// Set stroke cap style if supported
|
3260
|
-
if (["NONE", "ROUND", "SQUARE", "ARROW_LINES", "ARROW_EQUILATERAL"].includes(strokeCap)) {
|
3261
|
-
line.strokeCap = strokeCap;
|
3262
|
-
}
|
3263
|
-
|
3264
|
-
// Set fill to none (transparent) as lines typically don't have fills
|
3265
|
-
line.fills = [];
|
3266
|
-
|
3267
|
-
// If parentId is provided, append to that node, otherwise append to current page
|
3268
|
-
if (parentId) {
|
3269
|
-
const parentNode = await figma.getNodeByIdAsync(parentId);
|
3270
|
-
if (!parentNode) {
|
3271
|
-
throw new Error(`Parent node not found with ID: ${parentId}`);
|
3272
|
-
}
|
3273
|
-
if (!("appendChild" in parentNode)) {
|
3274
|
-
throw new Error(`Parent node does not support children: ${parentId}`);
|
3275
|
-
}
|
3276
|
-
parentNode.appendChild(line);
|
3277
|
-
} else {
|
3278
|
-
figma.currentPage.appendChild(line);
|
3279
|
-
}
|
3280
|
-
|
3281
|
-
return {
|
3282
|
-
id: line.id,
|
3283
|
-
name: line.name,
|
3284
|
-
type: line.type,
|
3285
|
-
x: line.x,
|
3286
|
-
y: line.y,
|
3287
|
-
width: line.width,
|
3288
|
-
height: line.height,
|
3289
|
-
strokeWeight: line.strokeWeight,
|
3290
|
-
strokeCap: line.strokeCap,
|
3291
|
-
strokes: line.strokes,
|
3292
|
-
vectorPaths: line.vectorPaths,
|
3293
|
-
parentId: line.parent ? line.parent.id : undefined
|
3294
|
-
};
|
3295
|
-
}
|