├── .cursor └── rules │ ├── cursor_rules.mdc │ ├── dev_workflow.mdc │ ├── self_improve.mdc │ └── taskmaster.mdc ├── .eslintignore ├── .eslintrc.json ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── new_feature.md ├── auto_assign_reviewer.yml ├── pull_request_template.md └── workflows │ ├── auto_assign.yml │ └── deploy.yml ├── .gitignore ├── .husky ├── common.sh └── pre-push ├── .npmrc ├── .prettierignore ├── .prettierrc ├── .roo ├── rules-architect │ └── architect-rules ├── rules-ask │ └── ask-rules ├── rules-boomerang │ └── boomerang-rules ├── rules-code │ └── code-rules ├── rules-debug │ └── debug-rules ├── rules-test │ └── test-rules └── rules │ ├── dev_workflow.md │ ├── roo_rules.md │ ├── self_improve.md │ └── taskmaster.md ├── .roomodes ├── .taskmasterconfig ├── .vscode └── settings.json ├── .windsurfrules ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── index.html ├── jest.config.js ├── package.json ├── patches └── html-to-image+1.11.11.patch ├── public ├── bookpile.ico ├── favicon.ico ├── favicon.png ├── manifest.json └── robots.txt ├── scripts └── prd.txt ├── src ├── App.tsx ├── Router.tsx ├── RouterContainer.tsx ├── apis │ └── packages.ts ├── assets │ ├── icons │ │ └── logoWithBackground.tsx │ └── images │ │ ├── landing-image.png │ │ ├── landing-label.png │ │ ├── loading │ │ ├── loading-1.png │ │ ├── loading-2.png │ │ └── loading-3.png │ │ └── logo.png ├── components │ ├── @common │ │ ├── background-with-circle │ │ │ ├── BackgroundWithCircle.tsx │ │ │ └── index.ts │ │ └── heading-text │ │ │ ├── HeadingText.tsx │ │ │ └── index.ts │ ├── button-options │ │ ├── ButtonOptions.tsx │ │ ├── OptionListContainer.tsx │ │ └── index.ts │ ├── drag-container │ │ ├── DragContainer.tsx │ │ └── DraggableChip.tsx │ ├── draggable-icon │ │ ├── DraggableIcon.tsx │ │ └── index.ts │ ├── header │ │ ├── Header.tsx │ │ └── index.ts │ ├── hover-card │ │ ├── HoverCard.tsx │ │ └── index.ts │ ├── input │ │ ├── Input.tsx │ │ └── index.ts │ ├── loading │ │ ├── BlockLoading.tsx │ │ └── LogoLoading.tsx │ ├── read-repository │ │ ├── ReadRepository.tsx │ │ └── index.ts │ ├── require-state │ │ ├── RequireState.tsx │ │ └── index.ts │ ├── stack-group │ │ ├── StackGroup.tsx │ │ └── index.ts │ ├── stack │ │ ├── Stack.tsx │ │ └── index.ts │ ├── stackticon-images │ │ └── StackticonImages.tsx │ └── toast │ │ ├── Toast.tsx │ │ └── index.ts ├── constants │ ├── animations.ts │ ├── constants.ts │ ├── custom-icons.json │ ├── icons.tsx │ └── svgs.ts ├── hooks │ └── useIntersectionObserver.tsx ├── index.tsx ├── mock │ └── stacks.ts ├── pages │ ├── background-colors │ │ ├── BackgroundColors.tsx │ │ └── index.ts │ ├── home │ │ ├── Home.tsx │ │ ├── guides │ │ │ ├── GuideSample.tsx │ │ │ ├── GuideUsage.tsx │ │ │ └── index.ts │ │ ├── index.ts │ │ └── landing │ │ │ ├── Landing.tsx │ │ │ └── index.ts │ ├── page404 │ │ ├── Cute404.tsx │ │ └── index.ts │ └── result │ │ ├── Result.tsx │ │ ├── bottom-buttons │ │ ├── BottomButtons.tsx │ │ └── index.ts │ │ └── index.ts ├── providers │ └── StacksProvider.tsx ├── services │ ├── firebase │ │ ├── initialize.ts │ │ └── storage.ts │ └── google-analytics │ │ └── GoogleAnalyticsTracker.tsx ├── styles │ ├── components.ts │ ├── palette.d.ts │ ├── paletteColors.ts │ ├── theme.ts │ └── typo.ts ├── types │ ├── backgroundColors.ts │ ├── common.ts │ ├── location.ts │ ├── packageJson.ts │ └── toast.ts ├── utils │ ├── allIconInfo.ts │ ├── array.ts │ ├── customIconObjects.ts │ ├── imageConverter.ts │ ├── packageJson.ts │ ├── resultUrl.ts │ ├── string.ts │ └── test │ │ ├── allIconInfo.test.ts │ │ ├── array.test.ts │ │ ├── imageConverter.test.ts │ │ ├── packageJson.test.ts │ │ ├── resultUrl.test.ts │ │ └── string.test.ts └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── yarn.lock /.cursor/rules/cursor_rules.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: Guidelines for creating and maintaining Cursor rules to ensure consistency and effectiveness. 3 | globs: .cursor/rules/*.mdc 4 | alwaysApply: true 5 | --- 6 | 7 | - **Required Rule Structure:** 8 | ```markdown 9 | --- 10 | description: Clear, one-line description of what the rule enforces 11 | globs: path/to/files/*.ext, other/path/**/* 12 | alwaysApply: boolean 13 | --- 14 | 15 | - **Main Points in Bold** 16 | - Sub-points with details 17 | - Examples and explanations 18 | ``` 19 | 20 | - **File References:** 21 | - Use `[filename](mdc:path/to/file)` ([filename](mdc:filename)) to reference files 22 | - Example: [prisma.mdc](mdc:.cursor/rules/prisma.mdc) for rule references 23 | - Example: [schema.prisma](mdc:prisma/schema.prisma) for code references 24 | 25 | - **Code Examples:** 26 | - Use language-specific code blocks 27 | ```typescript 28 | // ✅ DO: Show good examples 29 | const goodExample = true; 30 | 31 | // ❌ DON'T: Show anti-patterns 32 | const badExample = false; 33 | ``` 34 | 35 | - **Rule Content Guidelines:** 36 | - Start with high-level overview 37 | - Include specific, actionable requirements 38 | - Show examples of correct implementation 39 | - Reference existing code when possible 40 | - Keep rules DRY by referencing other rules 41 | 42 | - **Rule Maintenance:** 43 | - Update rules when new patterns emerge 44 | - Add examples from actual codebase 45 | - Remove outdated patterns 46 | - Cross-reference related rules 47 | 48 | - **Best Practices:** 49 | - Use bullet points for clarity 50 | - Keep descriptions concise 51 | - Include both DO and DON'T examples 52 | - Reference actual code over theoretical examples 53 | - Use consistent formatting across rules -------------------------------------------------------------------------------- /.cursor/rules/self_improve.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: Guidelines for continuously improving Cursor rules based on emerging code patterns and best practices. 3 | globs: **/* 4 | alwaysApply: true 5 | --- 6 | 7 | - **Rule Improvement Triggers:** 8 | - New code patterns not covered by existing rules 9 | - Repeated similar implementations across files 10 | - Common error patterns that could be prevented 11 | - New libraries or tools being used consistently 12 | - Emerging best practices in the codebase 13 | 14 | - **Analysis Process:** 15 | - Compare new code with existing rules 16 | - Identify patterns that should be standardized 17 | - Look for references to external documentation 18 | - Check for consistent error handling patterns 19 | - Monitor test patterns and coverage 20 | 21 | - **Rule Updates:** 22 | - **Add New Rules When:** 23 | - A new technology/pattern is used in 3+ files 24 | - Common bugs could be prevented by a rule 25 | - Code reviews repeatedly mention the same feedback 26 | - New security or performance patterns emerge 27 | 28 | - **Modify Existing Rules When:** 29 | - Better examples exist in the codebase 30 | - Additional edge cases are discovered 31 | - Related rules have been updated 32 | - Implementation details have changed 33 | 34 | - **Example Pattern Recognition:** 35 | ```typescript 36 | // If you see repeated patterns like: 37 | const data = await prisma.user.findMany({ 38 | select: { id: true, email: true }, 39 | where: { status: 'ACTIVE' } 40 | }); 41 | 42 | // Consider adding to [prisma.mdc](mdc:.cursor/rules/prisma.mdc): 43 | // - Standard select fields 44 | // - Common where conditions 45 | // - Performance optimization patterns 46 | ``` 47 | 48 | - **Rule Quality Checks:** 49 | - Rules should be actionable and specific 50 | - Examples should come from actual code 51 | - References should be up to date 52 | - Patterns should be consistently enforced 53 | 54 | - **Continuous Improvement:** 55 | - Monitor code review comments 56 | - Track common development questions 57 | - Update rules after major refactors 58 | - Add links to relevant documentation 59 | - Cross-reference related rules 60 | 61 | - **Rule Deprecation:** 62 | - Mark outdated patterns as deprecated 63 | - Remove rules that no longer apply 64 | - Update references to deprecated rules 65 | - Document migration paths for old patterns 66 | 67 | - **Documentation Updates:** 68 | - Keep examples synchronized with code 69 | - Update references to external docs 70 | - Maintain links between related rules 71 | - Document breaking changes 72 | Follow [cursor_rules.mdc](mdc:.cursor/rules/cursor_rules.mdc) for proper rule formatting and structure. 73 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | 4 | .prettierrc 5 | 6 | .github 7 | 8 | vscode 9 | build 10 | dist 11 | coverage 12 | yarn* 13 | package* 14 | tsconfig* -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "browser": true, 5 | "es2021": true, 6 | "node": true 7 | }, 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "plugin:react/recommended", 12 | "plugin:import/typescript", 13 | "plugin:import/recommended", 14 | "prettier" 15 | ], 16 | "parser": "@typescript-eslint/parser", 17 | "parserOptions": { 18 | "ecmaVersion": "latest", 19 | "sourceType": "module" 20 | }, 21 | "plugins": ["react", "@typescript-eslint", "import", "unused-imports"], 22 | "settings": { 23 | "import/extensions": [".js", ".jsx", ".ts", ".tsx"], 24 | "import/resolver": { 25 | "node": { 26 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 27 | }, 28 | "typescript": { 29 | "directory": "./src" 30 | } 31 | }, 32 | "import/parsers": { 33 | "@typescript-eslint/parser": [".ts", ".tsx"] 34 | }, 35 | "react": { 36 | "version": "detect" 37 | } 38 | }, 39 | "rules": { 40 | "react/no-unknown-property": ["error", { "ignore": ["css"] }], 41 | "import/order": [ 42 | "warn", 43 | { 44 | "groups": [ 45 | "builtin", 46 | "external", 47 | "internal", 48 | ["parent", "sibling", "index"], 49 | "type", 50 | "unknown" 51 | ], 52 | "pathGroups": [ 53 | { 54 | "pattern": "{react*,react*/**}", 55 | "group": "external", 56 | "position": "before" 57 | } 58 | ], 59 | "pathGroupsExcludedImportTypes": ["react", "unknown"], 60 | "newlines-between": "always", 61 | "alphabetize": { 62 | "order": "asc", 63 | "caseInsensitive": true 64 | } 65 | } 66 | ], 67 | "react/react-in-jsx-scope": "off", 68 | "import/default": "off", 69 | "no-unused-private-class-members": "error", 70 | "no-use-before-define": "error", 71 | "unused-imports/no-unused-imports": "error", 72 | "unused-imports/no-unused-vars": [ 73 | "warn", 74 | { 75 | "vars": "all", 76 | "varsIgnorePattern": "^_", 77 | "args": "after-used", 78 | "argsIgnorePattern": "^_" 79 | } 80 | ], 81 | "@typescript-eslint/no-unused-vars": "warn", 82 | "no-console": "warn", 83 | "@typescript-eslint/consistent-type-imports": [ 84 | "error", 85 | { 86 | "prefer": "type-imports" 87 | } 88 | ], 89 | "comma-spacing": [ 90 | "warn", 91 | { 92 | "before": false, 93 | "after": true 94 | } 95 | ], 96 | "import/no-unresolved": [ 97 | 2, 98 | { 99 | "ignore": [".png$", ".webp$", ".jpg$"] 100 | } 101 | ], 102 | "indent": ["error", 2], 103 | "quotes": ["error", "single"], 104 | "semi": ["error", "always"] 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # owner of this project 2 | * @msdio 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: template for reporting issue 4 | title: '' 5 | labels: bug 6 | assignees: msdio 7 | --- 8 | 9 | ## 📌 Describe Issue 10 | 11 | ### 🐠 Checklists 12 | 13 | - [ ] 14 | - [ ] 15 | 16 | ### 🚴 Possible(or Suggest) solutions 17 | 18 | ### 🍳 Environments 19 | 20 | - Device: 21 | - OS: 22 | - Browser: 23 | - Version: 24 | 25 | ### 📷 Screenshots 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new_feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Features 3 | about: template for new features. 4 | title: '' 5 | labels: '' 6 | assignees: author 7 | --- 8 | 9 | ## 📚 Tasks 10 | 11 | ## ✔ Checklists 12 | -------------------------------------------------------------------------------- /.github/auto_assign_reviewer.yml: -------------------------------------------------------------------------------- 1 | addReviewers: true 2 | 3 | addAssignees: author 4 | 5 | reviewers: 6 | - msdio 7 | 8 | # A number of reviewers added to the pull request 9 | # Set 0 to add all the reviewers (default: 0) 10 | numberOfReviewers: 0 11 | 12 | 13 | # A list of keywords to be skipped the process that add reviewers if pull requests include it 14 | # skipKeywords: 15 | # - wip -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## 📄 What I've done 2 | 3 | ### ⛱️ Major Changes 4 | 5 | - 6 | - 7 | 8 | ### 🙋 Review Points 9 | 10 | - 11 | - 12 | 13 | ### 🤔 References 14 | 15 | - [link name](url) 16 | - 17 | -------------------------------------------------------------------------------- /.github/workflows/auto_assign.yml: -------------------------------------------------------------------------------- 1 | name: AutoAssignment 2 | 3 | on: 4 | pull_request_target: 5 | types: [opened, reopened, ready_for_review] 6 | 7 | jobs: 8 | assignment: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Auto-assignment 12 | uses: kentaro-m/auto-assign-action@v1.2.1 13 | with: 14 | configuration-path: ".github/auto_assign_reviewer.yml" -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy static content to Pages 2 | 3 | on: 4 | push: 5 | branches: ['main'] 6 | paths: ['src/**'] 7 | 8 | workflow_dispatch: 9 | 10 | permissions: 11 | contents: read 12 | pages: write 13 | id-token: write 14 | 15 | concurrency: 16 | group: 'pages' 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | deploy: 21 | environment: 22 | name: github-pages 23 | url: ${{ steps.deployment.outputs.page_url }} 24 | runs-on: ubuntu-latest 25 | 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v4 29 | 30 | - name: Set up Node 31 | uses: actions/setup-node@v4 32 | with: 33 | node-version: 22 34 | cache: 'yarn' 35 | 36 | - name: Install dependencies 37 | run: yarn install 38 | 39 | - name: Create environment variables 40 | run: | 41 | touch .env 42 | echo VITE_API_KEY=${{ secrets.VITE_API_KEY }} >> .env 43 | echo VITE_AUTHDOMAIN=${{ secrets.VITE_AUTHDOMAIN }} >> .env 44 | echo VITE_PROJECT_ID=${{ secrets.VITE_PROJECT_ID }} >> .env 45 | echo VITE_STORAGE_BUCKET=${{ secrets.VITE_STORAGE_BUCKET }} >> .env 46 | echo VITE_MESSAGING_SENDER_ID=${{ secrets.VITE_MESSAGING_SENDER_ID }} >> .env 47 | echo VITE_APP_ID=${{ secrets.VITE_APP_ID }} >> .env 48 | echo VITE_MEASUREMENT_ID=${{ secrets.VITE_MEASUREMENT_ID }} >> .env 49 | echo VITE_PUBLIC_URL=${{ secrets.VITE_PUBLIC_URL }} >> .env 50 | echo VITE_GOOGLE_ANALYTICS_TRACKING_ID=${{ secrets.VITE_GOOGLE_ANALYTICS_TRACKING_ID }} >> .env 51 | 52 | - name: Build 53 | run: yarn build 54 | 55 | - name: Setup Pages 56 | uses: actions/configure-pages@v5 57 | 58 | - name: Upload artifact 59 | uses: actions/upload-pages-artifact@v3 60 | with: 61 | path: './dist' 62 | 63 | - name: Deploy to GitHub Pages 64 | id: deployment 65 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | # Logs 23 | logs 24 | *.log 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | pnpm-debug.log* 29 | 30 | .env 31 | 32 | # Added by Claude Task Master 33 | dev-debug.log 34 | # Dependency directories 35 | node_modules/ 36 | # Environment variables 37 | # Editor directories and files 38 | .idea 39 | .vscode 40 | *.suo 41 | *.ntvs* 42 | *.njsproj 43 | *.sln 44 | *.sw? 45 | # OS specific 46 | # Task files 47 | tasks.json 48 | tasks/ 49 | scripts/example_prd.txt 50 | .cursor/mcp.json -------------------------------------------------------------------------------- /.husky/common.sh: -------------------------------------------------------------------------------- 1 | command_exists () { 2 | command -v "$1" >/dev/null 2>&1 3 | } 4 | 5 | # Workaround for Windows 10, Git Bash and Yarn 6 | if command_exists winpty && test -t 1; then 7 | exec < /dev/tty 8 | fi -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | . "$(dirname -- "$0")/common.sh" 4 | 5 | protected_branch='main' 6 | policy='[Policy] DO NOT PUSH it directly to '$protected_branch' branch' 7 | 8 | current_branch=$(git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,') 9 | 10 | push_command=$(ps -ocommand= -p $PPID) 11 | 12 | is_destructive='force|delete|\-f' 13 | 14 | will_remove_protected_branch=':'$protected_branch 15 | 16 | do_exit(){ 17 | echo $policy 18 | exit 1 19 | } 20 | 21 | if [[ $push_command =~ $is_destructive ]] && [ $current_branch = $protected_branch ]; then 22 | do_exit 23 | fi 24 | 25 | if [[ $push_command =~ $is_destructive ]] && [[ $push_command =~ $protected_branch ]]; then 26 | do_exit 27 | fi 28 | 29 | if [[ $push_command =~ $will_remove_protected_branch ]]; then 30 | do_exit 31 | fi 32 | 33 | unset do_exit 34 | 35 | 36 | # check if there is an error when build project 37 | echo "[husky] Checking with tsc" 38 | yarn tsc 39 | 40 | echo "[husky] Running tests" 41 | yarn test 42 | 43 | echo "[husky] Checking errors when build project" 44 | yarn build 45 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | package.json 3 | yarn.lock 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | .github 8 | 9 | build 10 | dist 11 | coverage -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "useTabs": false, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "jsxSingleQuote": true, 7 | "trailingComma": "all", 8 | "printWidth": 100, 9 | "proseWrap": "preserve", 10 | "arrowParens": "always" 11 | } 12 | -------------------------------------------------------------------------------- /.roo/rules-architect/architect-rules: -------------------------------------------------------------------------------- 1 | **Core Directives & Agentivity:** 2 | # 1. Adhere strictly to the rules defined below. 3 | # 2. Use tools sequentially, one per message. Adhere strictly to the rules defined below. 4 | # 3. CRITICAL: ALWAYS wait for user confirmation of success after EACH tool use before proceeding. Do not assume success. 5 | # 4. Operate iteratively: Analyze task -> Plan steps -> Execute steps one by one. 6 | # 5. Use tags for *internal* analysis before tool use (context, tool choice, required params). 7 | # 6. **DO NOT DISPLAY XML TOOL TAGS IN THE OUTPUT.** 8 | # 7. **DO NOT DISPLAY YOUR THINKING IN THE OUTPUT.** 9 | 10 | **Architectural Design & Planning Role (Delegated Tasks):** 11 | 12 | Your primary role when activated via `new_task` by the Boomerang orchestrator is to perform specific architectural, design, or planning tasks, focusing on the instructions provided in the delegation message and referencing the relevant `taskmaster-ai` task ID. 13 | 14 | 1. **Analyze Delegated Task:** Carefully examine the `message` provided by Boomerang. This message contains the specific task scope, context (including the `taskmaster-ai` task ID), and constraints. 15 | 2. **Information Gathering (As Needed):** Use analysis tools to fulfill the task: 16 | * `list_files`: Understand project structure. 17 | * `read_file`: Examine specific code, configuration, or documentation files relevant to the architectural task. 18 | * `list_code_definition_names`: Analyze code structure and relationships. 19 | * `use_mcp_tool` (taskmaster-ai): Use `get_task` or `analyze_project_complexity` *only if explicitly instructed* by Boomerang in the delegation message to gather further context beyond what was provided. 20 | 3. **Task Execution (Design & Planning):** Focus *exclusively* on the delegated architectural task, which may involve: 21 | * Designing system architecture, component interactions, or data models. 22 | * Planning implementation steps or identifying necessary subtasks (to be reported back). 23 | * Analyzing technical feasibility, complexity, or potential risks. 24 | * Defining interfaces, APIs, or data contracts. 25 | * Reviewing existing code/architecture against requirements or best practices. 26 | 4. **Reporting Completion:** Signal completion using `attempt_completion`. Provide a concise yet thorough summary of the outcome in the `result` parameter. This summary is **crucial** for Boomerang to update `taskmaster-ai`. Include: 27 | * Summary of design decisions, plans created, analysis performed, or subtasks identified. 28 | * Any relevant artifacts produced (e.g., diagrams described, markdown files written - if applicable and instructed). 29 | * Completion status (success, failure, needs review). 30 | * Any significant findings, potential issues, or context gathered relevant to the next steps. 31 | 5. **Handling Issues:** 32 | * **Complexity/Review:** If you encounter significant complexity, uncertainty, or issues requiring further review (e.g., needing testing input, deeper debugging analysis), set the status to 'review' within your `attempt_completion` result and clearly state the reason. **Do not delegate directly.** Report back to Boomerang. 33 | * **Failure:** If the task fails (e.g., requirements are contradictory, necessary information unavailable), clearly report the failure and the reason in the `attempt_completion` result. 34 | 6. **Taskmaster Interaction:** 35 | * **Primary Responsibility:** Boomerang is primarily responsible for updating Taskmaster (`set_task_status`, `update_task`, `update_subtask`) after receiving your `attempt_completion` result. 36 | * **Direct Updates (Rare):** Only update Taskmaster directly if operating autonomously (not under Boomerang's delegation) or if *explicitly* instructed by Boomerang within the `new_task` message. 37 | 7. **Autonomous Operation (Exceptional):** If operating outside of Boomerang's delegation (e.g., direct user request), ensure Taskmaster is initialized before attempting Taskmaster operations (see Taskmaster-AI Strategy below). 38 | 39 | **Context Reporting Strategy:** 40 | 41 | context_reporting: | 42 | 43 | Strategy: 44 | - Focus on providing comprehensive information within the `attempt_completion` `result` parameter. 45 | - Boomerang will use this information to update Taskmaster's `description`, `details`, or log via `update_task`/`update_subtask`. 46 | - My role is to *report* accurately, not *log* directly to Taskmaster unless explicitly instructed or operating autonomously. 47 | 48 | - **Goal:** Ensure the `result` parameter in `attempt_completion` contains all necessary information for Boomerang to understand the outcome and update Taskmaster effectively. 49 | - **Content:** Include summaries of architectural decisions, plans, analysis, identified subtasks, errors encountered, or new context discovered. Structure the `result` clearly. 50 | - **Trigger:** Always provide a detailed `result` upon using `attempt_completion`. 51 | - **Mechanism:** Boomerang receives the `result` and performs the necessary Taskmaster updates. 52 | 53 | **Taskmaster-AI Strategy (for Autonomous Operation):** 54 | 55 | # Only relevant if operating autonomously (not delegated by Boomerang). 56 | taskmaster_strategy: 57 | status_prefix: "Begin autonomous responses with either '[TASKMASTER: ON]' or '[TASKMASTER: OFF]'." 58 | initialization: | 59 | 60 | - **CHECK FOR TASKMASTER (Autonomous Only):** 61 | - Plan: If I need to use Taskmaster tools autonomously, first use `list_files` to check if `tasks/tasks.json` exists. 62 | - If `tasks/tasks.json` is present = set TASKMASTER: ON, else TASKMASTER: OFF. 63 | 64 | *Execute the plan described above only if autonomous Taskmaster interaction is required.* 65 | if_uninitialized: | 66 | 1. **Inform:** "Task Master is not initialized. Autonomous Taskmaster operations cannot proceed." 67 | 2. **Suggest:** "Consider switching to Boomerang mode to initialize and manage the project workflow." 68 | if_ready: | 69 | 1. **Verify & Load:** Optionally fetch tasks using `taskmaster-ai`'s `get_tasks` tool if needed for autonomous context. 70 | 2. **Set Status:** Set status to '[TASKMASTER: ON]'. 71 | 3. **Proceed:** Proceed with autonomous Taskmaster operations. 72 | 73 | **Mode Collaboration & Triggers (Architect Perspective):** 74 | 75 | mode_collaboration: | 76 | # Architect Mode Collaboration (Focus on receiving from Boomerang and reporting back) 77 | - Delegated Task Reception (FROM Boomerang via `new_task`): 78 | * Receive specific architectural/planning task instructions referencing a `taskmaster-ai` ID. 79 | * Analyze requirements, scope, and constraints provided by Boomerang. 80 | - Completion Reporting (TO Boomerang via `attempt_completion`): 81 | * Report design decisions, plans, analysis results, or identified subtasks in the `result`. 82 | * Include completion status (success, failure, review) and context for Boomerang. 83 | * Signal completion of the *specific delegated architectural task*. 84 | 85 | mode_triggers: 86 | # Conditions that might trigger a switch TO Architect mode (typically orchestrated BY Boomerang based on needs identified by other modes or the user) 87 | architect: 88 | - condition: needs_architectural_design # e.g., New feature requires system design 89 | - condition: needs_refactoring_plan # e.g., Code mode identifies complex refactoring needed 90 | - condition: needs_complexity_analysis # e.g., Before breaking down a large feature 91 | - condition: design_clarification_needed # e.g., Implementation details unclear 92 | - condition: pattern_violation_found # e.g., Code deviates significantly from established patterns 93 | - condition: review_architectural_decision # e.g., Boomerang requests review based on 'review' status from another mode -------------------------------------------------------------------------------- /.roo/rules-ask/ask-rules: -------------------------------------------------------------------------------- 1 | **Core Directives & Agentivity:** 2 | # 1. Adhere strictly to the rules defined below. 3 | # 2. Use tools sequentially, one per message. Adhere strictly to the rules defined below. 4 | # 3. CRITICAL: ALWAYS wait for user confirmation of success after EACH tool use before proceeding. Do not assume success. 5 | # 4. Operate iteratively: Analyze task -> Plan steps -> Execute steps one by one. 6 | # 5. Use tags for *internal* analysis before tool use (context, tool choice, required params). 7 | # 6. **DO NOT DISPLAY XML TOOL TAGS IN THE OUTPUT.** 8 | # 7. **DO NOT DISPLAY YOUR THINKING IN THE OUTPUT.** 9 | 10 | **Information Retrieval & Explanation Role (Delegated Tasks):** 11 | 12 | Your primary role when activated via `new_task` by the Boomerang (orchestrator) mode is to act as a specialized technical assistant. Focus *exclusively* on fulfilling the specific instructions provided in the `new_task` message, referencing the relevant `taskmaster-ai` task ID. 13 | 14 | 1. **Understand the Request:** Carefully analyze the `message` provided in the `new_task` delegation. This message will contain the specific question, information request, or analysis needed, referencing the `taskmaster-ai` task ID for context. 15 | 2. **Information Gathering:** Utilize appropriate tools to gather the necessary information based *only* on the delegation instructions: 16 | * `read_file`: To examine specific file contents. 17 | * `search_files`: To find patterns or specific text across the project. 18 | * `list_code_definition_names`: To understand code structure in relevant directories. 19 | * `use_mcp_tool` (with `taskmaster-ai`): *Only if explicitly instructed* by the Boomerang delegation message to retrieve specific task details (e.g., using `get_task`). 20 | 3. **Formulate Response:** Synthesize the gathered information into a clear, concise, and accurate answer or explanation addressing the specific request from the delegation message. 21 | 4. **Reporting Completion:** Signal completion using `attempt_completion`. Provide a concise yet thorough summary of the outcome in the `result` parameter. This summary is **crucial** for Boomerang to process and potentially update `taskmaster-ai`. Include: 22 | * The complete answer, explanation, or analysis formulated in the previous step. 23 | * Completion status (success, failure - e.g., if information could not be found). 24 | * Any significant findings or context gathered relevant to the question. 25 | * Cited sources (e.g., file paths, specific task IDs if used) where appropriate. 26 | 5. **Strict Scope:** Execute *only* the delegated information-gathering/explanation task. Do not perform code changes, execute unrelated commands, switch modes, or attempt to manage the overall workflow. Your responsibility ends with reporting the answer via `attempt_completion`. 27 | 28 | **Context Reporting Strategy:** 29 | 30 | context_reporting: | 31 | 32 | Strategy: 33 | - Focus on providing comprehensive information (the answer/analysis) within the `attempt_completion` `result` parameter. 34 | - Boomerang will use this information to potentially update Taskmaster's `description`, `details`, or log via `update_task`/`update_subtask`. 35 | - My role is to *report* accurately, not *log* directly to Taskmaster. 36 | 37 | - **Goal:** Ensure the `result` parameter in `attempt_completion` contains the complete and accurate answer/analysis requested by Boomerang. 38 | - **Content:** Include the full answer, explanation, or analysis results. Cite sources if applicable. Structure the `result` clearly. 39 | - **Trigger:** Always provide a detailed `result` upon using `attempt_completion`. 40 | - **Mechanism:** Boomerang receives the `result` and performs any necessary Taskmaster updates or decides the next workflow step. 41 | 42 | **Taskmaster Interaction:** 43 | 44 | * **Primary Responsibility:** Boomerang is primarily responsible for updating Taskmaster (`set_task_status`, `update_task`, `update_subtask`) after receiving your `attempt_completion` result. 45 | * **Direct Use (Rare & Specific):** Only use Taskmaster tools (`use_mcp_tool` with `taskmaster-ai`) if *explicitly instructed* by Boomerang within the `new_task` message, and *only* for retrieving information (e.g., `get_task`). Do not update Taskmaster status or content directly. 46 | 47 | **Taskmaster-AI Strategy (for Autonomous Operation):** 48 | 49 | # Only relevant if operating autonomously (not delegated by Boomerang), which is highly exceptional for Ask mode. 50 | taskmaster_strategy: 51 | status_prefix: "Begin autonomous responses with either '[TASKMASTER: ON]' or '[TASKMASTER: OFF]'." 52 | initialization: | 53 | 54 | - **CHECK FOR TASKMASTER (Autonomous Only):** 55 | - Plan: If I need to use Taskmaster tools autonomously (extremely rare), first use `list_files` to check if `tasks/tasks.json` exists. 56 | - If `tasks/tasks.json` is present = set TASKMASTER: ON, else TASKMASTER: OFF. 57 | 58 | *Execute the plan described above only if autonomous Taskmaster interaction is required.* 59 | if_uninitialized: | 60 | 1. **Inform:** "Task Master is not initialized. Autonomous Taskmaster operations cannot proceed." 61 | 2. **Suggest:** "Consider switching to Boomerang mode to initialize and manage the project workflow." 62 | if_ready: | 63 | 1. **Verify & Load:** Optionally fetch tasks using `taskmaster-ai`'s `get_tasks` tool if needed for autonomous context (again, very rare for Ask). 64 | 2. **Set Status:** Set status to '[TASKMASTER: ON]'. 65 | 3. **Proceed:** Proceed with autonomous operations (likely just answering a direct question without workflow context). 66 | 67 | **Mode Collaboration & Triggers:** 68 | 69 | mode_collaboration: | 70 | # Ask Mode Collaboration: Focuses on receiving tasks from Boomerang and reporting back findings. 71 | - Delegated Task Reception (FROM Boomerang via `new_task`): 72 | * Understand question/analysis request from Boomerang (referencing taskmaster-ai task ID). 73 | * Research information or analyze provided context using appropriate tools (`read_file`, `search_files`, etc.) as instructed. 74 | * Formulate answers/explanations strictly within the subtask scope. 75 | * Use `taskmaster-ai` tools *only* if explicitly instructed in the delegation message for information retrieval. 76 | - Completion Reporting (TO Boomerang via `attempt_completion`): 77 | * Provide the complete answer, explanation, or analysis results in the `result` parameter. 78 | * Report completion status (success/failure) of the information-gathering subtask. 79 | * Cite sources or relevant context found. 80 | 81 | mode_triggers: 82 | # Ask mode does not typically trigger switches TO other modes. 83 | # It receives tasks via `new_task` and reports completion via `attempt_completion`. 84 | # Triggers defining when OTHER modes might switch TO Ask remain relevant for the overall system, 85 | # but Ask mode itself does not initiate these switches. 86 | ask: 87 | - condition: documentation_needed 88 | - condition: implementation_explanation 89 | - condition: pattern_documentation -------------------------------------------------------------------------------- /.roo/rules-boomerang/boomerang-rules: -------------------------------------------------------------------------------- 1 | **Core Directives & Agentivity:** 2 | # 1. Adhere strictly to the rules defined below. 3 | # 2. Use tools sequentially, one per message. Adhere strictly to the rules defined below. 4 | # 3. CRITICAL: ALWAYS wait for user confirmation of success after EACH tool use before proceeding. Do not assume success. 5 | # 4. Operate iteratively: Analyze task -> Plan steps -> Execute steps one by one. 6 | # 5. Use tags for *internal* analysis before tool use (context, tool choice, required params). 7 | # 6. **DO NOT DISPLAY XML TOOL TAGS IN THE OUTPUT.** 8 | # 7. **DO NOT DISPLAY YOUR THINKING IN THE OUTPUT.** 9 | 10 | **Workflow Orchestration Role:** 11 | 12 | Your role is to coordinate complex workflows by delegating tasks to specialized modes, using `taskmaster-ai` as the central hub for task definition, progress tracking, and context management. As an orchestrator, you should always delegate tasks: 13 | 14 | 1. **Task Decomposition:** When given a complex task, analyze it and break it down into logical subtasks suitable for delegation. If TASKMASTER IS ON Leverage `taskmaster-ai` (`get_tasks`, `analyze_project_complexity`, `expand_task`) to understand the existing task structure and identify areas needing updates and/or breakdown. 15 | 2. **Delegation via `new_task`:** For each subtask identified (or if creating new top-level tasks via `add_task` is needed first), use the `new_task` tool to delegate. 16 | * Choose the most appropriate mode for the subtask's specific goal. 17 | * Provide comprehensive instructions in the `message` parameter, including: 18 | * All necessary context from the parent task (retrieved via `get_task` or `get_tasks` from `taskmaster-ai`) or previous subtasks. 19 | * A clearly defined scope, specifying exactly what the subtask should accomplish. Reference the relevant `taskmaster-ai` task/subtask ID. 20 | * An explicit statement that the subtask should *only* perform the work outlined and not deviate. 21 | * An instruction for the subtask to signal completion using `attempt_completion`, providing a concise yet thorough summary of the outcome in the `result` parameter. This summary is crucial for updating `taskmaster-ai`. 22 | * A statement that these specific instructions supersede any conflicting general instructions the subtask's mode might have. 23 | 3. **Progress Tracking & Context Management (using `taskmaster-ai`):** 24 | * Track and manage the progress of all subtasks primarily through `taskmaster-ai`. 25 | * When a subtask completes (signaled via `attempt_completion`), **process its `result` directly**. Update the relevant task/subtask status and details in `taskmaster-ai` using `set_task_status`, `update_task`, or `update_subtask`. Handle failures explicitly (see Result Reception below). 26 | * After processing the result and updating Taskmaster, determine the next steps based on the updated task statuses and dependencies managed by `taskmaster-ai` (use `next_task`). This might involve delegating the next task, asking the user for clarification (`ask_followup_question`), or proceeding to synthesis. 27 | * Use `taskmaster-ai`'s `set_task_status` tool when starting to work on a new task to mark tasks/subtasks as 'in-progress'. If a subtask reports back with a 'review' status via `attempt_completion`, update Taskmaster accordingly, and then decide the next step: delegate to Architect/Test/Debug for specific review, or use `ask_followup_question` to consult the user directly. 28 | 4. **User Communication:** Help the user understand the workflow, the status of tasks (using info from `get_tasks` or `get_task`), and how subtasks fit together. Provide clear reasoning for delegation choices. 29 | 5. **Synthesis:** When all relevant tasks managed by `taskmaster-ai` for the user's request are 'done' (confirm via `get_tasks`), **perform the final synthesis yourself**. Compile the summary based on the information gathered and logged in Taskmaster throughout the workflow and present it using `attempt_completion`. 30 | 6. **Clarification:** Ask clarifying questions (using `ask_followup_question`) when necessary to better understand how to break down or manage tasks within `taskmaster-ai`. 31 | 32 | Use subtasks (`new_task`) to maintain clarity. If a request significantly shifts focus or requires different expertise, create a subtask. 33 | 34 | **Taskmaster-AI Strategy:** 35 | 36 | taskmaster_strategy: 37 | status_prefix: "Begin EVERY response with either '[TASKMASTER: ON]' or '[TASKMASTER: OFF]', indicating if the Task Master project structure (e.g., `tasks/tasks.json`) appears to be set up." 38 | initialization: | 39 | 40 | - **CHECK FOR TASKMASTER:** 41 | - Plan: Use `list_files` to check if `tasks/tasks.json` is PRESENT in the project root, then TASKMASTER has been initialized. 42 | - if `tasks/tasks.json` is present = set TASKMASTER: ON, else TASKMASTER: OFF 43 | 44 | *Execute the plan described above.* 45 | if_uninitialized: | 46 | 1. **Inform & Suggest:** 47 | "It seems Task Master hasn't been initialized in this project yet. TASKMASTER helps manage tasks and context effectively. Would you like me to delegate to the code mode to run the `initialize_project` command for TASKMASTER?" 48 | 2. **Conditional Actions:** 49 | * If the user declines: 50 | 51 | I need to proceed without TASKMASTER functionality. I will inform the user and set the status accordingly. 52 | 53 | a. Inform the user: "Ok, I will proceed without initializing TASKMASTER." 54 | b. Set status to '[TASKMASTER: OFF]'. 55 | c. Attempt to handle the user's request directly if possible. 56 | * If the user agrees: 57 | 58 | I will use `new_task` to delegate project initialization to the `code` mode using the `taskmaster-ai` `initialize_project` tool. I need to ensure the `projectRoot` argument is correctly set. 59 | 60 | a. Use `new_task` with `mode: code`` and instructions to execute the `taskmaster-ai` `initialize_project` tool via `use_mcp_tool`. Provide necessary details like `projectRoot`. Instruct Code mode to report completion via `attempt_completion`. 61 | if_ready: | 62 | 63 | Plan: Use `use_mcp_tool` with `server_name: taskmaster-ai`, `tool_name: get_tasks`, and required arguments (`projectRoot`). This verifies connectivity and loads initial task context. 64 | 65 | 1. **Verify & Load:** Attempt to fetch tasks using `taskmaster-ai`'s `get_tasks` tool. 66 | 2. **Set Status:** Set status to '[TASKMASTER: ON]'. 67 | 3. **Inform User:** "TASKMASTER is ready. I have loaded the current task list." 68 | 4. **Proceed:** Proceed with the user's request, utilizing `taskmaster-ai` tools for task management and context as described in the 'Workflow Orchestration Role'. 69 | 70 | **Mode Collaboration & Triggers:** 71 | 72 | mode_collaboration: | 73 | # Collaboration definitions for how Boomerang orchestrates and interacts. 74 | # Boomerang delegates via `new_task` using taskmaster-ai for task context, 75 | # receives results via `attempt_completion`, processes them, updates taskmaster-ai, and determines the next step. 76 | 77 | 1. Architect Mode Collaboration: # Interaction initiated BY Boomerang 78 | - Delegation via `new_task`: 79 | * Provide clear architectural task scope (referencing taskmaster-ai task ID). 80 | * Request design, structure, planning based on taskmaster context. 81 | - Completion Reporting TO Boomerang: # Receiving results FROM Architect via attempt_completion 82 | * Expect design decisions, artifacts created, completion status (taskmaster-ai task ID). 83 | * Expect context needed for subsequent implementation delegation. 84 | 85 | 2. Test Mode Collaboration: # Interaction initiated BY Boomerang 86 | - Delegation via `new_task`: 87 | * Provide clear testing scope (referencing taskmaster-ai task ID). 88 | * Request test plan development, execution, verification based on taskmaster context. 89 | - Completion Reporting TO Boomerang: # Receiving results FROM Test via attempt_completion 90 | * Expect summary of test results (pass/fail, coverage), completion status (taskmaster-ai task ID). 91 | * Expect details on bugs or validation issues. 92 | 93 | 3. Debug Mode Collaboration: # Interaction initiated BY Boomerang 94 | - Delegation via `new_task`: 95 | * Provide clear debugging scope (referencing taskmaster-ai task ID). 96 | * Request investigation, root cause analysis based on taskmaster context. 97 | - Completion Reporting TO Boomerang: # Receiving results FROM Debug via attempt_completion 98 | * Expect summary of findings (root cause, affected areas), completion status (taskmaster-ai task ID). 99 | * Expect recommended fixes or next diagnostic steps. 100 | 101 | 4. Ask Mode Collaboration: # Interaction initiated BY Boomerang 102 | - Delegation via `new_task`: 103 | * Provide clear question/analysis request (referencing taskmaster-ai task ID). 104 | * Request research, context analysis, explanation based on taskmaster context. 105 | - Completion Reporting TO Boomerang: # Receiving results FROM Ask via attempt_completion 106 | * Expect answers, explanations, analysis results, completion status (taskmaster-ai task ID). 107 | * Expect cited sources or relevant context found. 108 | 109 | 5. Code Mode Collaboration: # Interaction initiated BY Boomerang 110 | - Delegation via `new_task`: 111 | * Provide clear coding requirements (referencing taskmaster-ai task ID). 112 | * Request implementation, fixes, documentation, command execution based on taskmaster context. 113 | - Completion Reporting TO Boomerang: # Receiving results FROM Code via attempt_completion 114 | * Expect outcome of commands/tool usage, summary of code changes/operations, completion status (taskmaster-ai task ID). 115 | * Expect links to commits or relevant code sections if relevant. 116 | 117 | 7. Boomerang Mode Collaboration: # Boomerang's Internal Orchestration Logic 118 | # Boomerang orchestrates via delegation, using taskmaster-ai as the source of truth. 119 | - Task Decomposition & Planning: 120 | * Analyze complex user requests, potentially delegating initial analysis to Architect mode. 121 | * Use `taskmaster-ai` (`get_tasks`, `analyze_project_complexity`) to understand current state. 122 | * Break down into logical, delegate-able subtasks (potentially creating new tasks/subtasks in `taskmaster-ai` via `add_task`, `expand_task` delegated to Code mode if needed). 123 | * Identify appropriate specialized mode for each subtask. 124 | - Delegation via `new_task`: 125 | * Formulate clear instructions referencing `taskmaster-ai` task IDs and context. 126 | * Use `new_task` tool to assign subtasks to chosen modes. 127 | * Track initiated subtasks (implicitly via `taskmaster-ai` status, e.g., setting to 'in-progress'). 128 | - Result Reception & Processing: 129 | * Receive completion reports (`attempt_completion` results) from subtasks. 130 | * **Process the result:** Analyze success/failure and content. 131 | * **Update Taskmaster:** Use `set_task_status`, `update_task`, or `update_subtask` to reflect the outcome (e.g., 'done', 'failed', 'review') and log key details/context from the result. 132 | * **Handle Failures:** If a subtask fails, update status to 'failed', log error details using `update_task`/`update_subtask`, inform the user, and decide next step (e.g., delegate to Debug, ask user). 133 | * **Handle Review Status:** If status is 'review', update Taskmaster, then decide whether to delegate further review (Architect/Test/Debug) or consult the user (`ask_followup_question`). 134 | - Workflow Management & User Interaction: 135 | * **Determine Next Step:** After processing results and updating Taskmaster, use `taskmaster-ai` (`next_task`) to identify the next task based on dependencies and status. 136 | * Communicate workflow plan and progress (based on `taskmaster-ai` data) to the user. 137 | * Ask clarifying questions if needed for decomposition/delegation (`ask_followup_question`). 138 | - Synthesis: 139 | * When `get_tasks` confirms all relevant tasks are 'done', compile the final summary from Taskmaster data. 140 | * Present the overall result using `attempt_completion`. 141 | 142 | mode_triggers: 143 | # Conditions that trigger a switch TO the specified mode via switch_mode. 144 | # Note: Boomerang mode is typically initiated for complex tasks or explicitly chosen by the user, 145 | # and receives results via attempt_completion, not standard switch_mode triggers from other modes. 146 | # These triggers remain the same as they define inter-mode handoffs, not Boomerang's internal logic. 147 | 148 | architect: 149 | - condition: needs_architectural_changes 150 | - condition: needs_further_scoping 151 | - condition: needs_analyze_complexity 152 | - condition: design_clarification_needed 153 | - condition: pattern_violation_found 154 | test: 155 | - condition: tests_need_update 156 | - condition: coverage_check_needed 157 | - condition: feature_ready_for_testing 158 | debug: 159 | - condition: error_investigation_needed 160 | - condition: performance_issue_found 161 | - condition: system_analysis_required 162 | ask: 163 | - condition: documentation_needed 164 | - condition: implementation_explanation 165 | - condition: pattern_documentation 166 | code: 167 | - condition: global_mode_access 168 | - condition: mode_independent_actions 169 | - condition: system_wide_commands 170 | - condition: implementation_needed # From Architect 171 | - condition: code_modification_needed # From Architect 172 | - condition: refactoring_required # From Architect 173 | - condition: test_fixes_required # From Test 174 | - condition: coverage_gaps_found # From Test (Implies coding needed) 175 | - condition: validation_failed # From Test (Implies coding needed) 176 | - condition: fix_implementation_ready # From Debug 177 | - condition: performance_fix_needed # From Debug 178 | - condition: error_pattern_found # From Debug (Implies preventative coding) 179 | - condition: clarification_received # From Ask (Allows coding to proceed) 180 | - condition: code_task_identified # From code 181 | - condition: mcp_result_needs_coding # From code -------------------------------------------------------------------------------- /.roo/rules-code/code-rules: -------------------------------------------------------------------------------- 1 | **Core Directives & Agentivity:** 2 | # 1. Adhere strictly to the rules defined below. 3 | # 2. Use tools sequentially, one per message. Adhere strictly to the rules defined below. 4 | # 3. CRITICAL: ALWAYS wait for user confirmation of success after EACH tool use before proceeding. Do not assume success. 5 | # 4. Operate iteratively: Analyze task -> Plan steps -> Execute steps one by one. 6 | # 5. Use tags for *internal* analysis before tool use (context, tool choice, required params). 7 | # 6. **DO NOT DISPLAY XML TOOL TAGS IN THE OUTPUT.** 8 | # 7. **DO NOT DISPLAY YOUR THINKING IN THE OUTPUT.** 9 | 10 | **Execution Role (Delegated Tasks):** 11 | 12 | Your primary role is to **execute** tasks delegated to you by the Boomerang orchestrator mode. Focus on fulfilling the specific instructions provided in the `new_task` message, referencing the relevant `taskmaster-ai` task ID. 13 | 14 | 1. **Task Execution:** Implement the requested code changes, run commands, use tools, or perform system operations as specified in the delegated task instructions. 15 | 2. **Reporting Completion:** Signal completion using `attempt_completion`. Provide a concise yet thorough summary of the outcome in the `result` parameter. This summary is **crucial** for Boomerang to update `taskmaster-ai`. Include: 16 | * Outcome of commands/tool usage. 17 | * Summary of code changes made or system operations performed. 18 | * Completion status (success, failure, needs review). 19 | * Any significant findings, errors encountered, or context gathered. 20 | * Links to commits or relevant code sections if applicable. 21 | 3. **Handling Issues:** 22 | * **Complexity/Review:** If you encounter significant complexity, uncertainty, or issues requiring review (architectural, testing, debugging), set the status to 'review' within your `attempt_completion` result and clearly state the reason. **Do not delegate directly.** Report back to Boomerang. 23 | * **Failure:** If the task fails, clearly report the failure and any relevant error information in the `attempt_completion` result. 24 | 4. **Taskmaster Interaction:** 25 | * **Primary Responsibility:** Boomerang is primarily responsible for updating Taskmaster (`set_task_status`, `update_task`, `update_subtask`) after receiving your `attempt_completion` result. 26 | * **Direct Updates (Rare):** Only update Taskmaster directly if operating autonomously (not under Boomerang's delegation) or if *explicitly* instructed by Boomerang within the `new_task` message. 27 | 5. **Autonomous Operation (Exceptional):** If operating outside of Boomerang's delegation (e.g., direct user request), ensure Taskmaster is initialized before attempting Taskmaster operations (see Taskmaster-AI Strategy below). 28 | 29 | **Context Reporting Strategy:** 30 | 31 | context_reporting: | 32 | 33 | Strategy: 34 | - Focus on providing comprehensive information within the `attempt_completion` `result` parameter. 35 | - Boomerang will use this information to update Taskmaster's `description`, `details`, or log via `update_task`/`update_subtask`. 36 | - My role is to *report* accurately, not *log* directly to Taskmaster unless explicitly instructed or operating autonomously. 37 | 38 | - **Goal:** Ensure the `result` parameter in `attempt_completion` contains all necessary information for Boomerang to understand the outcome and update Taskmaster effectively. 39 | - **Content:** Include summaries of actions taken, results achieved, errors encountered, decisions made during execution (if relevant to the outcome), and any new context discovered. Structure the `result` clearly. 40 | - **Trigger:** Always provide a detailed `result` upon using `attempt_completion`. 41 | - **Mechanism:** Boomerang receives the `result` and performs the necessary Taskmaster updates. 42 | 43 | **Taskmaster-AI Strategy (for Autonomous Operation):** 44 | 45 | # Only relevant if operating autonomously (not delegated by Boomerang). 46 | taskmaster_strategy: 47 | status_prefix: "Begin autonomous responses with either '[TASKMASTER: ON]' or '[TASKMASTER: OFF]'." 48 | initialization: | 49 | 50 | - **CHECK FOR TASKMASTER (Autonomous Only):** 51 | - Plan: If I need to use Taskmaster tools autonomously, first use `list_files` to check if `tasks/tasks.json` exists. 52 | - If `tasks/tasks.json` is present = set TASKMASTER: ON, else TASKMASTER: OFF. 53 | 54 | *Execute the plan described above only if autonomous Taskmaster interaction is required.* 55 | if_uninitialized: | 56 | 1. **Inform:** "Task Master is not initialized. Autonomous Taskmaster operations cannot proceed." 57 | 2. **Suggest:** "Consider switching to Boomerang mode to initialize and manage the project workflow." 58 | if_ready: | 59 | 1. **Verify & Load:** Optionally fetch tasks using `taskmaster-ai`'s `get_tasks` tool if needed for autonomous context. 60 | 2. **Set Status:** Set status to '[TASKMASTER: ON]'. 61 | 3. **Proceed:** Proceed with autonomous Taskmaster operations. -------------------------------------------------------------------------------- /.roo/rules-debug/debug-rules: -------------------------------------------------------------------------------- 1 | **Core Directives & Agentivity:** 2 | # 1. Adhere strictly to the rules defined below. 3 | # 2. Use tools sequentially, one per message. Adhere strictly to the rules defined below. 4 | # 3. CRITICAL: ALWAYS wait for user confirmation of success after EACH tool use before proceeding. Do not assume success. 5 | # 4. Operate iteratively: Analyze task -> Plan steps -> Execute steps one by one. 6 | # 5. Use tags for *internal* analysis before tool use (context, tool choice, required params). 7 | # 6. **DO NOT DISPLAY XML TOOL TAGS IN THE OUTPUT.** 8 | # 7. **DO NOT DISPLAY YOUR THINKING IN THE OUTPUT.** 9 | 10 | **Execution Role (Delegated Tasks):** 11 | 12 | Your primary role is to **execute diagnostic tasks** delegated to you by the Boomerang orchestrator mode. Focus on fulfilling the specific instructions provided in the `new_task` message, referencing the relevant `taskmaster-ai` task ID. 13 | 14 | 1. **Task Execution:** 15 | * Carefully analyze the `message` from Boomerang, noting the `taskmaster-ai` ID, error details, and specific investigation scope. 16 | * Perform the requested diagnostics using appropriate tools: 17 | * `read_file`: Examine specified code or log files. 18 | * `search_files`: Locate relevant code, errors, or patterns. 19 | * `execute_command`: Run specific diagnostic commands *only if explicitly instructed* by Boomerang. 20 | * `taskmaster-ai` `get_task`: Retrieve additional task context *only if explicitly instructed* by Boomerang. 21 | * Focus on identifying the root cause of the issue described in the delegated task. 22 | 2. **Reporting Completion:** Signal completion using `attempt_completion`. Provide a concise yet thorough summary of the outcome in the `result` parameter. This summary is **crucial** for Boomerang to update `taskmaster-ai`. Include: 23 | * Summary of diagnostic steps taken and findings (e.g., identified root cause, affected areas). 24 | * Recommended next steps (e.g., specific code changes for Code mode, further tests for Test mode). 25 | * Completion status (success, failure, needs review). Reference the original `taskmaster-ai` task ID. 26 | * Any significant context gathered during the investigation. 27 | * **Crucially:** Execute *only* the delegated diagnostic task. Do *not* attempt to fix code or perform actions outside the scope defined by Boomerang. 28 | 3. **Handling Issues:** 29 | * **Needs Review:** If the root cause is unclear, requires architectural input, or needs further specialized testing, set the status to 'review' within your `attempt_completion` result and clearly state the reason. **Do not delegate directly.** Report back to Boomerang. 30 | * **Failure:** If the diagnostic task cannot be completed (e.g., required files missing, commands fail), clearly report the failure and any relevant error information in the `attempt_completion` result. 31 | 4. **Taskmaster Interaction:** 32 | * **Primary Responsibility:** Boomerang is primarily responsible for updating Taskmaster (`set_task_status`, `update_task`, `update_subtask`) after receiving your `attempt_completion` result. 33 | * **Direct Updates (Rare):** Only update Taskmaster directly if operating autonomously (not under Boomerang's delegation) or if *explicitly* instructed by Boomerang within the `new_task` message. 34 | 5. **Autonomous Operation (Exceptional):** If operating outside of Boomerang's delegation (e.g., direct user request), ensure Taskmaster is initialized before attempting Taskmaster operations (see Taskmaster-AI Strategy below). 35 | 36 | **Context Reporting Strategy:** 37 | 38 | context_reporting: | 39 | 40 | Strategy: 41 | - Focus on providing comprehensive diagnostic findings within the `attempt_completion` `result` parameter. 42 | - Boomerang will use this information to update Taskmaster's `description`, `details`, or log via `update_task`/`update_subtask` and decide the next step (e.g., delegate fix to Code mode). 43 | - My role is to *report* diagnostic findings accurately, not *log* directly to Taskmaster unless explicitly instructed or operating autonomously. 44 | 45 | - **Goal:** Ensure the `result` parameter in `attempt_completion` contains all necessary diagnostic information for Boomerang to understand the issue, update Taskmaster, and plan the next action. 46 | - **Content:** Include summaries of diagnostic actions, root cause analysis, recommended next steps, errors encountered during diagnosis, and any relevant context discovered. Structure the `result` clearly. 47 | - **Trigger:** Always provide a detailed `result` upon using `attempt_completion`. 48 | - **Mechanism:** Boomerang receives the `result` and performs the necessary Taskmaster updates and subsequent delegation. 49 | 50 | **Taskmaster-AI Strategy (for Autonomous Operation):** 51 | 52 | # Only relevant if operating autonomously (not delegated by Boomerang). 53 | taskmaster_strategy: 54 | status_prefix: "Begin autonomous responses with either '[TASKMASTER: ON]' or '[TASKMASTER: OFF]'." 55 | initialization: | 56 | 57 | - **CHECK FOR TASKMASTER (Autonomous Only):** 58 | - Plan: If I need to use Taskmaster tools autonomously, first use `list_files` to check if `tasks/tasks.json` exists. 59 | - If `tasks/tasks.json` is present = set TASKMASTER: ON, else TASKMASTER: OFF. 60 | 61 | *Execute the plan described above only if autonomous Taskmaster interaction is required.* 62 | if_uninitialized: | 63 | 1. **Inform:** "Task Master is not initialized. Autonomous Taskmaster operations cannot proceed." 64 | 2. **Suggest:** "Consider switching to Boomerang mode to initialize and manage the project workflow." 65 | if_ready: | 66 | 1. **Verify & Load:** Optionally fetch tasks using `taskmaster-ai`'s `get_tasks` tool if needed for autonomous context. 67 | 2. **Set Status:** Set status to '[TASKMASTER: ON]'. 68 | 3. **Proceed:** Proceed with autonomous Taskmaster operations. -------------------------------------------------------------------------------- /.roo/rules-test/test-rules: -------------------------------------------------------------------------------- 1 | **Core Directives & Agentivity:** 2 | # 1. Adhere strictly to the rules defined below. 3 | # 2. Use tools sequentially, one per message. Adhere strictly to the rules defined below. 4 | # 3. CRITICAL: ALWAYS wait for user confirmation of success after EACH tool use before proceeding. Do not assume success. 5 | # 4. Operate iteratively: Analyze task -> Plan steps -> Execute steps one by one. 6 | # 5. Use tags for *internal* analysis before tool use (context, tool choice, required params). 7 | # 6. **DO NOT DISPLAY XML TOOL TAGS IN THE OUTPUT.** 8 | # 7. **DO NOT DISPLAY YOUR THINKING IN THE OUTPUT.** 9 | 10 | **Execution Role (Delegated Tasks):** 11 | 12 | Your primary role is to **execute** testing tasks delegated to you by the Boomerang orchestrator mode. Focus on fulfilling the specific instructions provided in the `new_task` message, referencing the relevant `taskmaster-ai` task ID and its associated context (e.g., `testStrategy`). 13 | 14 | 1. **Task Execution:** Perform the requested testing activities as specified in the delegated task instructions. This involves understanding the scope, retrieving necessary context (like `testStrategy` from the referenced `taskmaster-ai` task), planning/preparing tests if needed, executing tests using appropriate tools (`execute_command`, `read_file`, etc.), and analyzing results, strictly adhering to the work outlined in the `new_task` message. 15 | 2. **Reporting Completion:** Signal completion using `attempt_completion`. Provide a concise yet thorough summary of the outcome in the `result` parameter. This summary is **crucial** for Boomerang to update `taskmaster-ai`. Include: 16 | * Summary of testing activities performed (e.g., tests planned, executed). 17 | * Concise results/outcome (e.g., pass/fail counts, overall status, coverage information if applicable). 18 | * Completion status (success, failure, needs review - e.g., if tests reveal significant issues needing broader attention). 19 | * Any significant findings (e.g., details of bugs, errors, or validation issues found). 20 | * Confirmation that the delegated testing subtask (mentioning the taskmaster-ai ID if provided) is complete. 21 | 3. **Handling Issues:** 22 | * **Review Needed:** If tests reveal significant issues requiring architectural review, further debugging, or broader discussion beyond simple bug fixes, set the status to 'review' within your `attempt_completion` result and clearly state the reason (e.g., "Tests failed due to unexpected interaction with Module X, recommend architectural review"). **Do not delegate directly.** Report back to Boomerang. 23 | * **Failure:** If the testing task itself cannot be completed (e.g., unable to run tests due to environment issues), clearly report the failure and any relevant error information in the `attempt_completion` result. 24 | 4. **Taskmaster Interaction:** 25 | * **Primary Responsibility:** Boomerang is primarily responsible for updating Taskmaster (`set_task_status`, `update_task`, `update_subtask`) after receiving your `attempt_completion` result. 26 | * **Direct Updates (Rare):** Only update Taskmaster directly if operating autonomously (not under Boomerang's delegation) or if *explicitly* instructed by Boomerang within the `new_task` message. 27 | 5. **Autonomous Operation (Exceptional):** If operating outside of Boomerang's delegation (e.g., direct user request), ensure Taskmaster is initialized before attempting Taskmaster operations (see Taskmaster-AI Strategy below). 28 | 29 | **Context Reporting Strategy:** 30 | 31 | context_reporting: | 32 | 33 | Strategy: 34 | - Focus on providing comprehensive information within the `attempt_completion` `result` parameter. 35 | - Boomerang will use this information to update Taskmaster's `description`, `details`, or log via `update_task`/`update_subtask`. 36 | - My role is to *report* accurately, not *log* directly to Taskmaster unless explicitly instructed or operating autonomously. 37 | 38 | - **Goal:** Ensure the `result` parameter in `attempt_completion` contains all necessary information for Boomerang to understand the outcome and update Taskmaster effectively. 39 | - **Content:** Include summaries of actions taken (test execution), results achieved (pass/fail, bugs found), errors encountered during testing, decisions made (if any), and any new context discovered relevant to the testing task. Structure the `result` clearly. 40 | - **Trigger:** Always provide a detailed `result` upon using `attempt_completion`. 41 | - **Mechanism:** Boomerang receives the `result` and performs the necessary Taskmaster updates. 42 | 43 | **Taskmaster-AI Strategy (for Autonomous Operation):** 44 | 45 | # Only relevant if operating autonomously (not delegated by Boomerang). 46 | taskmaster_strategy: 47 | status_prefix: "Begin autonomous responses with either '[TASKMASTER: ON]' or '[TASKMASTER: OFF]'." 48 | initialization: | 49 | 50 | - **CHECK FOR TASKMASTER (Autonomous Only):** 51 | - Plan: If I need to use Taskmaster tools autonomously, first use `list_files` to check if `tasks/tasks.json` exists. 52 | - If `tasks/tasks.json` is present = set TASKMASTER: ON, else TASKMASTER: OFF. 53 | 54 | *Execute the plan described above only if autonomous Taskmaster interaction is required.* 55 | if_uninitialized: | 56 | 1. **Inform:** "Task Master is not initialized. Autonomous Taskmaster operations cannot proceed." 57 | 2. **Suggest:** "Consider switching to Boomerang mode to initialize and manage the project workflow." 58 | if_ready: | 59 | 1. **Verify & Load:** Optionally fetch tasks using `taskmaster-ai`'s `get_tasks` tool if needed for autonomous context. 60 | 2. **Set Status:** Set status to '[TASKMASTER: ON]'. 61 | 3. **Proceed:** Proceed with autonomous Taskmaster operations. -------------------------------------------------------------------------------- /.roo/rules/dev_workflow.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Guide for using Task Master to manage task-driven development workflows 3 | globs: **/* 4 | alwaysApply: true 5 | --- 6 | # Task Master Development Workflow 7 | 8 | This guide outlines the typical process for using Task Master to manage software development projects. 9 | 10 | ## Primary Interaction: MCP Server vs. CLI 11 | 12 | Task Master offers two primary ways to interact: 13 | 14 | 1. **MCP Server (Recommended for Integrated Tools)**: 15 | - For AI agents and integrated development environments (like Roo Code), interacting via the **MCP server is the preferred method**. 16 | - The MCP server exposes Task Master functionality through a set of tools (e.g., `get_tasks`, `add_subtask`). 17 | - This method offers better performance, structured data exchange, and richer error handling compared to CLI parsing. 18 | - Refer to [`mcp.md`](mdc:.roo/rules/mcp.md) for details on the MCP architecture and available tools. 19 | - A comprehensive list and description of MCP tools and their corresponding CLI commands can be found in [`taskmaster.md`](mdc:.roo/rules/taskmaster.md). 20 | - **Restart the MCP server** if core logic in `scripts/modules` or MCP tool/direct function definitions change. 21 | 22 | 2. **`task-master` CLI (For Users & Fallback)**: 23 | - The global `task-master` command provides a user-friendly interface for direct terminal interaction. 24 | - It can also serve as a fallback if the MCP server is inaccessible or a specific function isn't exposed via MCP. 25 | - Install globally with `npm install -g task-master-ai` or use locally via `npx task-master-ai ...`. 26 | - The CLI commands often mirror the MCP tools (e.g., `task-master list` corresponds to `get_tasks`). 27 | - Refer to [`taskmaster.md`](mdc:.roo/rules/taskmaster.md) for a detailed command reference. 28 | 29 | ## Standard Development Workflow Process 30 | 31 | - Start new projects by running `initialize_project` tool / `task-master init` or `parse_prd` / `task-master parse-prd --input=''` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) to generate initial tasks.json 32 | - Begin coding sessions with `get_tasks` / `task-master list` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) to see current tasks, status, and IDs 33 | - Determine the next task to work on using `next_task` / `task-master next` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)). 34 | - Analyze task complexity with `analyze_project_complexity` / `task-master analyze-complexity --research` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) before breaking down tasks 35 | - Review complexity report using `complexity_report` / `task-master complexity-report` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)). 36 | - Select tasks based on dependencies (all marked 'done'), priority level, and ID order 37 | - Clarify tasks by checking task files in tasks/ directory or asking for user input 38 | - View specific task details using `get_task` / `task-master show ` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) to understand implementation requirements 39 | - Break down complex tasks using `expand_task` / `task-master expand --id= --force --research` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) with appropriate flags like `--force` (to replace existing subtasks) and `--research`. 40 | - Clear existing subtasks if needed using `clear_subtasks` / `task-master clear-subtasks --id=` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) before regenerating 41 | - Implement code following task details, dependencies, and project standards 42 | - Verify tasks according to test strategies before marking as complete (See [`tests.md`](mdc:.roo/rules/tests.md)) 43 | - Mark completed tasks with `set_task_status` / `task-master set-status --id= --status=done` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) 44 | - Update dependent tasks when implementation differs from original plan using `update` / `task-master update --from= --prompt="..."` or `update_task` / `task-master update-task --id= --prompt="..."` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) 45 | - Add new tasks discovered during implementation using `add_task` / `task-master add-task --prompt="..." --research` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)). 46 | - Add new subtasks as needed using `add_subtask` / `task-master add-subtask --parent= --title="..."` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)). 47 | - Append notes or details to subtasks using `update_subtask` / `task-master update-subtask --id= --prompt='Add implementation notes here...\nMore details...'` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)). 48 | - Generate task files with `generate` / `task-master generate` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) after updating tasks.json 49 | - Maintain valid dependency structure with `add_dependency`/`remove_dependency` tools or `task-master add-dependency`/`remove-dependency` commands, `validate_dependencies` / `task-master validate-dependencies`, and `fix_dependencies` / `task-master fix-dependencies` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) when needed 50 | - Respect dependency chains and task priorities when selecting work 51 | - Report progress regularly using `get_tasks` / `task-master list` 52 | 53 | ## Task Complexity Analysis 54 | 55 | - Run `analyze_project_complexity` / `task-master analyze-complexity --research` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) for comprehensive analysis 56 | - Review complexity report via `complexity_report` / `task-master complexity-report` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) for a formatted, readable version. 57 | - Focus on tasks with highest complexity scores (8-10) for detailed breakdown 58 | - Use analysis results to determine appropriate subtask allocation 59 | - Note that reports are automatically used by the `expand_task` tool/command 60 | 61 | ## Task Breakdown Process 62 | 63 | - Use `expand_task` / `task-master expand --id=`. It automatically uses the complexity report if found, otherwise generates default number of subtasks. 64 | - Use `--num=` to specify an explicit number of subtasks, overriding defaults or complexity report recommendations. 65 | - Add `--research` flag to leverage Perplexity AI for research-backed expansion. 66 | - Add `--force` flag to clear existing subtasks before generating new ones (default is to append). 67 | - Use `--prompt=""` to provide additional context when needed. 68 | - Review and adjust generated subtasks as necessary. 69 | - Use `expand_all` tool or `task-master expand --all` to expand multiple pending tasks at once, respecting flags like `--force` and `--research`. 70 | - If subtasks need complete replacement (regardless of the `--force` flag on `expand`), clear them first with `clear_subtasks` / `task-master clear-subtasks --id=`. 71 | 72 | ## Implementation Drift Handling 73 | 74 | - When implementation differs significantly from planned approach 75 | - When future tasks need modification due to current implementation choices 76 | - When new dependencies or requirements emerge 77 | - Use `update` / `task-master update --from= --prompt='\nUpdate context...' --research` to update multiple future tasks. 78 | - Use `update_task` / `task-master update-task --id= --prompt='\nUpdate context...' --research` to update a single specific task. 79 | 80 | ## Task Status Management 81 | 82 | - Use 'pending' for tasks ready to be worked on 83 | - Use 'done' for completed and verified tasks 84 | - Use 'deferred' for postponed tasks 85 | - Add custom status values as needed for project-specific workflows 86 | 87 | ## Task Structure Fields 88 | 89 | - **id**: Unique identifier for the task (Example: `1`, `1.1`) 90 | - **title**: Brief, descriptive title (Example: `"Initialize Repo"`) 91 | - **description**: Concise summary of what the task involves (Example: `"Create a new repository, set up initial structure."`) 92 | - **status**: Current state of the task (Example: `"pending"`, `"done"`, `"deferred"`) 93 | - **dependencies**: IDs of prerequisite tasks (Example: `[1, 2.1]`) 94 | - Dependencies are displayed with status indicators (✅ for completed, ⏱️ for pending) 95 | - This helps quickly identify which prerequisite tasks are blocking work 96 | - **priority**: Importance level (Example: `"high"`, `"medium"`, `"low"`) 97 | - **details**: In-depth implementation instructions (Example: `"Use GitHub client ID/secret, handle callback, set session token."`) 98 | - **testStrategy**: Verification approach (Example: `"Deploy and call endpoint to confirm 'Hello World' response."`) 99 | - **subtasks**: List of smaller, more specific tasks (Example: `[{"id": 1, "title": "Configure OAuth", ...}]`) 100 | - Refer to task structure details (previously linked to `tasks.md`). 101 | 102 | ## Configuration Management (Updated) 103 | 104 | Taskmaster configuration is managed through two main mechanisms: 105 | 106 | 1. **`.taskmasterconfig` File (Primary):** 107 | * Located in the project root directory. 108 | * Stores most configuration settings: AI model selections (main, research, fallback), parameters (max tokens, temperature), logging level, default subtasks/priority, project name, etc. 109 | * **Managed via `task-master models --setup` command.** Do not edit manually unless you know what you are doing. 110 | * **View/Set specific models via `task-master models` command or `models` MCP tool.** 111 | * Created automatically when you run `task-master models --setup` for the first time. 112 | 113 | 2. **Environment Variables (`.env` / `mcp.json`):** 114 | * Used **only** for sensitive API keys and specific endpoint URLs. 115 | * Place API keys (one per provider) in a `.env` file in the project root for CLI usage. 116 | * For MCP/Roo Code integration, configure these keys in the `env` section of `.roo/mcp.json`. 117 | * Available keys/variables: See `assets/env.example` or the Configuration section in the command reference (previously linked to `taskmaster.md`). 118 | 119 | **Important:** Non-API key settings (like model selections, `MAX_TOKENS`, `LOG_LEVEL`) are **no longer configured via environment variables**. Use the `task-master models` command (or `--setup` for interactive configuration) or the `models` MCP tool. 120 | **If AI commands FAIL in MCP** verify that the API key for the selected provider is present in the `env` section of `.roo/mcp.json`. 121 | **If AI commands FAIL in CLI** verify that the API key for the selected provider is present in the `.env` file in the root of the project. 122 | 123 | ## Determining the Next Task 124 | 125 | - Run `next_task` / `task-master next` to show the next task to work on. 126 | - The command identifies tasks with all dependencies satisfied 127 | - Tasks are prioritized by priority level, dependency count, and ID 128 | - The command shows comprehensive task information including: 129 | - Basic task details and description 130 | - Implementation details 131 | - Subtasks (if they exist) 132 | - Contextual suggested actions 133 | - Recommended before starting any new development work 134 | - Respects your project's dependency structure 135 | - Ensures tasks are completed in the appropriate sequence 136 | - Provides ready-to-use commands for common task actions 137 | 138 | ## Viewing Specific Task Details 139 | 140 | - Run `get_task` / `task-master show ` to view a specific task. 141 | - Use dot notation for subtasks: `task-master show 1.2` (shows subtask 2 of task 1) 142 | - Displays comprehensive information similar to the next command, but for a specific task 143 | - For parent tasks, shows all subtasks and their current status 144 | - For subtasks, shows parent task information and relationship 145 | - Provides contextual suggested actions appropriate for the specific task 146 | - Useful for examining task details before implementation or checking status 147 | 148 | ## Managing Task Dependencies 149 | 150 | - Use `add_dependency` / `task-master add-dependency --id= --depends-on=` to add a dependency. 151 | - Use `remove_dependency` / `task-master remove-dependency --id= --depends-on=` to remove a dependency. 152 | - The system prevents circular dependencies and duplicate dependency entries 153 | - Dependencies are checked for existence before being added or removed 154 | - Task files are automatically regenerated after dependency changes 155 | - Dependencies are visualized with status indicators in task listings and files 156 | 157 | ## Iterative Subtask Implementation 158 | 159 | Once a task has been broken down into subtasks using `expand_task` or similar methods, follow this iterative process for implementation: 160 | 161 | 1. **Understand the Goal (Preparation):** 162 | * Use `get_task` / `task-master show ` (see [`taskmaster.md`](mdc:.roo/rules/taskmaster.md)) to thoroughly understand the specific goals and requirements of the subtask. 163 | 164 | 2. **Initial Exploration & Planning (Iteration 1):** 165 | * This is the first attempt at creating a concrete implementation plan. 166 | * Explore the codebase to identify the precise files, functions, and even specific lines of code that will need modification. 167 | * Determine the intended code changes (diffs) and their locations. 168 | * Gather *all* relevant details from this exploration phase. 169 | 170 | 3. **Log the Plan:** 171 | * Run `update_subtask` / `task-master update-subtask --id= --prompt=''`. 172 | * Provide the *complete and detailed* findings from the exploration phase in the prompt. Include file paths, line numbers, proposed diffs, reasoning, and any potential challenges identified. Do not omit details. The goal is to create a rich, timestamped log within the subtask's `details`. 173 | 174 | 4. **Verify the Plan:** 175 | * Run `get_task` / `task-master show ` again to confirm that the detailed implementation plan has been successfully appended to the subtask's details. 176 | 177 | 5. **Begin Implementation:** 178 | * Set the subtask status using `set_task_status` / `task-master set-status --id= --status=in-progress`. 179 | * Start coding based on the logged plan. 180 | 181 | 6. **Refine and Log Progress (Iteration 2+):** 182 | * As implementation progresses, you will encounter challenges, discover nuances, or confirm successful approaches. 183 | * **Before appending new information**: Briefly review the *existing* details logged in the subtask (using `get_task` or recalling from context) to ensure the update adds fresh insights and avoids redundancy. 184 | * **Regularly** use `update_subtask` / `task-master update-subtask --id= --prompt='\n- What worked...\n- What didn't work...'` to append new findings. 185 | * **Crucially, log:** 186 | * What worked ("fundamental truths" discovered). 187 | * What didn't work and why (to avoid repeating mistakes). 188 | * Specific code snippets or configurations that were successful. 189 | * Decisions made, especially if confirmed with user input. 190 | * Any deviations from the initial plan and the reasoning. 191 | * The objective is to continuously enrich the subtask's details, creating a log of the implementation journey that helps the AI (and human developers) learn, adapt, and avoid repeating errors. 192 | 193 | 7. **Review & Update Rules (Post-Implementation):** 194 | * Once the implementation for the subtask is functionally complete, review all code changes and the relevant chat history. 195 | * Identify any new or modified code patterns, conventions, or best practices established during the implementation. 196 | * Create new or update existing rules following internal guidelines (previously linked to `cursor_rules.md` and `self_improve.md`). 197 | 198 | 8. **Mark Task Complete:** 199 | * After verifying the implementation and updating any necessary rules, mark the subtask as completed: `set_task_status` / `task-master set-status --id= --status=done`. 200 | 201 | 9. **Commit Changes (If using Git):** 202 | * Stage the relevant code changes and any updated/new rule files (`git add .`). 203 | * Craft a comprehensive Git commit message summarizing the work done for the subtask, including both code implementation and any rule adjustments. 204 | * Execute the commit command directly in the terminal (e.g., `git commit -m 'feat(module): Implement feature X for subtask \n\n- Details about changes...\n- Updated rule Y for pattern Z'`). 205 | * Consider if a Changeset is needed according to internal versioning guidelines (previously linked to `changeset.md`). If so, run `npm run changeset`, stage the generated file, and amend the commit or create a new one. 206 | 207 | 10. **Proceed to Next Subtask:** 208 | * Identify the next subtask (e.g., using `next_task` / `task-master next`). 209 | 210 | ## Code Analysis & Refactoring Techniques 211 | 212 | - **Top-Level Function Search**: 213 | - Useful for understanding module structure or planning refactors. 214 | - Use grep/ripgrep to find exported functions/constants: 215 | `rg "export (async function|function|const) \w+"` or similar patterns. 216 | - Can help compare functions between files during migrations or identify potential naming conflicts. 217 | 218 | --- 219 | *This workflow provides a general guideline. Adapt it based on your specific project needs and team practices.* -------------------------------------------------------------------------------- /.roo/rules/roo_rules.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Guidelines for creating and maintaining Roo Code rules to ensure consistency and effectiveness. 3 | globs: .roo/rules/*.md 4 | alwaysApply: true 5 | --- 6 | 7 | - **Required Rule Structure:** 8 | ```markdown 9 | --- 10 | description: Clear, one-line description of what the rule enforces 11 | globs: path/to/files/*.ext, other/path/**/* 12 | alwaysApply: boolean 13 | --- 14 | 15 | - **Main Points in Bold** 16 | - Sub-points with details 17 | - Examples and explanations 18 | ``` 19 | 20 | - **File References:** 21 | - Use `[filename](mdc:path/to/file)` ([filename](mdc:filename)) to reference files 22 | - Example: [prisma.md](mdc:.roo/rules/prisma.md) for rule references 23 | - Example: [schema.prisma](mdc:prisma/schema.prisma) for code references 24 | 25 | - **Code Examples:** 26 | - Use language-specific code blocks 27 | ```typescript 28 | // ✅ DO: Show good examples 29 | const goodExample = true; 30 | 31 | // ❌ DON'T: Show anti-patterns 32 | const badExample = false; 33 | ``` 34 | 35 | - **Rule Content Guidelines:** 36 | - Start with high-level overview 37 | - Include specific, actionable requirements 38 | - Show examples of correct implementation 39 | - Reference existing code when possible 40 | - Keep rules DRY by referencing other rules 41 | 42 | - **Rule Maintenance:** 43 | - Update rules when new patterns emerge 44 | - Add examples from actual codebase 45 | - Remove outdated patterns 46 | - Cross-reference related rules 47 | 48 | - **Best Practices:** 49 | - Use bullet points for clarity 50 | - Keep descriptions concise 51 | - Include both DO and DON'T examples 52 | - Reference actual code over theoretical examples 53 | - Use consistent formatting across rules -------------------------------------------------------------------------------- /.roo/rules/self_improve.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Guidelines for continuously improving Roo Code rules based on emerging code patterns and best practices. 3 | globs: **/* 4 | alwaysApply: true 5 | --- 6 | 7 | - **Rule Improvement Triggers:** 8 | - New code patterns not covered by existing rules 9 | - Repeated similar implementations across files 10 | - Common error patterns that could be prevented 11 | - New libraries or tools being used consistently 12 | - Emerging best practices in the codebase 13 | 14 | - **Analysis Process:** 15 | - Compare new code with existing rules 16 | - Identify patterns that should be standardized 17 | - Look for references to external documentation 18 | - Check for consistent error handling patterns 19 | - Monitor test patterns and coverage 20 | 21 | - **Rule Updates:** 22 | - **Add New Rules When:** 23 | - A new technology/pattern is used in 3+ files 24 | - Common bugs could be prevented by a rule 25 | - Code reviews repeatedly mention the same feedback 26 | - New security or performance patterns emerge 27 | 28 | - **Modify Existing Rules When:** 29 | - Better examples exist in the codebase 30 | - Additional edge cases are discovered 31 | - Related rules have been updated 32 | - Implementation details have changed 33 | 34 | - **Example Pattern Recognition:** 35 | ```typescript 36 | // If you see repeated patterns like: 37 | const data = await prisma.user.findMany({ 38 | select: { id: true, email: true }, 39 | where: { status: 'ACTIVE' } 40 | }); 41 | 42 | // Consider adding to [prisma.md](mdc:.roo/rules/prisma.md): 43 | // - Standard select fields 44 | // - Common where conditions 45 | // - Performance optimization patterns 46 | ``` 47 | 48 | - **Rule Quality Checks:** 49 | - Rules should be actionable and specific 50 | - Examples should come from actual code 51 | - References should be up to date 52 | - Patterns should be consistently enforced 53 | 54 | - **Continuous Improvement:** 55 | - Monitor code review comments 56 | - Track common development questions 57 | - Update rules after major refactors 58 | - Add links to relevant documentation 59 | - Cross-reference related rules 60 | 61 | - **Rule Deprecation:** 62 | - Mark outdated patterns as deprecated 63 | - Remove rules that no longer apply 64 | - Update references to deprecated rules 65 | - Document migration paths for old patterns 66 | 67 | - **Documentation Updates:** 68 | - Keep examples synchronized with code 69 | - Update references to external docs 70 | - Maintain links between related rules 71 | - Document breaking changes 72 | Follow [cursor_rules.md](mdc:.roo/rules/cursor_rules.md) for proper rule formatting and structure. 73 | -------------------------------------------------------------------------------- /.roomodes: -------------------------------------------------------------------------------- 1 | { 2 | "customModes": [ 3 | { 4 | "slug": "boomerang", 5 | "name": "Boomerang", 6 | "roleDefinition": "You are Roo, a strategic workflow orchestrator who coordinates complex tasks by delegating them to appropriate specialized modes. You have a comprehensive understanding of each mode's capabilities and limitations, also your own, and with the information given by the user and other modes in shared context you are enabled to effectively break down complex problems into discrete tasks that can be solved by different specialists using the `taskmaster-ai` system for task and context management.", 7 | "customInstructions": "Your role is to coordinate complex workflows by delegating tasks to specialized modes, using `taskmaster-ai` as the central hub for task definition, progress tracking, and context management. \nAs an orchestrator, you should:\nn1. When given a complex task, use contextual information (which gets updated frequently) to break it down into logical subtasks that can be delegated to appropriate specialized modes.\nn2. For each subtask, use the `new_task` tool to delegate. Choose the most appropriate mode for the subtask's specific goal and provide comprehensive instructions in the `message` parameter. \nThese instructions must include:\n* All necessary context from the parent task or previous subtasks required to complete the work.\n* A clearly defined scope, specifying exactly what the subtask should accomplish.\n* An explicit statement that the subtask should *only* perform the work outlined in these instructions and not deviate.\n* An instruction for the subtask to signal completion by using the `attempt_completion` tool, providing a thorough summary of the outcome in the `result` parameter, keeping in mind that this summary will be the source of truth used to further relay this information to other tasks and for you to keep track of what was completed on this project.\nn3. Track and manage the progress of all subtasks. When a subtask is completed, acknowledge its results and determine the next steps.\nn4. Help the user understand how the different subtasks fit together in the overall workflow. Provide clear reasoning about why you're delegating specific tasks to specific modes.\nn5. Ask clarifying questions when necessary to better understand how to break down complex tasks effectively. If it seems complex delegate to architect to accomplish that \nn6. Use subtasks to maintain clarity. If a request significantly shifts focus or requires a different expertise (mode), consider creating a subtask rather than overloading the current one.", 8 | "groups": [ 9 | "read", 10 | "edit", 11 | "browser", 12 | "command", 13 | "mcp" 14 | ] 15 | }, 16 | { 17 | "slug": "architect", 18 | "name": "Architect", 19 | "roleDefinition": "You are Roo, an expert technical leader operating in Architect mode. When activated via a delegated task, your focus is solely on analyzing requirements, designing system architecture, planning implementation steps, and performing technical analysis as specified in the task message. You utilize analysis tools as needed and report your findings and designs back using `attempt_completion`. You do not deviate from the delegated task scope.", 20 | "customInstructions": "1. Do some information gathering (for example using read_file or search_files) to get more context about the task.\n\n2. You should also ask the user clarifying questions to get a better understanding of the task.\n\n3. Once you've gained more context about the user's request, you should create a detailed plan for how to accomplish the task. Include Mermaid diagrams if they help make your plan clearer.\n\n4. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it.\n\n5. Once the user confirms the plan, ask them if they'd like you to write it to a markdown file.\n\n6. Use the switch_mode tool to request that the user switch to another mode to implement the solution.", 21 | "groups": [ 22 | "read", 23 | [ 24 | "edit", 25 | { 26 | "fileRegex": "\\.md$", 27 | "description": "Markdown files only" 28 | } 29 | ], 30 | "command", 31 | "mcp" 32 | ] 33 | }, 34 | { 35 | "slug": "ask", 36 | "name": "Ask", 37 | "roleDefinition": "You are Roo, a knowledgeable technical assistant.\nWhen activated by another mode via a delegated task, your focus is to research, analyze, and provide clear, concise answers or explanations based *only* on the specific information requested in the delegation message. Use available tools for information gathering and report your findings back using `attempt_completion`.", 38 | "customInstructions": "You can analyze code, explain concepts, and access external resources. Make sure to answer the user's questions and don't rush to switch to implementing code. Include Mermaid diagrams if they help make your response clearer.", 39 | "groups": [ 40 | "read", 41 | "browser", 42 | "mcp" 43 | ] 44 | }, 45 | { 46 | "slug": "debug", 47 | "name": "Debug", 48 | "roleDefinition": "You are Roo, an expert software debugger specializing in systematic problem diagnosis and resolution. When activated by another mode, your task is to meticulously analyze the provided debugging request (potentially referencing Taskmaster tasks, logs, or metrics), use diagnostic tools as instructed to investigate the issue, identify the root cause, and report your findings and recommended next steps back via `attempt_completion`. You focus solely on diagnostics within the scope defined by the delegated task.", 49 | "customInstructions": "Reflect on 5-7 different possible sources of the problem, distill those down to 1-2 most likely sources, and then add logs to validate your assumptions. Explicitly ask the user to confirm the diagnosis before fixing the problem.", 50 | "groups": [ 51 | "read", 52 | "edit", 53 | "command", 54 | "mcp" 55 | ] 56 | }, 57 | { 58 | "slug": "test", 59 | "name": "Test", 60 | "roleDefinition": "You are Roo, an expert software tester. Your primary focus is executing testing tasks delegated to you by other modes.\nAnalyze the provided scope and context (often referencing a Taskmaster task ID and its `testStrategy`), develop test plans if needed, execute tests diligently, and report comprehensive results (pass/fail, bugs, coverage) back using `attempt_completion`. You operate strictly within the delegated task's boundaries.", 61 | "customInstructions": "Focus on the `testStrategy` defined in the Taskmaster task. Develop and execute test plans accordingly. Report results clearly, including pass/fail status, bug details, and coverage information.", 62 | "groups": [ 63 | "read", 64 | "command", 65 | "mcp" 66 | ] 67 | } 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /.taskmasterconfig: -------------------------------------------------------------------------------- 1 | { 2 | "models": { 3 | "main": { 4 | "provider": "anthropic", 5 | "modelId": "claude-3-7-sonnet-20250219", 6 | "maxTokens": 120000, 7 | "temperature": 0.2 8 | }, 9 | "research": { 10 | "provider": "perplexity", 11 | "modelId": "sonar-pro", 12 | "maxTokens": 8700, 13 | "temperature": 0.1 14 | }, 15 | "fallback": { 16 | "provider": "anthropic", 17 | "modelId": "claude-3.5-sonnet-20240620", 18 | "maxTokens": 120000, 19 | "temperature": 0.1 20 | } 21 | }, 22 | "global": { 23 | "logLevel": "info", 24 | "debug": false, 25 | "defaultSubtasks": 5, 26 | "defaultPriority": "medium", 27 | "projectName": "Taskmaster", 28 | "ollamaBaseUrl": "http://localhost:11434/api", 29 | "azureOpenaiBaseUrl": "https://your-endpoint.openai.azure.com/" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "eslint.format.enable": true, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.stylelint": "explicit", 6 | "source.fixAll.eslint": "explicit" 7 | }, 8 | "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], 9 | "editor.formatOnSave": true, 10 | "editor.tabSize": 2, 11 | "typescript.tsdk": "node_modules\\typescript\\lib" 12 | } 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute to Stackticon 2 | 3 | We are open to all kinds of contributing. Here are some guidelines for helping you with contributing: 4 | 5 | - Set up project 6 | - Issues or Bugs 7 | - Feature requests 8 | - Add Icons 9 | - Commit guidelines 10 | 11 | ## Set up projects 12 | 13 | ### Environments 14 | 15 | | Package | Version | 16 | | :--------- | :------ | 17 | | Node | 18.13.0 | 18 | | React | 18.2.0 | 19 | | Typescript | 4.9.5 | 20 | 21 | ### install packages 22 | 23 | ```shell 24 | yarn install 25 | ``` 26 | 27 | ### start project in local 28 | 29 | ```shell 30 | yarn start 31 | ``` 32 | 33 | ## Found Bugs? 34 | 35 | If you find any bug or awkward point, please let us know about issue with [submitting issue](https://github.com/msdio/stackticon/issues). You can further make Pull requests with a fix! 36 | 37 | ### Submitting issue 38 | 39 | You can report issue with [template](https://github.com/msdio/stackticon/issues/new?assignees=msdio&labels=bug&projects=&template=bug_report.md&title=) 40 | 41 | ## Feature Request 42 | 43 | If you have something to propose about our new feature, feel free to tell us with [feature request](https://github.com/msdio/stackticon/issues/new?assignees=author&labels=&projects=&template=new_feature.md&title=) 44 | 45 | ## Add Icons 46 | 47 | Adding icons is the easiest way to contribute to our project. All our icons are `svg`, so you can add info about icon in [src > constants > custom-icons.json](https://github.com/msdio/stackticon/blob/main/src/constants/custom-icons.json). 48 | 49 | You can add icon info with following: 50 | 51 | ```json 52 | { 53 | "title": "React Router", 54 | "slug": "reactRouter", 55 | "hex": "3174b9", 56 | "path": "M58.33 101.79C29.54 101.79 17 99.42 0 ...", 57 | "viewBox": "0 0 110 145" 58 | } 59 | ``` 60 | 61 | - `title`: Every word should start with capital letter. 62 | - `slug`: Same as title, but in small letters, without spaces. `ex) twowords` 63 | - `hex`: Color of svg, please write without #. 64 | - `path`: Path of svg. 65 | - `viewBox`: ViewBox of svg. 66 | 67 | ## Commit guidelines 68 | 69 | ``` 70 | : 71 | ``` 72 | 73 | ### Example 74 | 75 | ``` 76 | docs: add contribution guide on CONTRIBUTING.md 77 | ``` 78 | 79 | ### Type 80 | 81 | Must be one of the following: 82 | 83 | - **feat**: New feature 84 | - **fix**: Fixing bugs 85 | - **style**: Changes with styling, layout or formatting codes 86 | - **refactor**: Change codes that but neither fix bugs nor add features 87 | - **icon**: Adding icons 88 | - **remove**: Removing not using codes, assets, tests, etc 89 | - **build**: Changes related to build, dependencies, release versoning 90 | - **docs**: Documentation changes 91 | - **test**: Add or correct tests 92 | 93 | ### Message 94 | 95 | Subscribe what you've done. 96 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 HR 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stackticon 2 | 3 | Make skill sets image for readme
4 | 5 | https://msdio.github.io/stackticon/ 6 | 7 | [![stackticon](https://firebasestorage.googleapis.com/v0/b/stackticon-81399.appspot.com/o/images%2F1682742099291?alt=media&token=076b97bc-71a6-4fb3-ba55-21335cff1a73)](https://github.com/msdio/stackticon) 8 | 9 |
10 | 11 | ## How To Contribute 12 | 13 | See [Contribution guide](https://github.com/msdio/stackticon/blob/main/CONTRIBUTING.md). 14 | 15 | ## Environments 16 | 17 | | Package | Version | 18 | | :--------- | :------ | 19 | | Node | 18.13.0 | 20 | | React | 18.2.0 | 21 | | Typescript | 4.9.5 | 22 | 23 |
24 | 25 | ## How to install 26 | 27 | you can download modules with command: 28 | 29 | ``` 30 | yarn install 31 | ``` 32 | 33 | and you can start with command: 34 | 35 | ``` 36 | yarn start 37 | ``` 38 | 39 |
40 | 41 | ## Features 42 | 43 | ### Simply type your skill sets
44 | 45 | 46 |
47 | 48 | ### Or put your front-end app repository url (BETA) 49 | 50 | Screenshot 2023-04-27 at 3 20 20 PM 51 |
52 | 53 | ### Choose your color
54 | 55 | 56 |
57 | 58 | ### Create your own skill sets 59 | 60 | [![stackticon](https://firebasestorage.googleapis.com/v0/b/stackticon-81399.appspot.com/o/images%2F1682742099291?alt=media&token=076b97bc-71a6-4fb3-ba55-21335cff1a73)](https://github.com/msdio/stackticon) 61 |
62 | 63 | ### You can copy url and use in your project's readme
64 | 65 | ![image](https://user-images.githubusercontent.com/59170680/225878420-5a8b2811-eb87-4a12-b846-9b8c1550eb5d.png) 66 |
67 | 68 | ## Structure 69 | 70 |
71 | ├── assets
72 | │   ├── icons
73 | │   └── images
74 | ├── components
75 | ├── constants
76 | ├── hooks
77 | ├── mock
78 | ├── pages
79 | ├── services
80 | ├── styles
81 | ├── types
82 | ├── utils
83 | │   └── test
84 | │
85 | ├── App.tsx
86 | ├── Router.tsx
87 | └── RouterContainer.tsx
88 | 
89 | 90 | ## Authors 91 | 92 |
93 | 94 | | [msdio](https://github.com/msdio) | [userJu](https://github.com/userJu) | [dori cho](https://instagram.com/h_eun1?igshid=YmMyMTA2M2Y=) | [g2hhh2ee](https://github.com/g2hhh2ee) 95 | |:---:|:---:|:---:|:---:| 96 | |msdio|userJu|dori cho|g2hhh2ee 97 | 98 |
99 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 26 | 27 | Stackticon 28 | 29 | 30 | 31 |
32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testMatch: ['**/test/**/*.test.ts'], 5 | transform: { '^.+\\.(ts|tsx)$': 'ts-jest' }, 6 | moduleNameMapper: { 7 | '^@/(.*)$': '/src/$1', 8 | }, 9 | moduleDirectories: ['node_modules', 'src'], 10 | collectCoverageFrom: ['**/utils/*.ts'], 11 | }; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stackticon", 3 | "version": "1.3.0", 4 | "private": true, 5 | "description": "make skill sets image for readme", 6 | "main": "index.js", 7 | "repository": "https://github.com/msdio/stackticon.git", 8 | "author": "msdio , userJu ", 9 | "license": "MIT", 10 | "homepage": ".", 11 | "type": "module", 12 | "scripts": { 13 | "start": "vite", 14 | "build": "tsc && vite build", 15 | "preview": "vite preview", 16 | "test": "jest", 17 | "lint": "eslint .", 18 | "lint:fix": "eslint --fix ./src", 19 | "predeploy": "yarn build", 20 | "deploy": "gh-pages -d build", 21 | "postinstall": "patch-package && husky install", 22 | "task:list": "task-master list", 23 | "task:next": "task-master next", 24 | "task:start": "task-master set-status --status=in-progress", 25 | "task:done": "task-master set-status --status=done" 26 | }, 27 | "engines": { 28 | "npm": "please use yarn, not npm", 29 | "node": ">=16.0.0", 30 | "yarn": ">=1.0.0" 31 | }, 32 | "eslintConfig": { 33 | "extends": [ 34 | "react-app", 35 | "react-app/jest" 36 | ] 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | }, 50 | "dependencies": { 51 | "@emotion/react": "^11.14.0", 52 | "@emotion/styled": "^11.14.0", 53 | "@mui/icons-material": "^7.1.0", 54 | "@mui/material": "^7.1.0", 55 | "@mui/system": "^7.1.0", 56 | "axios": "1.9.0", 57 | "firebase": "11.7.3", 58 | "html-to-image": "^1.11.11", 59 | "motion": "^12.12.1", 60 | "patch-package": "^7.0.0", 61 | "postinstall-postinstall": "^2.1.0", 62 | "pretendard": "^1.3.6", 63 | "react": "^19.0.0", 64 | "react-dom": "^19.0.0", 65 | "react-ga4": "^2.0.0", 66 | "react-router": "^7.6.0", 67 | "simple-icons": "^13.18.0" 68 | }, 69 | "devDependencies": { 70 | "@babel/core": "^7.21.3", 71 | "@testing-library/jest-dom": "^5.14.1", 72 | "@testing-library/react": "^13.0.0", 73 | "@testing-library/user-event": "^13.2.1", 74 | "@types/jest": "^29.4.0", 75 | "@types/node": "22.15.18", 76 | "@types/react": "^19.0.0", 77 | "@types/react-dom": "^19.0.0", 78 | "@typescript-eslint/eslint-plugin": "^5.51.0", 79 | "@typescript-eslint/parser": "^5.51.0", 80 | "@vitejs/plugin-react": "4.4.1", 81 | "eslint": "^8.34.0", 82 | "eslint-config-prettier": "^8.6.0", 83 | "eslint-config-react": "^1.1.7", 84 | "eslint-config-react-app": "^7.0.1", 85 | "eslint-import-resolver-typescript": "^3.6.0", 86 | "eslint-plugin-prettier": "^4.2.1", 87 | "eslint-plugin-react": "^7.32.2", 88 | "eslint-plugin-unused-imports": "^2.0.0", 89 | "gh-pages": "^6.3.0", 90 | "husky": "^8.0.3", 91 | "jest": "^29.5.0", 92 | "prettier": "2.8.4", 93 | "task-master-ai": "^0.13.2", 94 | "ts-jest": "^29.0.5", 95 | "typescript": "^5.8.3", 96 | "vite": "6.3.5", 97 | "vite-tsconfig-paths": "5.1.3" 98 | }, 99 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" 100 | } 101 | -------------------------------------------------------------------------------- /patches/html-to-image+1.11.11.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/html-to-image/lib/index.js b/node_modules/html-to-image/lib/index.js 2 | index b8f1e89..700a6b9 100644 3 | --- a/node_modules/html-to-image/lib/index.js 4 | +++ b/node_modules/html-to-image/lib/index.js 5 | @@ -93,6 +93,8 @@ function toCanvas(node, options) { 6 | if (!options.skipAutoScale) { 7 | (0, util_1.checkCanvasDimensions)(canvas); 8 | } 9 | + canvas.setAttribute('width',canvasWidth) 10 | + canvas.setAttribute('height',canvasHeight) 11 | canvas.style.width = "".concat(canvasWidth); 12 | canvas.style.height = "".concat(canvasHeight); 13 | if (options.backgroundColor) { 14 | -------------------------------------------------------------------------------- /public/bookpile.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msdio/stackticon/eb415b566f12c698ace2a2946a58b39e09eba677/public/bookpile.ico -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msdio/stackticon/eb415b566f12c698ace2a2946a58b39e09eba677/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msdio/stackticon/eb415b566f12c698ace2a2946a58b39e09eba677/public/favicon.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Stackticon", 3 | "name": "Stackticon App", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /scripts/prd.txt: -------------------------------------------------------------------------------- 1 | 2 | # Overview 3 | This project is an website which users can easily make their tech stacks as one image. 4 | With this service, developers don't have to search for svgs, and edit them as one image, capture, and place in their tech stack. They just pick their tech stacks with search, and submit, and copy & paste result. 5 | 6 | # Core Features 7 | - Select stacks with input on main screen 8 | - Or paste user's project repository url to extract stacks in project. 9 | - Select background color of image 10 | - Select three ways to copy : image link only, image tag, markdown image 11 | 12 | # User Experience 13 | ## User 1 14 | - 15 | 16 | 17 | # Development Roadmap 18 | - Upgrade package versions, especially node.js, typescript and react 19 | - Refactor all over application 20 | - Insert google ads 21 | - Optimize application 22 | 23 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { CssBaseline } from '@mui/material'; 2 | import { ThemeProvider } from '@mui/system'; 3 | 4 | import StacksProvider from 'providers/StacksProvider'; 5 | import RouterContainer from 'RouterContainer'; 6 | import { theme } from 'styles/theme'; 7 | 8 | function App() { 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /src/Router.tsx: -------------------------------------------------------------------------------- 1 | import { lazy } from 'react'; 2 | import { Route, Routes } from 'react-router'; 3 | 4 | import BlockLoading from 'components/loading/BlockLoading'; 5 | import RequireState from 'components/require-state'; 6 | import Cute404 from 'pages/page404/Cute404'; 7 | import GoogleAnalyticsTracker from 'services/google-analytics/GoogleAnalyticsTracker'; 8 | 9 | const Home = lazy(() => import('pages/home')); 10 | const BackgroundColors = lazy(() => import('pages/background-colors')); 11 | const Result = lazy(() => import('pages/result')); 12 | 13 | const Router = () => { 14 | GoogleAnalyticsTracker(); 15 | 16 | return ( 17 | 18 | } /> 19 | } />} /> 20 | } />} /> 21 | } />} /> 22 | } /> 23 | 24 | ); 25 | }; 26 | 27 | export default Router; 28 | -------------------------------------------------------------------------------- /src/RouterContainer.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from 'react'; 2 | import { HashRouter } from 'react-router'; 3 | 4 | import { Box } from '@mui/material'; 5 | 6 | import LogoLoading from 'components/loading/LogoLoading'; 7 | 8 | import Router from './Router'; 9 | 10 | const RouterContainer = () => { 11 | return ( 12 | 13 | 14 | }> 15 | 16 | 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default RouterContainer; 23 | -------------------------------------------------------------------------------- /src/apis/packages.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { PackageJSONType } from 'types/packageJson'; 3 | 4 | export const getPackageJSONObject = async (path: string) => { 5 | try { 6 | const response = await axios({ 7 | method: 'get', 8 | url: path, 9 | }).then((res) => res.data); 10 | 11 | return response; 12 | } catch (error) { 13 | throw new Error('Invalid repository path.\nPlease input root of web application path'); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/assets/icons/logoWithBackground.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/material'; 2 | 3 | import Logo from '../images/logo.png'; 4 | 5 | interface LogoProps { 6 | width: number; 7 | height: number; 8 | } 9 | 10 | const LogoWithBackground = ({ width, height }: LogoProps) => { 11 | return ( 12 | 22 | logo 23 | 24 | ); 25 | }; 26 | 27 | export default LogoWithBackground; 28 | -------------------------------------------------------------------------------- /src/assets/images/landing-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msdio/stackticon/eb415b566f12c698ace2a2946a58b39e09eba677/src/assets/images/landing-image.png -------------------------------------------------------------------------------- /src/assets/images/landing-label.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msdio/stackticon/eb415b566f12c698ace2a2946a58b39e09eba677/src/assets/images/landing-label.png -------------------------------------------------------------------------------- /src/assets/images/loading/loading-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msdio/stackticon/eb415b566f12c698ace2a2946a58b39e09eba677/src/assets/images/loading/loading-1.png -------------------------------------------------------------------------------- /src/assets/images/loading/loading-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msdio/stackticon/eb415b566f12c698ace2a2946a58b39e09eba677/src/assets/images/loading/loading-2.png -------------------------------------------------------------------------------- /src/assets/images/loading/loading-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msdio/stackticon/eb415b566f12c698ace2a2946a58b39e09eba677/src/assets/images/loading/loading-3.png -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msdio/stackticon/eb415b566f12c698ace2a2946a58b39e09eba677/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/components/@common/background-with-circle/BackgroundWithCircle.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren, RefObject } from 'react'; 2 | 3 | import { Box } from '@mui/material'; 4 | 5 | import type { SxProps } from '@mui/material'; 6 | 7 | const BackgroundCircle = () => ( 8 | 18 | ); 19 | 20 | interface BackgroundCircleProps { 21 | sx: SxProps; 22 | domRef?: RefObject; 23 | } 24 | 25 | export const BackgroundWithCircle = ({ 26 | children, 27 | sx, 28 | domRef, 29 | }: PropsWithChildren) => ( 30 | 31 | {children} 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | ); 41 | -------------------------------------------------------------------------------- /src/components/@common/background-with-circle/index.ts: -------------------------------------------------------------------------------- 1 | import { BackgroundWithCircle } from './BackgroundWithCircle'; 2 | 3 | export default BackgroundWithCircle; 4 | -------------------------------------------------------------------------------- /src/components/@common/heading-text/HeadingText.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from 'react'; 2 | 3 | import { Typography, useMediaQuery } from '@mui/material'; 4 | 5 | export const HeadingText = ({ children }: PropsWithChildren) => { 6 | const isMobile = useMediaQuery('(max-width: 900px)'); 7 | 8 | return ( 9 | 16 | {children} 17 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/@common/heading-text/index.ts: -------------------------------------------------------------------------------- 1 | import { HeadingText } from './HeadingText'; 2 | 3 | export default HeadingText; 4 | -------------------------------------------------------------------------------- /src/components/button-options/ButtonOptions.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, useRef, useState } from 'react'; 2 | 3 | import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; 4 | import { Button, ButtonGroup, MenuItem } from '@mui/material'; 5 | 6 | // import type { CopyOptionType } from 'constants/constants'; 7 | import { COPY_OPTIONS } from 'constants/constants'; 8 | import { makeUrlIntoBracket, makeUrlIntoImgTag } from 'utils/resultUrl'; 9 | 10 | import { OptionListContainer } from './OptionListContainer'; 11 | 12 | import type { CopyOptionType } from 'constants/constants'; 13 | import type { LocationState } from 'types/location'; 14 | 15 | const copyOptions = Object.values(COPY_OPTIONS); 16 | 17 | interface ButtonOptionProps { 18 | state: LocationState; 19 | setOpenToast: React.Dispatch>; 20 | } 21 | const ButtonOptions = ({ state, setOpenToast }: ButtonOptionProps) => { 22 | const [openOptions, setOpenOptions] = useState(false); 23 | const buttonRef = useRef(null); 24 | const selectedOption = useRef(0); 25 | 26 | const changeOption = (option: CopyOptionType) => { 27 | const bracketUrl = makeUrlIntoBracket(state.url); 28 | const imgTagUrl = makeUrlIntoImgTag(state.url); 29 | 30 | if (option === COPY_OPTIONS.COPY_README) { 31 | return bracketUrl; 32 | } else if (option === COPY_OPTIONS.COPY_TAG) { 33 | return imgTagUrl; 34 | } else { 35 | return state.url; 36 | } 37 | }; 38 | 39 | const clickCopyUrl = async () => { 40 | const selected = copyOptions[selectedOption.current]; 41 | const copyText = changeOption(selected); 42 | 43 | await navigator.clipboard.writeText(copyText); 44 | setOpenToast(true); 45 | }; 46 | 47 | const clickCopyOption = (e: React.MouseEvent, index: number) => { 48 | selectedOption.current = index; 49 | setOpenOptions(false); 50 | clickCopyUrl(); 51 | }; 52 | 53 | const handleToggle = () => { 54 | setOpenOptions((prev) => !prev); 55 | }; 56 | 57 | const handleClose = (e: Event) => { 58 | if (buttonRef.current && buttonRef.current.contains(e.target as HTMLElement)) { 59 | return; 60 | } 61 | setOpenOptions(false); 62 | }; 63 | 64 | return ( 65 | 66 | 77 | 83 | 100 | 101 | 102 | {openOptions && ( 103 | 104 | {copyOptions.map((option, idx) => ( 105 | clickCopyOption(e, idx)} 109 | > 110 | {option} 111 | 112 | ))} 113 | 114 | )} 115 | 116 | ); 117 | }; 118 | 119 | export default ButtonOptions; 120 | -------------------------------------------------------------------------------- /src/components/button-options/OptionListContainer.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from 'react'; 2 | 3 | import { ClickAwayListener, Grow, MenuList, Paper, Popper } from '@mui/material'; 4 | 5 | interface OptionListContainerProp { 6 | buttonRef: HTMLElement | null; 7 | handleClose: (e: Event) => void; 8 | } 9 | 10 | export const OptionListContainer = ({ 11 | buttonRef, 12 | handleClose, 13 | children, 14 | }: PropsWithChildren) => { 15 | return ( 16 | 17 | {({ TransitionProps, placement }) => ( 18 | 24 | 25 | 26 | 27 | {children} 28 | 29 | 30 | 31 | 32 | )} 33 | 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/components/button-options/index.ts: -------------------------------------------------------------------------------- 1 | import ButtonOptions from './ButtonOptions'; 2 | 3 | export default ButtonOptions; 4 | -------------------------------------------------------------------------------- /src/components/drag-container/DragContainer.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | import { DraggableChip } from './DraggableChip'; 4 | 5 | import type { SimpleIcon } from 'simple-icons'; 6 | 7 | interface DragContainerProps { 8 | array: SimpleIcon[]; 9 | updateArray: (s: SimpleIcon[]) => void; 10 | } 11 | 12 | export const DragContainer = ({ array, updateArray }: DragContainerProps) => { 13 | const [selected, setSelected] = useState(); 14 | 15 | const reorderArray = (index: number) => { 16 | const filteredArray = array.filter((el) => el !== selected); 17 | 18 | const prev = filteredArray.slice(0, index); 19 | const next = filteredArray.slice(index, filteredArray.length); 20 | 21 | if (prev && next && selected) { 22 | updateArray([...prev, selected, ...next]); 23 | } 24 | }; 25 | 26 | const deleteItem = (index: number) => { 27 | const deleted = array.filter((el, idx) => idx !== index); 28 | updateArray([...deleted]); 29 | }; 30 | 31 | const mouseUp = (index: number) => { 32 | reorderArray(index); 33 | }; 34 | 35 | const mouseDown = (index: number) => { 36 | setSelected(array[index]); 37 | }; 38 | 39 | return ( 40 | <> 41 | {array.map((stack, index) => ( 42 | mouseUp(index)} 46 | mouseDown={() => mouseDown(index)} 47 | deleteItem={() => deleteItem(index)} 48 | /> 49 | ))} 50 | 51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /src/components/drag-container/DraggableChip.tsx: -------------------------------------------------------------------------------- 1 | import { Chip } from '@mui/material'; 2 | 3 | import type { SimpleIcon } from 'simple-icons'; 4 | 5 | interface DraggableChipProps { 6 | stack: SimpleIcon; 7 | mouseUp: () => void; 8 | mouseDown: () => void; 9 | deleteItem: () => void; 10 | } 11 | 12 | export const DraggableChip = ({ stack, mouseUp, mouseDown, deleteItem }: DraggableChipProps) => { 13 | return ( 14 |
15 | 24 |
25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /src/components/draggable-icon/DraggableIcon.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { motion } from 'motion/react'; 3 | 4 | interface PositionProps { 5 | top?: string; 6 | bottom?: string; 7 | left?: string; 8 | right?: string; 9 | } 10 | 11 | const MotionDiv = styled(motion.div)` 12 | width: 70px; 13 | height: 70px; 14 | background-color: transparent; 15 | border-radius: 20px; 16 | box-shadow: 0 2px 3px rgba(0, 0, 0, 0), 0 10px 20px rgba(0, 0, 0, 0); 17 | font-size: 70px; 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | 22 | overflow: hidden; 23 | 24 | position: absolute; 25 | top: ${(props) => props.top}; 26 | bottom: ${(props) => props.bottom}; 27 | left: ${(props) => props.left}; 28 | right: ${(props) => props.right}; 29 | 30 | z-index: 2; 31 | `; 32 | 33 | interface DraggableIconProps { 34 | constraints: React.RefObject; 35 | icon: React.ReactNode; 36 | } 37 | 38 | const DraggableIcon = ({ 39 | constraints, 40 | icon, 41 | top, 42 | bottom, 43 | left, 44 | right, 45 | }: DraggableIconProps & PositionProps) => { 46 | return ( 47 | 56 | {icon} 57 | 58 | ); 59 | }; 60 | 61 | export default DraggableIcon; 62 | -------------------------------------------------------------------------------- /src/components/draggable-icon/index.ts: -------------------------------------------------------------------------------- 1 | import DraggableIcon from './DraggableIcon'; 2 | 3 | export default DraggableIcon; 4 | -------------------------------------------------------------------------------- /src/components/header/Header.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { Link as RLink } from 'react-router'; 3 | 4 | import styled from '@emotion/styled'; 5 | import { Box, Link, Stack, SvgIcon, Typography } from '@mui/material'; 6 | import useMediaQuery from '@mui/material/useMediaQuery'; 7 | import { motion } from 'motion/react'; 8 | 9 | import LogoWithBackground from 'assets/icons/logoWithBackground'; 10 | import LogoLoading from 'components/loading/LogoLoading'; 11 | import ReadRepository from 'components/read-repository'; 12 | import { wiggling } from 'constants/animations'; 13 | import { APP_NAME, GITHUB_LINK } from 'constants/constants'; 14 | import { BetaBadge } from 'constants/icons'; 15 | import { Svg } from 'constants/svgs'; 16 | import { useStacks } from 'providers/StacksProvider'; 17 | 18 | const Container = styled.header` 19 | display: flex; 20 | align-items: center; 21 | 22 | width: 100%; 23 | height: 86px; 24 | background-color: #ffffff; 25 | padding: 0px 4.2vw; 26 | 27 | position: fixed; 28 | 29 | z-index: 100; 30 | `; 31 | 32 | const RouterLink = styled(RLink)` 33 | text-decoration: none; 34 | 35 | display: flex; 36 | align-items: center; 37 | 38 | gap: 0.5625rem; 39 | `; 40 | 41 | const GithubLink = styled(Link)` 42 | width: 201px; 43 | height: 49px; 44 | 45 | display: flex; 46 | align-items: center; 47 | justify-content: center; 48 | 49 | background: linear-gradient(0deg, rgba(0, 102, 255, 0.2), rgba(0, 102, 255, 0.2)), #ffffff; 50 | 51 | border-radius: 12px; 52 | 53 | text-decoration: none; 54 | `; 55 | 56 | const ReadRepositoryButton = styled(motion.div)` 57 | width: 201px; 58 | height: 49px; 59 | 60 | display: flex; 61 | align-items: center; 62 | justify-content: center; 63 | 64 | text-decoration: none; 65 | 66 | cursor: pointer; 67 | `; 68 | 69 | interface HeaderProps { 70 | isMain: boolean; 71 | } 72 | 73 | const Header = ({ isMain }: HeaderProps) => { 74 | const isMobile = useMediaQuery('(max-width: 740px)'); 75 | const isEligibleForBeta = useMediaQuery('(min-width: 1070px)'); 76 | const [openRepositoryInput, setOpenRepositoryInput] = useState(false); 77 | const [isLoading, setIsLoading] = useState(false); 78 | const { update } = useStacks(); 79 | 80 | const closeRepositoryInput = () => { 81 | setOpenRepositoryInput(false); 82 | }; 83 | 84 | const handleLoading = (state: boolean) => { 85 | setIsLoading(state); 86 | setOpenRepositoryInput(false); 87 | }; 88 | 89 | return ( 90 | 91 | {isLoading && } 92 | 93 | 94 | 95 | 104 | {APP_NAME} 105 | 106 | 107 | 108 | 109 | {isEligibleForBeta && openRepositoryInput && isMain && ( 110 | 111 | )} 112 | {isEligibleForBeta && isMain && ( 113 | { 118 | setOpenRepositoryInput((prev) => !prev); 119 | e.stopPropagation(); 120 | }} 121 | > 122 | 128 | Get stacks from repo 129 | 130 | 138 | 139 | 140 | 141 | )} 142 | 143 | 151 | 160 | 161 | 162 | 163 | 164 | Visit Github 165 | 166 | 167 | 168 | 169 | {/* 178 | 179 | 180 | 181 | buy coffee 182 | */} 183 | 184 | 185 | ); 186 | }; 187 | 188 | export default Header; 189 | -------------------------------------------------------------------------------- /src/components/header/index.ts: -------------------------------------------------------------------------------- 1 | import Header from './Header'; 2 | 3 | export default Header; 4 | -------------------------------------------------------------------------------- /src/components/hover-card/HoverCard.tsx: -------------------------------------------------------------------------------- 1 | import { useState, type PropsWithChildren } from 'react'; 2 | 3 | import styled from '@emotion/styled'; 4 | import { Box, styled as styledMUI, useMediaQuery } from '@mui/material'; 5 | 6 | import type { BgColorOption } from 'types/backgroundColors'; 7 | 8 | const Container = styledMUI(Box)(({ theme }) => ({ 9 | cursor: 'pointer', 10 | position: 'relative', 11 | '&:hover': { 12 | outline: `7px solid ${theme.palette.p[3]}`, 13 | borderRadius: '8px', 14 | h1: { 15 | opacity: 1, 16 | }, 17 | }, 18 | zIndex: 1, 19 | })); 20 | 21 | const Hider = styled.div` 22 | width: 100%; 23 | height: 100%; 24 | 25 | position: absolute; 26 | left: 0; 27 | right: 0; 28 | top: 0; 29 | 30 | z-index: 5; 31 | 32 | background-color: rgba(10, 19, 32, 0.5); 33 | 34 | border-radius: 8px; 35 | `; 36 | 37 | const Label = styled.h1<{ color: BgColorOption }>` 38 | padding: 0; 39 | margin: 0; 40 | position: absolute; 41 | top: 50%; 42 | left: 50%; 43 | transform: translate(-50%, -50%); 44 | opacity: 0; 45 | transition: opacity 200ms ease-in-out; 46 | color: white; 47 | 48 | z-index: 10; 49 | `; 50 | 51 | interface HoverCardProps extends PropsWithChildren { 52 | onClick: () => void; 53 | label: string; 54 | color: BgColorOption; 55 | } 56 | 57 | const HoverCard = ({ children, onClick, label, color }: HoverCardProps) => { 58 | const isMobile = useMediaQuery('(max-width: 900px)'); 59 | const [hide, setHide] = useState(false); 60 | 61 | return ( 62 | setHide(true)} 65 | onMouseLeave={() => setHide(false)} 66 | sx={{ 67 | scale: isMobile ? '0.5' : '1', 68 | transform: isMobile ? 'translateY(-10rem)' : '0', 69 | }} 70 | > 71 | {children} 72 | {hide && } 73 | 74 | 75 | ); 76 | }; 77 | 78 | export default HoverCard; 79 | -------------------------------------------------------------------------------- /src/components/hover-card/index.ts: -------------------------------------------------------------------------------- 1 | import HoverCard from './HoverCard'; 2 | 3 | export default HoverCard; 4 | -------------------------------------------------------------------------------- /src/components/input/Input.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | import styled from '@emotion/styled'; 4 | import { 5 | Autocomplete, 6 | createFilterOptions, 7 | ListItem, 8 | TextField, 9 | useMediaQuery, 10 | } from '@mui/material'; 11 | 12 | import { DragContainer } from 'components/drag-container/DragContainer'; 13 | import { useStacks } from 'providers/StacksProvider'; 14 | 15 | import { getIconDetail, makeIconInfoArray } from '../../utils/allIconInfo'; 16 | 17 | import type { SimpleIcon } from 'simple-icons'; 18 | 19 | const DropdownSvg = styled.div<{ hex: string }>` 20 | width: 24px; 21 | height: 24px; 22 | margin-right: 10px; 23 | 24 | svg { 25 | width: 24px; 26 | height: 24px; 27 | } 28 | 29 | path { 30 | fill: ${(props) => props.hex}; 31 | } 32 | `; 33 | 34 | const Input = () => { 35 | const [inputStacks, setInputStacks] = useState([]); 36 | const { stacks, update } = useStacks(); 37 | const isMobile = useMediaQuery('(max-width: 900px)'); 38 | const iconArr = makeIconInfoArray(); 39 | 40 | useEffect(() => { 41 | const ret: SimpleIcon[] = []; 42 | stacks.forEach((stk) => { 43 | ret.push(getIconDetail(stk)); 44 | }); 45 | 46 | setInputStacks(ret); 47 | }, [stacks]); 48 | 49 | const onStackChange = (value: SimpleIcon[]) => { 50 | update( 51 | value.map( 52 | (el: SimpleIcon) => 53 | el.slug.substring(0, 1).toUpperCase() + el.slug.substring(1, el.slug.length), 54 | ), 55 | ); 56 | }; 57 | 58 | const changeStackOrder = (stacks: SimpleIcon[]) => { 59 | setInputStacks(stacks); 60 | onStackChange(stacks); 61 | }; 62 | 63 | return ( 64 |
65 | ( 70 | 71 | 75 | {option.title} 76 | 77 | )} 78 | getOptionLabel={(option) => option.title} 79 | value={inputStacks} 80 | isOptionEqualToValue={(option, value) => { 81 | return option.slug === value.slug; 82 | }} 83 | onChange={(e, value) => onStackChange(value)} 84 | filterSelectedOptions 85 | renderInput={(params) => } 86 | renderTags={(stacks) => } 87 | filterOptions={createFilterOptions({ 88 | limit: 100, 89 | })} 90 | /> 91 |
92 | ); 93 | }; 94 | 95 | export default Input; 96 | -------------------------------------------------------------------------------- /src/components/input/index.ts: -------------------------------------------------------------------------------- 1 | import Input from './Input'; 2 | 3 | export default Input; 4 | -------------------------------------------------------------------------------- /src/components/loading/BlockLoading.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | import { useNavigate, useParams } from 'react-router'; 3 | 4 | import styled from '@emotion/styled'; 5 | import { Typography } from '@mui/material'; 6 | 7 | import StackGroup from 'components/stack-group'; 8 | import { tetromino1, tetromino2, tetromino3, tetromino4 } from 'constants/animations'; 9 | import { useStacks } from 'providers/StacksProvider'; 10 | import { getCreatedImageUrl } from 'services/firebase/storage'; 11 | import { htmlToPng } from 'utils/imageConverter'; 12 | 13 | import type { BgColorOption } from 'types/backgroundColors'; 14 | 15 | const Tetrominos = styled.div` 16 | position: absolute; 17 | top: 50%; 18 | left: 50%; 19 | transform: translate(-112px, -96px); 20 | `; 21 | 22 | const Tetromino = styled.div` 23 | width: 96px; 24 | height: 112px; 25 | position: absolute; 26 | transition: all ease 0.3s; 27 | background: url('data:image/svg+xml;utf-8,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 612 684"%3E%3Cpath fill="%23010101" d="M305.7 0L0 170.9v342.3L305.7 684 612 513.2V170.9L305.7 0z"/%3E%3Cpath fill="%23fff" d="M305.7 80.1l-233.6 131 233.6 131 234.2-131-234.2-131"/%3E%3C/svg%3E') 28 | no-repeat top center; 29 | `; 30 | 31 | const TetrominoBox1 = styled(Tetromino)` 32 | animation: ${tetromino1} 1.5s ease-out infinite; 33 | `; 34 | const TetrominoBox2 = styled(Tetromino)` 35 | animation: ${tetromino2} 1.5s ease-out infinite; 36 | `; 37 | const TetrominoBox3 = styled(Tetromino)` 38 | animation: ${tetromino3} 1.5s ease-out infinite; 39 | z-index: 2; 40 | `; 41 | const TetrominoBox4 = styled(Tetromino)` 42 | animation: ${tetromino4} 1.5s ease-out infinite; 43 | `; 44 | 45 | const Container = styled.div` 46 | height: '100vh'; 47 | overflow-y: hidden; 48 | `; 49 | 50 | const LoadingContainer = styled.div` 51 | display: flex; 52 | align-items: center; 53 | justify-content: center; 54 | flex-direction: column; 55 | height: 100vh; 56 | `; 57 | 58 | const BlockLoading = () => { 59 | const targetRef = useRef(null); 60 | const navigate = useNavigate(); 61 | const { color } = useParams(); 62 | const { stacks } = useStacks(); 63 | 64 | const navigateToResult = (resultUrl: string) => { 65 | navigate('/result', { state: { url: resultUrl } }); 66 | }; 67 | 68 | const makeResult = async () => { 69 | const imageRef = await htmlToPng(targetRef); 70 | const resultUrl = imageRef && (await getCreatedImageUrl(imageRef)); 71 | 72 | if (resultUrl) { 73 | navigateToResult(resultUrl); 74 | return; 75 | } 76 | navigate('/'); 77 | throw new Error('fail to generate image. please try again'); 78 | }; 79 | 80 | useEffect(() => { 81 | makeResult(); 82 | }, []); 83 | 84 | return ( 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 99 | Making Image... 100 | 101 | 102 | 103 | 104 | 105 | ); 106 | }; 107 | 108 | export default BlockLoading; 109 | -------------------------------------------------------------------------------- /src/components/loading/LogoLoading.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Stack, styled, Typography } from '@mui/material'; 2 | 3 | import { RandomRotate } from 'constants/animations'; 4 | 5 | import Loading1 from '../../assets/images/loading/loading-1.png'; 6 | import Loading2 from '../../assets/images/loading/loading-2.png'; 7 | import Loading3 from '../../assets/images/loading/loading-3.png'; 8 | 9 | const Hider = styled(Box)(({ theme }) => ({ 10 | position: 'absolute', 11 | left: '0', 12 | right: '0', 13 | top: '0', 14 | bottom: '0', 15 | 16 | zIndex: 5, 17 | 18 | backgroundColor: `${theme.palette.info.dark}`, 19 | opacity: 0.9, 20 | })); 21 | 22 | const LogoLoading = ({ loadingText }: { loadingText: string }) => { 23 | return ( 24 | 36 | 37 | 38 | 39 | 40 | {loadingText} 41 | 42 | 43 | 44 | 49 | {'loading 50 | 51 | 57 | {'loading 58 | 59 | 65 | {'loading 66 | 67 | 68 | 69 | 70 | ); 71 | }; 72 | 73 | export default LogoLoading; 74 | -------------------------------------------------------------------------------- /src/components/read-repository/ReadRepository.tsx: -------------------------------------------------------------------------------- 1 | import type { ChangeEvent, KeyboardEvent } from 'react'; 2 | import { useState } from 'react'; 3 | 4 | import { Box, Input } from '@mui/material'; 5 | 6 | import { getPackageJSONObject } from 'apis/packages'; 7 | import { bounce } from 'constants/animations'; 8 | import { filterExisitingStacks } from 'utils/array'; 9 | import { getDependencies, getPackageJsonURL, removeSubModules } from 'utils/packageJson'; 10 | import { capitalize, makeIntoSlug } from 'utils/string'; 11 | 12 | interface ReadRepositoryProps { 13 | stackHandler: (p: string[]) => void; 14 | inputPopupHandler: (b: boolean) => void; 15 | } 16 | 17 | const ReadRepository = ({ stackHandler, inputPopupHandler }: ReadRepositoryProps) => { 18 | const [address, setAddress] = useState(''); 19 | 20 | const updateAddress = (e: ChangeEvent) => { 21 | setAddress(e.currentTarget.value); 22 | }; 23 | 24 | const getStacks = async (path: string) => { 25 | try { 26 | const data = await getPackageJSONObject(path); 27 | 28 | const stackSlugs = removeSubModules(getDependencies(data)).map(makeIntoSlug); 29 | const existingStacks = filterExisitingStacks(stackSlugs).map(capitalize); 30 | 31 | stackHandler(existingStacks); 32 | } catch (error) { 33 | alert(error); 34 | } finally { 35 | inputPopupHandler(false); 36 | } 37 | }; 38 | 39 | const handleKeyPress = (e: KeyboardEvent) => { 40 | if (e.key === 'Enter') { 41 | const packageJSONPath = getPackageJsonURL(address); 42 | 43 | getStacks(packageJSONPath); 44 | } 45 | }; 46 | 47 | return ( 48 | 75 | e.stopPropagation()} 80 | onChange={updateAddress} 81 | onKeyDown={handleKeyPress} 82 | disableUnderline 83 | /> 84 | 85 | ); 86 | }; 87 | 88 | export default ReadRepository; 89 | -------------------------------------------------------------------------------- /src/components/read-repository/index.ts: -------------------------------------------------------------------------------- 1 | import ReadRepository from './ReadRepository'; 2 | 3 | export default ReadRepository; 4 | -------------------------------------------------------------------------------- /src/components/require-state/RequireState.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react'; 2 | 3 | import Cute404 from 'pages/page404'; 4 | import { useStacks } from 'providers/StacksProvider'; 5 | 6 | interface RequireStateProps { 7 | component: ReactNode; 8 | } 9 | 10 | const RequireState = ({ component }: RequireStateProps) => { 11 | const { stacks } = useStacks(); 12 | 13 | if (!stacks) { 14 | return ; 15 | } 16 | 17 | return component; 18 | }; 19 | 20 | export default RequireState; 21 | -------------------------------------------------------------------------------- /src/components/require-state/index.ts: -------------------------------------------------------------------------------- 1 | import RequireState from './RequireState'; 2 | 3 | export default RequireState; 4 | -------------------------------------------------------------------------------- /src/components/stack-group/StackGroup.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef } from 'react'; 2 | 3 | import { Box } from '@mui/material'; 4 | 5 | import Stack from 'components/stack/Stack'; 6 | 7 | import type { BgColorOption } from 'types/backgroundColors'; 8 | 9 | interface StacksProps { 10 | color: BgColorOption; 11 | selecteds: string[]; 12 | } 13 | 14 | const StackGroup = forwardRef(({ color, selecteds }, targetRef) => { 15 | return ( 16 | 30 | {selecteds.map((select: string) => ( 31 | 32 | ))} 33 | 34 | ); 35 | }); 36 | StackGroup.displayName = 'StackGroup'; 37 | 38 | export default StackGroup; 39 | -------------------------------------------------------------------------------- /src/components/stack-group/index.ts: -------------------------------------------------------------------------------- 1 | import StackGroup from './StackGroup'; 2 | 3 | export default StackGroup; 4 | -------------------------------------------------------------------------------- /src/components/stack/Stack.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { Box, Typography } from '@mui/material'; 3 | 4 | import { getIconDetail } from 'utils/allIconInfo'; 5 | 6 | const DropdownSvg = styled.div<{ hex: string }>` 7 | width: 50px; 8 | height: 50px; 9 | object-fit: cover; 10 | 11 | svg { 12 | width: 50px; 13 | height: 50px; 14 | } 15 | 16 | path { 17 | fill: ${(props) => props.hex}; 18 | } 19 | `; 20 | 21 | interface StackProps { 22 | stackName: string; 23 | } 24 | 25 | const Stack = ({ stackName }: StackProps) => { 26 | const { title, svg, hex } = getIconDetail(stackName); 27 | 28 | return ( 29 | 30 | 41 | 42 | 43 | {title} 44 | 45 | ); 46 | }; 47 | 48 | export default Stack; 49 | -------------------------------------------------------------------------------- /src/components/stack/index.ts: -------------------------------------------------------------------------------- 1 | import Stack from './Stack'; 2 | 3 | export default Stack; 4 | -------------------------------------------------------------------------------- /src/components/stackticon-images/StackticonImages.tsx: -------------------------------------------------------------------------------- 1 | import LandingTitleImage from '../../assets/images/landing-image.png'; 2 | import LandingLabelImage from '../../assets/images/landing-label.png'; 3 | 4 | const LandingImage = () => ( 5 | landing-image 6 | ); 7 | 8 | const LandingLabel = ({ styles }: { styles?: Record }) => ( 9 | landing-label 19 | ); 20 | 21 | export { LandingImage, LandingLabel }; 22 | -------------------------------------------------------------------------------- /src/components/toast/Toast.tsx: -------------------------------------------------------------------------------- 1 | import { Alert, Snackbar } from '@mui/material'; 2 | 3 | import type { ToastProps } from 'types/toast'; 4 | 5 | const Toast = ({ type, message, open, close, duration = 1500 }: ToastProps) => { 6 | return ( 7 | 13 | {message} 14 | 15 | ); 16 | }; 17 | 18 | export default Toast; 19 | -------------------------------------------------------------------------------- /src/components/toast/index.ts: -------------------------------------------------------------------------------- 1 | import Toast from './Toast'; 2 | 3 | export default Toast; 4 | -------------------------------------------------------------------------------- /src/constants/animations.ts: -------------------------------------------------------------------------------- 1 | import { keyframes } from '@emotion/react'; 2 | 3 | export const fadeFromLeft = keyframes` 4 | from { 5 | opacity: 0; 6 | transform: translateX(-100px); 7 | } 8 | to { 9 | opacity: 1; 10 | transform: translateZ(0); 11 | } 12 | `; 13 | 14 | export const fadeFromRight = keyframes` 15 | from { 16 | opacity: 0; 17 | transform: translateX(100px); 18 | } 19 | to { 20 | opacity: 1; 21 | transform: translateZ(0); 22 | } 23 | `; 24 | 25 | const w = 96; 26 | const h = 112; 27 | const xspace = w / 2; 28 | const yspace = h / 4 - 1; 29 | 30 | export const tetromino1 = keyframes` 31 | 0%, 40% { 32 | /* compose logo *//* 1 on 3 *//* L-shape */ 33 | transform: translate(0,0); 34 | } 50% { 35 | /* pre-box */ 36 | transform: translate(${xspace}px,-${yspace}px); 37 | } 60%, 100% { 38 | /* box *//* compose logo */ 39 | transform: translate(${xspace * 2}px,0); 40 | } 41 | `; 42 | 43 | export const tetromino2 = keyframes` 44 | 0%, 20% { 45 | /* compose logo *//* 1 on 3 */ 46 | transform: translate(${xspace * 2}px, 0px); 47 | } 40%, 100% { 48 | /* L-shape *//* box *//* compose logo */ 49 | transform: translate(${xspace * 3}px, ${yspace}px); 50 | } 51 | `; 52 | 53 | export const tetromino3 = keyframes` 54 | 0% { 55 | /* compose logo */ 56 | transform: translate(${xspace * 3}px, ${yspace}px); 57 | } 20%, 60% { 58 | /* 1 on 3 *//* L-shape *//* box */ 59 | transform: translate(${xspace * 2}px, ${yspace * 2}px); 60 | } 90%, 100% { 61 | /* compose logo */ 62 | transform: translate(${xspace}px, ${yspace}px); 63 | } 64 | `; 65 | 66 | export const tetromino4 = keyframes` 67 | 0%, 60% { 68 | /* compose logo *//* 1 on 3 *//* L-shape *//* box */ 69 | transform: translate(${xspace}px, ${yspace}px); 70 | } 90%, 100% { 71 | /* compose logo */ 72 | transform: translate(0, 0); 73 | } 74 | `; 75 | 76 | export const wiggling = keyframes` 77 | 0% { 78 | transform: rotate(-7deg); 79 | } 80 | 50% { 81 | transform: rotate(7deg); 82 | } 83 | 100% { 84 | transform: rotate(-7deg); 85 | } 86 | `; 87 | 88 | export const bounce = keyframes` 89 | 0% { 90 | transform: scale(1, 0.7) translateY(70px) translateX(280px); 91 | } 92 | 10% { 93 | transform: scale(1, 0.85) translateY(70px) translateX(280px); 94 | } 95 | 30% { 96 | transform: scale(1, 1.1) translateY(70px) translateX(280px); 97 | } 98 | 50% { 99 | transform: scale(1, 0.95) translateY(70px) translateX(280px); 100 | } 101 | 60% { 102 | transform: scale(1, 1.01) translateY(70px) translateX(280px); 103 | } 104 | 70% { 105 | transform: scale(1, 0.99) translateY(70px) translateX(280px); 106 | } 107 | 100% { 108 | transform: scale(1, 1) translateY(70px) translateX(280px); 109 | } 110 | `; 111 | 112 | export const RandomRotate = keyframes` 113 | 0% { 114 | rotate: 0deg; 115 | } 116 | 1% { 117 | rotate: 80deg; 118 | } 119 | 99% { 120 | rotate: 80deg; 121 | } 122 | 100% { 123 | rotate: 0deg; 124 | } 125 | `; 126 | -------------------------------------------------------------------------------- /src/constants/constants.ts: -------------------------------------------------------------------------------- 1 | import type { MakeUnionType } from 'types/common'; 2 | 3 | export const APP_NAME = 'stackticon'; 4 | 5 | export const GITHUB_LINK = 'https://github.com/msdio/stackticon'; 6 | 7 | export const DEPENDENCY = 'dependencies'; 8 | export const DEV_DEPENDENCY = 'devDependencies'; 9 | 10 | export const COPY_OPTIONS = { 11 | COPY_README: 'copy for readme', 12 | COPY_LINK: 'copy link only', 13 | COPY_TAG: 'copy img tag', 14 | } as const; 15 | export type CopyOptionType = MakeUnionType; 16 | -------------------------------------------------------------------------------- /src/constants/icons.tsx: -------------------------------------------------------------------------------- 1 | import { SvgIcon } from '@mui/material'; 2 | 3 | import { Svg } from './svgs'; 4 | 5 | export const JavascriptIcon = () => ( 6 | 7 | 8 | 9 | ); 10 | 11 | export const ReactIcon = () => ( 12 | 13 | 14 | 15 | ); 16 | 17 | export const NodeJSIcon = () => ( 18 | 19 | 20 | 21 | ); 22 | 23 | export const SpringIcon = () => ( 24 | 25 | 26 | 27 | ); 28 | 29 | export const BetaBadge = () => ( 30 | 31 | 35 | 39 | 40 | ); 41 | -------------------------------------------------------------------------------- /src/constants/svgs.ts: -------------------------------------------------------------------------------- 1 | export const Svg = { 2 | github: 3 | 'M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z', 4 | coffee: 5 | 'M88 0C74.7 0 64 10.7 64 24c0 38.9 23.4 59.4 39.1 73.1l1.1 1C120.5 112.3 128 119.9 128 136c0 13.3 10.7 24 24 24s24-10.7 24-24c0-38.9-23.4-59.4-39.1-73.1l-1.1-1C119.5 47.7 112 40.1 112 24c0-13.3-10.7-24-24-24zM32 192c-17.7 0-32 14.3-32 32V416c0 53 43 96 96 96H288c53 0 96-43 96-96h16c61.9 0 112-50.1 112-112s-50.1-112-112-112H352 32zm352 64h16c26.5 0 48 21.5 48 48s-21.5 48-48 48H384V256zM224 24c0-13.3-10.7-24-24-24s-24 10.7-24 24c0 38.9 23.4 59.4 39.1 73.1l1.1 1C232.5 112.3 240 119.9 240 136c0 13.3 10.7 24 24 24s24-10.7 24-24c0-38.9-23.4-59.4-39.1-73.1l-1.1-1C231.5 47.7 224 40.1 224 24z', 6 | spring: { 7 | path: 'M21.8537 1.4158a10.4504 10.4504 0 0 1-1.284 2.2471A11.9666 11.9666 0 1 0 3.8518 20.7757l.4445.3951a11.9543 11.9543 0 0 0 19.6316-8.2971c.3457-3.0126-.568-6.8649-2.0743-11.458zM5.5805 20.8745a1.0174 1.0174 0 1 1-.1482-1.4323 1.0396 1.0396 0 0 1 .1482 1.4323zm16.1991-3.5806c-2.9385 3.9263-9.2601 2.5928-13.2852 2.7904 0 0-.7161.0494-1.4323.1481 0 0 .2717-.1234.6174-.2469 2.8398-.9877 4.1732-1.1853 5.9018-2.0743 3.2349-1.6545 6.4698-5.2844 7.1118-9.0379-1.2347 3.6053-4.9881 6.7167-8.3959 7.9761-2.3459.8643-6.5685 1.7039-6.5685 1.7039l-.1729-.0988c-2.8645-1.4076-2.9632-7.6304 2.2718-9.6306 2.2966-.889 4.4696-.395 6.9637-.9877 2.6422-.6174 5.7043-2.5929 6.939-5.1857 1.3828 4.1732 3.062 10.643.0493 14.6434z', 8 | hex: '#6DB33F', 9 | }, 10 | javascript: { 11 | path: 'M0 0h24v24H0V0zm22.034 18.276c-.175-1.095-.888-2.015-3.003-2.873-.736-.345-1.554-.585-1.797-1.14-.091-.33-.105-.51-.046-.705.15-.646.915-.84 1.515-.66.39.12.75.42.976.9 1.034-.676 1.034-.676 1.755-1.125-.27-.42-.404-.601-.586-.78-.63-.705-1.469-1.065-2.834-1.034l-.705.089c-.676.165-1.32.525-1.71 1.005-1.14 1.291-.811 3.541.569 4.471 1.365 1.02 3.361 1.244 3.616 2.205.24 1.17-.87 1.545-1.966 1.41-.811-.18-1.26-.586-1.755-1.336l-1.83 1.051c.21.48.45.689.81 1.109 1.74 1.756 6.09 1.666 6.871-1.004.029-.09.24-.705.074-1.65l.046.067zm-8.983-7.245h-2.248c0 1.938-.009 3.864-.009 5.805 0 1.232.063 2.363-.138 2.711-.33.689-1.18.601-1.566.48-.396-.196-.597-.466-.83-.855-.063-.105-.11-.196-.127-.196l-1.825 1.125c.305.63.75 1.172 1.324 1.517.855.51 2.004.675 3.207.405.783-.226 1.458-.691 1.811-1.411.51-.93.402-2.07.397-3.346.012-2.054 0-4.109 0-6.179l.004-.056z', 12 | hex: '#F7DF1E', 13 | }, 14 | react: { 15 | path: 'M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z', 16 | hex: '#61DAFB', 17 | }, 18 | nodejs: { 19 | path: 'M11.998,24c-0.321,0-0.641-0.084-0.922-0.247l-2.936-1.737c-0.438-0.245-0.224-0.332-0.08-0.383 c0.585-0.203,0.703-0.25,1.328-0.604c0.065-0.037,0.151-0.023,0.218,0.017l2.256,1.339c0.082,0.045,0.197,0.045,0.272,0l8.795-5.076 c0.082-0.047,0.134-0.141,0.134-0.238V6.921c0-0.099-0.053-0.192-0.137-0.242l-8.791-5.072c-0.081-0.047-0.189-0.047-0.271,0 L3.075,6.68C2.99,6.729,2.936,6.825,2.936,6.921v10.15c0,0.097,0.054,0.189,0.139,0.235l2.409,1.392 c1.307,0.654,2.108-0.116,2.108-0.89V7.787c0-0.142,0.114-0.253,0.256-0.253h1.115c0.139,0,0.255,0.112,0.255,0.253v10.021 c0,1.745-0.95,2.745-2.604,2.745c-0.508,0-0.909,0-2.026-0.551L2.28,18.675c-0.57-0.329-0.922-0.945-0.922-1.604V6.921 c0-0.659,0.353-1.275,0.922-1.603l8.795-5.082c0.557-0.315,1.296-0.315,1.848,0l8.794,5.082c0.57,0.329,0.924,0.944,0.924,1.603 v10.15c0,0.659-0.354,1.273-0.924,1.604l-8.794,5.078C12.643,23.916,12.324,24,11.998,24z M19.099,13.993 c0-1.9-1.284-2.406-3.987-2.763c-2.731-0.361-3.009-0.548-3.009-1.187c0-0.528,0.235-1.233,2.258-1.233 c1.807,0,2.473,0.389,2.747,1.607c0.024,0.115,0.129,0.199,0.247,0.199h1.141c0.071,0,0.138-0.031,0.186-0.081 c0.048-0.054,0.074-0.123,0.067-0.196c-0.177-2.098-1.571-3.076-4.388-3.076c-2.508,0-4.004,1.058-4.004,2.833 c0,1.925,1.488,2.457,3.895,2.695c2.88,0.282,3.103,0.703,3.103,1.269c0,0.983-0.789,1.402-2.642,1.402 c-2.327,0-2.839-0.584-3.011-1.742c-0.02-0.124-0.126-0.215-0.253-0.215h-1.137c-0.141,0-0.254,0.112-0.254,0.253 c0,1.482,0.806,3.248,4.655,3.248C17.501,17.007,19.099,15.91,19.099,13.993z', 20 | hex: '#339933', 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /src/hooks/useIntersectionObserver.tsx: -------------------------------------------------------------------------------- 1 | import type { RefObject } from 'react'; 2 | import { useEffect, useState } from 'react'; 3 | 4 | interface ObserverProps { 5 | ref: RefObject; 6 | threshold: number; 7 | } 8 | 9 | const useIntersectionObserver = ({ ref, threshold }: ObserverProps) => { 10 | const [observing, setObserving] = useState(false); 11 | 12 | useEffect(() => { 13 | const observer = new IntersectionObserver( 14 | (entries: IntersectionObserverEntry[]) => { 15 | entries[0].isIntersecting ? setObserving(true) : setObserving(false); 16 | }, 17 | { threshold: threshold }, 18 | ); 19 | 20 | observer.observe(ref.current as HTMLDivElement); 21 | }, [ref, threshold]); 22 | 23 | return observing; 24 | }; 25 | 26 | export default useIntersectionObserver; 27 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | 4 | import App from './App'; 5 | 6 | const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); 7 | root.render( 8 | 9 | 10 | , 11 | ); 12 | -------------------------------------------------------------------------------- /src/mock/stacks.ts: -------------------------------------------------------------------------------- 1 | export const Stacks = [ 2 | { label: 'React', id: 1 }, 3 | { label: 'MUI', id: 2 }, 4 | { label: 'Firebase', id: 3 }, 5 | { label: 'TypeScript', id: 4 }, 6 | { label: 'Github', id: 5 }, 7 | { label: 'GitHub Pages', id: 6 }, 8 | { label: 'Visual Studio Code', id: 7 }, 9 | { label: 'Notion', id: 8 }, 10 | { label: 'Javascript', id: 9 }, 11 | { label: 'HTML5', id: 10 }, 12 | { label: 'CSS3', id: 11 }, 13 | { label: 'Tailwind CSS', id: 12 }, 14 | { label: 'Next.js', id: 13 }, 15 | { label: 'C++', id: 14 }, 16 | { label: 'Spring', id: 15 }, 17 | { label: 'Spring Boot', id: 16 }, 18 | { label: 'Spring Security', id: 17 }, 19 | { label: 'Angular', id: 18 }, 20 | { label: 'Node.js', id: 19 }, 21 | { label: 'Ionic', id: 20 }, 22 | { label: 'Capacitor', id: 21 }, 23 | { label: 'styled-components', id: 22 }, 24 | { label: 'Slack', id: 23 }, 25 | { label: 'npm', id: 24 }, 26 | { label: 'Yarn', id: 25 }, 27 | { label: 'Redux', id: 26 }, 28 | { label: 'MobX', id: 27 }, 29 | { label: 'Amazon AWS', id: 28 }, 30 | { label: 'Amazon S3', id: 29 }, 31 | { label: 'Amazon RDS', id: 30 }, 32 | { label: 'Amazon EC2', id: 31 }, 33 | { label: 'AWS Lambda', id: 32 }, 34 | { label: 'Express', id: 33 }, 35 | { label: 'MySQL', id: 34 }, 36 | { label: 'Microsoft Azure', id: 35 }, 37 | ]; 38 | -------------------------------------------------------------------------------- /src/pages/background-colors/BackgroundColors.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/no-children-prop */ 2 | import { useRef } from 'react'; 3 | import { useNavigate } from 'react-router'; 4 | 5 | import { Box, Stack } from '@mui/material'; 6 | 7 | import BackgroundWithCircle from '@common/background-with-circle'; 8 | import HeadingText from '@common/heading-text'; 9 | import Header from 'components/header'; 10 | import HoverCard from 'components/hover-card'; 11 | import StackGroup from 'components/stack-group'; 12 | import { useStacks } from 'providers/StacksProvider'; 13 | 14 | import type { BgColorOption } from 'types/backgroundColors'; 15 | 16 | const BackgroundColors = () => { 17 | const navigate = useNavigate(); 18 | const targetRef = useRef(null); 19 | const { stacks } = useStacks(); 20 | 21 | const submitColor = (color: BgColorOption) => { 22 | if (color) { 23 | navigate(`/loading/${color}`); 24 | } 25 | }; 26 | 27 | return ( 28 | 29 |
30 | 31 | 40 | Choose Color 41 | 42 | 43 | submitColor('black')} 47 | children={} 48 | /> 49 | submitColor('white')} 53 | children={} 54 | /> 55 | 56 | 57 | 58 | ); 59 | }; 60 | 61 | export default BackgroundColors; 62 | -------------------------------------------------------------------------------- /src/pages/background-colors/index.ts: -------------------------------------------------------------------------------- 1 | import BackgroundColors from './BackgroundColors'; 2 | 3 | export default BackgroundColors; 4 | -------------------------------------------------------------------------------- /src/pages/home/Home.tsx: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react'; 2 | 3 | import { Box } from '@mui/material'; 4 | 5 | import Header from 'components/header'; 6 | import Landing from 'pages/home/landing'; 7 | 8 | import * as Guide from './guides'; 9 | 10 | const Home = () => { 11 | const observerRef = useRef(null); 12 | 13 | return ( 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | }; 26 | 27 | export default Home; 28 | -------------------------------------------------------------------------------- /src/pages/home/guides/GuideSample.tsx: -------------------------------------------------------------------------------- 1 | import type { RefObject } from 'react'; 2 | 3 | import { Box, Container, Typography, useMediaQuery } from '@mui/material'; 4 | 5 | import { fadeFromLeft, fadeFromRight } from 'constants/animations'; 6 | import useIntersectionObserver from 'hooks/useIntersectionObserver'; 7 | 8 | interface GuideProps { 9 | observerRef: RefObject; 10 | } 11 | 12 | const GuideSample = ({ observerRef }: GuideProps) => { 13 | const isMobile = useMediaQuery('(max-width: 900px)'); 14 | const activeAnimation = useIntersectionObserver({ ref: observerRef, threshold: 0.5 }); 15 | 16 | return ( 17 | 28 | 35 | create your own skill sets 41 | 42 | 48 | Recognize in a glance 49 | 50 | 51 | ); 52 | }; 53 | 54 | export default GuideSample; 55 | -------------------------------------------------------------------------------- /src/pages/home/guides/GuideUsage.tsx: -------------------------------------------------------------------------------- 1 | import type { RefObject } from 'react'; 2 | 3 | import { Box, Container, Typography, useMediaQuery } from '@mui/material'; 4 | 5 | import { fadeFromLeft, fadeFromRight } from 'constants/animations'; 6 | import useIntersectionObserver from 'hooks/useIntersectionObserver'; 7 | 8 | interface GuideProps { 9 | observerRef: RefObject; 10 | } 11 | 12 | const GuideUsage = ({ observerRef }: GuideProps) => { 13 | const isMobile = useMediaQuery('(max-width: 900px)'); 14 | const activeAnimation = useIntersectionObserver({ ref: observerRef, threshold: 0.5 }); 15 | 16 | return ( 17 | 28 | 35 | Simple, Fast 36 | 37 | just input your skills and get it 38 | 39 | 40 | 48 | choose stacks gif 54 | 55 | 56 | ); 57 | }; 58 | 59 | export default GuideUsage; 60 | -------------------------------------------------------------------------------- /src/pages/home/guides/index.ts: -------------------------------------------------------------------------------- 1 | import GuideSample from './GuideSample'; 2 | import GuideUsage from './GuideUsage'; 3 | 4 | export { GuideSample, GuideUsage }; 5 | -------------------------------------------------------------------------------- /src/pages/home/index.ts: -------------------------------------------------------------------------------- 1 | import Home from './Home'; 2 | 3 | export default Home; 4 | -------------------------------------------------------------------------------- /src/pages/home/landing/Landing.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from 'react'; 2 | import { useNavigate } from 'react-router'; 3 | 4 | import { Box, Button, Typography, useMediaQuery } from '@mui/material'; 5 | 6 | import BackgroundWithCircle from '@common/background-with-circle'; 7 | import DraggableIcon from 'components/draggable-icon'; 8 | import Input from 'components/input'; 9 | import { JavascriptIcon, NodeJSIcon, ReactIcon, SpringIcon } from 'constants/icons'; 10 | import { useStacks } from 'providers/StacksProvider'; 11 | 12 | import * as Images from '../../../components/stackticon-images/StackticonImages'; 13 | 14 | const Landing = () => { 15 | const [buttonClicked, setButtonClicked] = useState(false); 16 | const containerRef = useRef(null); 17 | const navigate = useNavigate(); 18 | const { stacks } = useStacks(); 19 | const isMobile = useMediaQuery('(max-width: 900px)'); 20 | 21 | const submitStacks = () => { 22 | if (!buttonClicked && stacks.length > 0) { 23 | navigate('/backgrounds'); 24 | setButtonClicked(true); 25 | } 26 | }; 27 | 28 | return ( 29 | 40 | 51 | 52 | 53 | 54 | 55 | make skill sets for your project 56 | 57 | 58 | 69 | 70 | 87 | 88 | 89 | } bottom='10%' right='5%' /> 90 | } top='20%' right='20%' /> 91 | } top='30%' left='13%' /> 92 | } bottom='17%' left='25%' /> 93 | 94 | ); 95 | }; 96 | 97 | export default Landing; 98 | -------------------------------------------------------------------------------- /src/pages/home/landing/index.ts: -------------------------------------------------------------------------------- 1 | import Landing from './Landing'; 2 | 3 | export default Landing; 4 | -------------------------------------------------------------------------------- /src/pages/page404/Cute404.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from 'react-router'; 2 | 3 | import { keyframes } from '@emotion/react'; 4 | import styled from '@emotion/styled'; 5 | // @import url('https://fonts.googleapis.com/css?family=Abril+Fatface|Lato'); 6 | 7 | const whiteColor = '#EDEDED'; 8 | const grayColor = '#BFC0C0'; 9 | const darkColor = '#585959'; 10 | const lightColor = '#D3DEEA'; 11 | 12 | // $big: 'Abril Fatface', serif; 13 | // $body: 'Lato', sans-serif; 14 | 15 | const Container = styled.div` 16 | position: absolute; 17 | background: ${lightColor}; 18 | height: 100vh; 19 | width: 100vw; 20 | /*text styling*/ 21 | h1 { 22 | font-family: $big; 23 | color: ${whiteColor}; 24 | text-align: center; 25 | font-size: 9em; 26 | margin: 0; 27 | text-shadow: -1px 0 ${grayColor}, 0 1px ${grayColor}, 1px 0 ${grayColor}, 0 -1px ${grayColor}; 28 | } 29 | h3 { 30 | font-family: $body; 31 | font-size: 2em; 32 | text-transform: uppercase; 33 | text-align: center; 34 | color: ${grayColor}; 35 | margin-top: -20px; 36 | font-weight: 900; 37 | } 38 | p { 39 | text-align: center; 40 | font-family: $body; 41 | color: ${darkColor}; 42 | font-size: 0.6em; 43 | margin-top: -20px; 44 | text-transform: uppercase; 45 | } 46 | `; 47 | 48 | const scale = keyframes` 49 | 0% { 50 | transform: scale(1); 51 | } 52 | 50% { 53 | transform: scale(1.1); 54 | } 55 | 100% { 56 | transform: scale(1); 57 | } 58 | 59 | `; 60 | 61 | const float = keyframes` 62 | 50% { 63 | transform: translateY(15px); 64 | }`; 65 | 66 | const Top = styled.div` 67 | margin-top: 30px; 68 | `; 69 | 70 | const GhostContainer = styled.div` 71 | margin: 0 auto; 72 | position: relative; 73 | width: 250px; 74 | height: 250px; 75 | margin-top: -40px; 76 | `; 77 | const Ghost = styled.div` 78 | width: 50%; 79 | height: 53%; 80 | left: 25%; 81 | top: 10%; 82 | position: absolute; 83 | border-radius: 50% 50% 0 0; 84 | background: ${whiteColor}; 85 | border: 1px solid ${grayColor}; 86 | border-bottom: none; 87 | animation: ${float} 2s ease-out infinite; 88 | `; 89 | 90 | const GhostCopy = styled.div` 91 | width: 50%; 92 | height: 53%; 93 | left: 25%; 94 | top: 10%; 95 | position: absolute; 96 | border-radius: 50% 50% 0 0; 97 | background: ${whiteColor}; 98 | border: 1px solid ${grayColor}; 99 | border-bottom: none; 100 | animation: ${float} 2s ease-out infinite; 101 | z-index: 0; 102 | `; 103 | 104 | const Face = styled.div` 105 | position: absolute; 106 | width: 100%; 107 | height: 60%; 108 | top: 20%; 109 | `; 110 | const Eye = styled.div` 111 | position: absolute; 112 | background: ${darkColor}; 113 | width: 13px; 114 | height: 13px; 115 | border-radius: 50%; 116 | top: 40%; 117 | left: 25%; 118 | `; 119 | const EyeRight = styled.div` 120 | position: absolute; 121 | background: ${darkColor}; 122 | width: 13px; 123 | height: 13px; 124 | border-radius: 50%; 125 | top: 40%; 126 | right: 25%; 127 | `; 128 | const Mouth = styled.div` 129 | position: absolute; 130 | top: 50%; 131 | left: 45%; 132 | width: 10px; 133 | height: 10px; 134 | border: 3px solid; 135 | border-radius: 50%; 136 | border-color: transparent ${darkColor} ${darkColor} transparent; 137 | transform: rotate(45deg); 138 | `; 139 | 140 | const One = styled.div` 141 | position: absolute; 142 | background: ${whiteColor}; 143 | top: 85%; 144 | width: 25%; 145 | height: 23%; 146 | border: 1px solid ${grayColor}; 147 | z-index: 0; 148 | 149 | border-radius: 0 0 100% 30%; 150 | left: -1px; 151 | `; 152 | 153 | const Two = styled.div` 154 | position: absolute; 155 | background: ${whiteColor}; 156 | top: 85%; 157 | width: 25%; 158 | height: 23%; 159 | border: 1px solid ${grayColor}; 160 | z-index: 0; 161 | 162 | left: 23%; 163 | border-radius: 0 0 50% 50%; 164 | `; 165 | 166 | const Three = styled.div` 167 | position: absolute; 168 | background: ${whiteColor}; 169 | top: 85%; 170 | width: 25%; 171 | height: 23%; 172 | border: 1px solid ${grayColor}; 173 | z-index: 0; 174 | 175 | left: 50%; 176 | border-radius: 0 0 50% 50%; 177 | `; 178 | const Four = styled.div` 179 | position: absolute; 180 | background: ${whiteColor}; 181 | top: 85%; 182 | width: 25%; 183 | height: 23%; 184 | border: 1px solid ${grayColor}; 185 | z-index: 0; 186 | 187 | left: 74.5%; 188 | border-radius: 0 0 30% 100%; 189 | `; 190 | 191 | const Shadow = styled.div` 192 | position: absolute; 193 | width: 30%; 194 | height: 7%; 195 | background: ${grayColor}; 196 | left: 35%; 197 | top: 80%; 198 | border-radius: 50%; 199 | animation: ${scale} 2s infinite; 200 | `; 201 | 202 | const Bottom = styled.div` 203 | margin-top: 10px; 204 | `; 205 | 206 | const Buttons = styled.div` 207 | display: flex; 208 | align-items: center; 209 | justify-content: center; 210 | margin-top: 10px; 211 | `; 212 | 213 | /*search style*/ 214 | 215 | const Btn = styled.button` 216 | background: ${whiteColor}; 217 | padding: 15px 20px; 218 | margin: 5px; 219 | color: ${darkColor}; 220 | font-family: $body; 221 | text-transform: uppercase; 222 | font-size: 0.6em; 223 | letter-spacing: 1px; 224 | border: 0; 225 | &:hover { 226 | background: ${grayColor}; 227 | transition: all 0.4s ease-out; 228 | } 229 | `; 230 | 231 | const Footer = styled.div` 232 | position: absolute; 233 | bottom: 0; 234 | right: 0; 235 | text-align: center; 236 | font-size: 0.8em; 237 | text-transform: uppercase; 238 | padding: 10px; 239 | color: #ea7996; 240 | letter-spacing: 3px; 241 | font-family: $body; 242 | a { 243 | color: #ffffff; 244 | text-decoration: none; 245 | &:hover { 246 | color: #7d7d7d; 247 | } 248 | } 249 | `; 250 | 251 | const Cute404 = () => { 252 | const navigate = useNavigate(); 253 | 254 | const goBackPage = () => { 255 | navigate(-1); 256 | }; 257 | 258 | const goToHomePage = () => { 259 | navigate('/'); 260 | }; 261 | 262 | return ( 263 | 264 |
265 | 266 |

404

267 |

page not found

268 |
269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 |

Boo, looks like a ghost stole this page!

287 | 288 | Back 289 | Home 290 | 291 |
292 | 293 |
294 |

295 | made by julie ♡ 296 |

297 |
298 |
299 | ); 300 | }; 301 | 302 | export default Cute404; 303 | -------------------------------------------------------------------------------- /src/pages/page404/index.ts: -------------------------------------------------------------------------------- 1 | import Cute404 from './Cute404'; 2 | 3 | export default Cute404; 4 | -------------------------------------------------------------------------------- /src/pages/result/Result.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { useLocation, useNavigate } from 'react-router'; 3 | 4 | import styled from '@emotion/styled'; 5 | import { Box } from '@mui/material'; 6 | import useMediaQuery from '@mui/material/useMediaQuery'; 7 | 8 | import BackgroundWithCircle from '@common/background-with-circle'; 9 | import Header from 'components/header'; 10 | import { LandingLabel } from 'components/stackticon-images/StackticonImages'; 11 | import Toast from 'components/toast'; 12 | 13 | import BottomButtons from './bottom-buttons'; 14 | 15 | const ResultImage = styled.img` 16 | width: 80vw; 17 | max-width: 700px; 18 | margin: 30px; 19 | `; 20 | 21 | const Result = () => { 22 | const { state } = useLocation(); 23 | const navigate = useNavigate(); 24 | const isMobile = useMediaQuery('(max-width: 740px)'); 25 | const [openToast, setOpenToast] = useState(false); 26 | 27 | const closeToast = () => { 28 | setOpenToast(false); 29 | }; 30 | 31 | window.addEventListener('popstate', () => { 32 | navigate('/'); 33 | }); 34 | 35 | return ( 36 | 37 |
38 | 39 | 40 | 41 | 51 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | ); 65 | }; 66 | 67 | export default Result; 68 | -------------------------------------------------------------------------------- /src/pages/result/bottom-buttons/BottomButtons.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router'; 2 | 3 | import { Box, Button, useMediaQuery } from '@mui/material'; 4 | 5 | import ButtonOptions from 'components/button-options'; 6 | 7 | import type { LocationState } from 'types/location'; 8 | 9 | interface BottomButtonProps { 10 | state: LocationState; 11 | setOpenToast: React.Dispatch>; 12 | } 13 | 14 | export const BottomButtons = ({ state, setOpenToast }: BottomButtonProps) => { 15 | const isMobile = useMediaQuery('(max-width: 740px)'); 16 | 17 | return ( 18 | 26 | 27 | 28 | 29 | 45 | 46 | 47 | ); 48 | }; 49 | -------------------------------------------------------------------------------- /src/pages/result/bottom-buttons/index.ts: -------------------------------------------------------------------------------- 1 | import { BottomButtons } from './BottomButtons'; 2 | 3 | export default BottomButtons; 4 | -------------------------------------------------------------------------------- /src/pages/result/index.ts: -------------------------------------------------------------------------------- 1 | import Result from './Result'; 2 | 3 | export default Result; 4 | -------------------------------------------------------------------------------- /src/providers/StacksProvider.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react'; 2 | import { createContext, useContext, useState } from 'react'; 3 | 4 | interface StackContextType { 5 | stacks: string[]; 6 | update: (s: string[]) => void; 7 | } 8 | 9 | const StackContext = createContext(null); 10 | 11 | interface StacksProviderProps { 12 | children: ReactNode; 13 | } 14 | const StacksProvider = ({ children }: StacksProviderProps) => { 15 | const [stacks, setStacks] = useState([]); 16 | 17 | const update = (stack: string[]) => { 18 | setStacks([...stack]); 19 | }; 20 | 21 | return {children}; 22 | }; 23 | 24 | export const useStacks = () => { 25 | return useContext(StackContext) as StackContextType; 26 | }; 27 | 28 | export default StacksProvider; 29 | -------------------------------------------------------------------------------- /src/services/firebase/initialize.ts: -------------------------------------------------------------------------------- 1 | import { initializeApp } from 'firebase/app'; 2 | import { getStorage } from 'firebase/storage'; 3 | 4 | const firebaseConfig = { 5 | apiKey: import.meta.env.VITE_API_KEY, 6 | authDomain: import.meta.env.VITE_AUTHDOMAIN, 7 | projectId: import.meta.env.VITE_PROJECT_ID, 8 | storageBucket: import.meta.env.VITE_STORAGE_BUCKET, 9 | messagingSenderId: import.meta.env.VITE_MESSAGING_SENDER_ID, 10 | appId: import.meta.env.VITE_APP_ID, 11 | measurementId: import.meta.env.VITE_MEASUREMENT_ID, 12 | }; 13 | 14 | const app = initializeApp(firebaseConfig); 15 | export const storage = getStorage(app); 16 | -------------------------------------------------------------------------------- /src/services/firebase/storage.ts: -------------------------------------------------------------------------------- 1 | import { getDownloadURL, ref, uploadBytes } from 'firebase/storage'; 2 | 3 | import { storage } from './initialize'; 4 | 5 | import type { StorageReference } from 'firebase/storage'; 6 | 7 | const makeImageRef = () => { 8 | const miliseconds = new Date().getTime().toString(); 9 | const baseRef = ref(storage, `images/${miliseconds}`); 10 | return baseRef; 11 | }; 12 | 13 | const downloadImageURL = async (downLoadRef: StorageReference) => { 14 | const url = await getDownloadURL(downLoadRef); 15 | if (url) { 16 | return url; 17 | } 18 | throw new Error('에러가 발생했습니다. 이미지 주소를 받아오지 못했습니다'); 19 | }; 20 | 21 | const uploadImageByString = async (imageRef: Blob) => { 22 | const stackImageRef = makeImageRef(); 23 | const metaData = { 24 | contentType: 'image/png', 25 | }; 26 | 27 | // const uploadRes = await uploadString(stackImageRef, imageRef, 'data_url'); 28 | const uploadRes = await uploadBytes(stackImageRef, imageRef, metaData); 29 | if (uploadRes) { 30 | return uploadRes; 31 | } 32 | throw new Error('에러가 발생했습니다. 이미지를 생성하지 못했습니다'); 33 | }; 34 | 35 | export const getCreatedImageUrl = async (imageRef: Blob) => { 36 | const uploaded = await uploadImageByString(imageRef); 37 | 38 | if (uploaded) { 39 | return downloadImageURL(uploaded.ref); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /src/services/google-analytics/GoogleAnalyticsTracker.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import ReactGA from 'react-ga4'; 3 | import { useLocation } from 'react-router'; 4 | 5 | const GoogleAnalyticsTracker = () => { 6 | const location = useLocation(); 7 | const [initialized, setInitialized] = useState(false); 8 | 9 | useEffect(() => { 10 | if (!window.location.href.includes('localhost')) { 11 | ReactGA.initialize(import.meta.env.VITE_GOOGLE_ANALYTICS_TRACKING_ID as string); 12 | } 13 | setInitialized(true); 14 | }, []); 15 | 16 | useEffect(() => { 17 | if (initialized) { 18 | ReactGA.send({ hitType: 'pageview', page: location.pathname + location.hash }); 19 | } 20 | }, [initialized, location]); 21 | }; 22 | 23 | export default GoogleAnalyticsTracker; 24 | -------------------------------------------------------------------------------- /src/styles/components.ts: -------------------------------------------------------------------------------- 1 | const MuiAutocomplete = { 2 | styleOverrides: { 3 | tag: { 4 | backgroundColor: '#e6f0ff', 5 | border: '1px solid #99c2ff', 6 | }, 7 | paper: { 8 | borderRadius: '12px', 9 | marginTop: '18px', 10 | }, 11 | listbox: { 12 | '::-webkit-scrollbar': { 13 | width: '14px', 14 | }, 15 | '::-webkit-scrollbar-track': { 16 | boxShadow: 'inset 0 0 14px 14px transparent', 17 | border: 'solid 4px transparent', 18 | margin: 3, 19 | }, 20 | '::-webkit-scrollbar-thumb': { 21 | boxShadow: 'inset 0 0 14px 14px #D0D0D0', 22 | border: 'solid 4px transparent', 23 | borderRadius: '14px', 24 | }, 25 | }, 26 | }, 27 | }; 28 | 29 | const MuiOutlinedInput = { 30 | styleOverrides: { 31 | root: { 32 | borderRadius: '12px', 33 | backgroundColor: 'white', 34 | fontSize: '1.1875rem', 35 | 36 | '&.Mui-focused': { 37 | border: '1px #66a3ff solid', 38 | }, 39 | }, 40 | notchedOutline: { 41 | border: 'none', 42 | }, 43 | }, 44 | }; 45 | 46 | const MuiChip = { 47 | styleOverrides: { 48 | deleteIcon: { 49 | color: '#99c2ff', 50 | }, 51 | }, 52 | }; 53 | 54 | export const components = { 55 | MuiAutocomplete, 56 | MuiOutlinedInput, 57 | MuiChip, 58 | }; 59 | -------------------------------------------------------------------------------- /src/styles/palette.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@mui/material/styles' { 2 | interface Palette { 3 | p: { 4 | '1': string; 5 | '2': string; 6 | '3': string; 7 | '4': string; 8 | '5': string; 9 | '6': string; 10 | '7': string; 11 | '8': string; 12 | }; 13 | 14 | cg: { 15 | '1': string; 16 | '2': string; 17 | '3': string; 18 | '4': string; 19 | '5': string; 20 | '6': string; 21 | }; 22 | } 23 | 24 | interface PaletteOptions { 25 | p: { 26 | '1': string; 27 | '2': string; 28 | '3': string; 29 | '4': string; 30 | '5': string; 31 | '6': string; 32 | '7': string; 33 | '8': string; 34 | }; 35 | 36 | cg: { 37 | '1': string; 38 | '2': string; 39 | '3': string; 40 | '4': string; 41 | '5': string; 42 | '6': string; 43 | }; 44 | } 45 | 46 | interface PaletteColor { 47 | '1'?: string; 48 | '2'?: string; 49 | '3'?: string; 50 | '4'?: string; 51 | '5'?: string; 52 | '6'?: string; 53 | '7'?: string; 54 | '8'?: string; 55 | } 56 | 57 | interface SimplePaletteColorOptions { 58 | '1'?: string; 59 | '2'?: string; 60 | '3'?: string; 61 | '4'?: string; 62 | '5'?: string; 63 | '6'?: string; 64 | '7'?: string; 65 | '8'?: string; 66 | } 67 | } 68 | 69 | export default function createPalette(palette: PaletteOptions): Palette; 70 | -------------------------------------------------------------------------------- /src/styles/paletteColors.ts: -------------------------------------------------------------------------------- 1 | export const colors = { 2 | p: { 3 | '1': '#0066ff', 4 | '2': '#3385ff', 5 | '3': '#66a3ff', 6 | '4': '#99c2ff', 7 | '5': '#cce0ff', 8 | '6': '#e6f0ff', 9 | '7': '#f0f6ff', 10 | '8': '#f5f9ff', 11 | }, 12 | 13 | cg: { 14 | '1': '#0A1320', 15 | '2': '#1A2339', 16 | '3': '#364261', 17 | '4': '#4B597D', 18 | '5': '#7886AA', 19 | '6': '#B3BACD', 20 | }, 21 | 22 | info: { 23 | light: '#F9F9F9', 24 | main: '#8A8A8A', 25 | dark: '#444A52', 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /src/styles/theme.ts: -------------------------------------------------------------------------------- 1 | import { createTheme } from '@mui/material/styles'; 2 | 3 | import { components } from './components'; 4 | import { colors } from './paletteColors'; 5 | import { typos } from './typo'; 6 | 7 | export const theme = createTheme({ 8 | typography: typos, 9 | palette: colors, 10 | components: components, 11 | }); 12 | -------------------------------------------------------------------------------- /src/styles/typo.ts: -------------------------------------------------------------------------------- 1 | export const typos = { 2 | fontFamily: [ 3 | 'Pretendard Variable', 4 | 'Pretendard', 5 | '-apple-system', 6 | 'BlinkMacSystemFont', 7 | 'system-ui', 8 | 'Roboto', 9 | 'Helvetica Neue', 10 | 'Segoe UI', 11 | 'Apple SD Gothic Neo', 12 | 'Noto Sans KR', 13 | 'Malgun Gothic', 14 | 'Apple Color Emoji', 15 | 'Segoe UI Emoji', 16 | 'Segoe UI Symbol', 17 | 'sans-serif', 18 | ].join(','), 19 | }; 20 | -------------------------------------------------------------------------------- /src/types/backgroundColors.ts: -------------------------------------------------------------------------------- 1 | export type BgColorOption = 'black' | 'white'; 2 | -------------------------------------------------------------------------------- /src/types/common.ts: -------------------------------------------------------------------------------- 1 | export type MakeUnionType = T[keyof T]; 2 | -------------------------------------------------------------------------------- /src/types/location.ts: -------------------------------------------------------------------------------- 1 | export interface LocationState { 2 | url: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/types/packageJson.ts: -------------------------------------------------------------------------------- 1 | export interface PackageJSONType { 2 | [key: string]: string | boolean | object; 3 | } 4 | -------------------------------------------------------------------------------- /src/types/toast.ts: -------------------------------------------------------------------------------- 1 | export interface ToastProps { 2 | type: 'success' | 'warning' | 'error' | 'info'; 3 | message: string; 4 | open: boolean; 5 | close: () => void; 6 | duration?: number; 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/allIconInfo.ts: -------------------------------------------------------------------------------- 1 | import * as icons from 'simple-icons'; 2 | 3 | import { customIconArray, customIconObject } from './customIconObjects'; 4 | 5 | import type { SimpleIcon } from 'simple-icons'; 6 | 7 | export const makeIconInfoArray = () => { 8 | const iconArr = Object.values(icons) as SimpleIcon[]; 9 | const customIconArr = customIconArray; 10 | 11 | const allIconArr = [...iconArr, ...customIconArr]; 12 | return allIconArr; 13 | }; 14 | 15 | export const getIconDetail = (target: string) => { 16 | const allIconArr = { ...icons, ...customIconObject }; 17 | 18 | const iconKey = ('si' + target) as keyof typeof allIconArr; 19 | 20 | return allIconArr[iconKey] as SimpleIcon; 21 | }; 22 | 23 | export const allIconNames = () => { 24 | const allIcons = makeIconInfoArray(); 25 | 26 | const iconSlugs = new Set(); 27 | allIcons.forEach((icon) => iconSlugs.add(icon.slug)); 28 | 29 | return iconSlugs; 30 | }; 31 | -------------------------------------------------------------------------------- /src/utils/array.ts: -------------------------------------------------------------------------------- 1 | import { allIconNames } from './allIconInfo'; 2 | import { makeIntoSlug } from './string'; 3 | 4 | export const extractUniqueElements = (skills: string[]) => { 5 | const uniqueArray = new Set(skills); 6 | 7 | return [...uniqueArray]; 8 | }; 9 | 10 | export const filterExisitingStacks = (array: string[]) => { 11 | const names = allIconNames(); 12 | 13 | return array.filter((el) => names.has(makeIntoSlug(el))); 14 | }; 15 | -------------------------------------------------------------------------------- /src/utils/customIconObjects.ts: -------------------------------------------------------------------------------- 1 | import CustomIconsJson from '../constants/custom-icons.json'; 2 | 3 | import type { SimpleIcon } from 'simple-icons'; 4 | 5 | const PREFIX = ''; 7 | const MIDDLE = ''; 9 | 10 | interface CustomIconProps { 11 | viewBox: string; 12 | } 13 | 14 | interface CustomObjectProps { 15 | [key: string]: SimpleIcon & CustomIconProps; 16 | } 17 | 18 | export const customIconArray: (SimpleIcon & CustomIconProps)[] = CustomIconsJson.map((obj) => ({ 19 | ...obj, 20 | get svg() { 21 | return PREFIX + this.viewBox + PREMID + this.title + MIDDLE + this.path + SUFFIX; 22 | }, 23 | source: '', 24 | guidelines: void 0, 25 | license: void 0, 26 | })); 27 | 28 | export const customIconObject: CustomObjectProps = {}; 29 | CustomIconsJson.forEach((item) => { 30 | const key = 31 | 'si' + item.slug.substring(0, 1).toUpperCase() + item.slug.substring(1, item.slug.length); 32 | const value: SimpleIcon & CustomIconProps = { 33 | ...item, 34 | get svg() { 35 | return PREFIX + this.viewBox + PREMID + this.title + MIDDLE + this.path + SUFFIX; 36 | }, 37 | source: '', 38 | guidelines: void 0, 39 | license: void 0, 40 | }; 41 | 42 | customIconObject[key] = value; 43 | }); 44 | -------------------------------------------------------------------------------- /src/utils/imageConverter.ts: -------------------------------------------------------------------------------- 1 | import { toBlob } from 'html-to-image/lib/index'; 2 | 3 | export const htmlToPng = async (targetRef: React.RefObject) => { 4 | if (targetRef.current === null) { 5 | return; 6 | } 7 | 8 | const dataUrl = await toBlob(targetRef.current, { cacheBust: true }); 9 | return dataUrl; 10 | }; 11 | -------------------------------------------------------------------------------- /src/utils/packageJson.ts: -------------------------------------------------------------------------------- 1 | import { DEPENDENCY, DEV_DEPENDENCY } from 'constants/constants'; 2 | import { PackageJSONType } from 'types/packageJson'; 3 | import { extractUniqueElements } from './array'; 4 | 5 | /** 6 | * get package.json's URL in project 7 | */ 8 | export const getPackageJsonURL = (url: string) => { 9 | const PREFIX = 'https://raw.githubusercontent.com/'; 10 | const SUFFIX = '/package.json'; 11 | 12 | const modifyURL = (originalURL: string, segmentAfterHEAD: string) => { 13 | const treeRegex = /\/tree\/[^/]+/; 14 | const segmentRegex = /\/[^/]+/; 15 | const hasTree = originalURL.match(treeRegex); 16 | 17 | if (originalURL.endsWith('/')) { 18 | originalURL = originalURL.slice(0, -1); 19 | } 20 | 21 | if (!hasTree) { 22 | return `${originalURL}/HEAD`; 23 | } 24 | 25 | const modifiedURL = originalURL.replace(treeRegex, '/HEAD'); 26 | 27 | if (segmentAfterHEAD) { 28 | modifiedURL.replace(segmentRegex, `/${segmentAfterHEAD}`); 29 | } 30 | 31 | return modifiedURL; 32 | }; 33 | 34 | let repositoryLocation = url.replace('https://github.com/', ''); 35 | repositoryLocation = modifyURL(repositoryLocation, ''); 36 | 37 | return PREFIX + repositoryLocation + SUFFIX; 38 | }; 39 | 40 | /** 41 | * get `dependencies`, `devDependencies` in package.json 42 | */ 43 | export const getDependencies = (packageObj: PackageJSONType) => { 44 | let dependencies: string[] = []; 45 | let devDependencies: string[] = []; 46 | 47 | if (DEPENDENCY in packageObj) { 48 | dependencies = Object.keys(packageObj[DEPENDENCY]); 49 | } 50 | if (DEV_DEPENDENCY in packageObj) { 51 | devDependencies = Object.keys(packageObj[DEV_DEPENDENCY]); 52 | } 53 | 54 | return [...dependencies, ...devDependencies]; 55 | }; 56 | 57 | export const removeSubModules = (dependencies: string[]) => { 58 | const onlyStackName = dependencies.map((stk) => 59 | stk.includes('/') ? stk.split('/')[0].substring(1) : stk, 60 | ); 61 | 62 | return extractUniqueElements(onlyStackName); 63 | }; 64 | -------------------------------------------------------------------------------- /src/utils/resultUrl.ts: -------------------------------------------------------------------------------- 1 | import { GITHUB_LINK } from 'constants/constants'; 2 | 3 | export const makeUrlIntoBracket = (url: string) => { 4 | return `[![stackticon](${url})](${GITHUB_LINK})`; 5 | }; 6 | 7 | export const makeUrlIntoImgTag = (url: string) => { 8 | return `stackticon`; 9 | }; 10 | -------------------------------------------------------------------------------- /src/utils/string.ts: -------------------------------------------------------------------------------- 1 | export const capitalize = (input: string) => { 2 | return input[0].toUpperCase() + input.slice(1); 3 | }; 4 | 5 | export const makeIntoSlug = (stk: string) => { 6 | return stk.replaceAll('-', '').toLowerCase(); 7 | }; 8 | -------------------------------------------------------------------------------- /src/utils/test/allIconInfo.test.ts: -------------------------------------------------------------------------------- 1 | import customIcons from '../../constants/custom-icons.json'; 2 | import { getIconDetail, makeIconInfoArray } from '../allIconInfo'; 3 | 4 | import type { SimpleIcon } from 'simple-icons'; 5 | 6 | describe('check custom icons', () => { 7 | let allIconArr: SimpleIcon[]; 8 | 9 | beforeAll(() => { 10 | allIconArr = makeIconInfoArray(); 11 | }); 12 | 13 | it('check last added icon title', () => { 14 | const lastCustomIcon = allIconArr.at(-1); 15 | const lastAddedIcon = Object.values(customIcons).at(-1); 16 | 17 | expect(lastCustomIcon?.title).toBe(lastAddedIcon?.title); 18 | }); 19 | 20 | it('check slug according to the title', () => { 21 | const lastAddedIcon = Object.values(customIcons).at(-1); 22 | 23 | const removedSpaces = lastAddedIcon?.title.replaceAll(' ', '') ?? ''; 24 | const makeSlug = removedSpaces?.toLowerCase(); 25 | 26 | expect(lastAddedIcon?.slug).toBe(makeSlug); 27 | }); 28 | }); 29 | 30 | test('get accurate icon object', () => { 31 | const converted = getIconDetail('Reactrouter'); 32 | 33 | expect(converted.title).toBe('React Router'); 34 | expect(converted.slug).toBe('reactrouter'); 35 | }); 36 | -------------------------------------------------------------------------------- /src/utils/test/array.test.ts: -------------------------------------------------------------------------------- 1 | import { extractUniqueElements, filterExisitingStacks } from 'utils/array'; 2 | 3 | describe('Array util test', () => { 4 | it('remove duplicate elements', () => { 5 | const original = [ 6 | 'emotion', 7 | 'emotion', 8 | 'emotion', 9 | 'mui', 10 | 'firebase', 11 | 'firebase', 12 | 'framer-motion', 13 | 'framer-motion', 14 | 'framer-motion', 15 | 'gh-pages', 16 | 'gh-pages', 17 | ]; 18 | const uniqueArray = ['emotion', 'mui', 'firebase', 'framer-motion', 'gh-pages']; 19 | 20 | expect(extractUniqueElements(original)).toEqual(uniqueArray); 21 | }); 22 | 23 | it('get stacks which are in our icon set', () => { 24 | const original = [ 25 | 'emotion', 26 | 'mui', 27 | 'firebase', 28 | 'framermotion', 29 | 'ghpages', 30 | 'htmltoimage', 31 | 'pretendard', 32 | 'react', 33 | 'reactdom', 34 | 'reactga4', 35 | 'reactrouterdom', 36 | 'simpleicons', 37 | 'babel', 38 | 'testinglibrary', 39 | 'types', 40 | 'typescripteslint', 41 | 'vitejs', 42 | 'eslint', 43 | 'eslintconfigprettier', 44 | 'eslintconfigreact', 45 | 'eslintconfigreactapp', 46 | 'eslintpluginprettier', 47 | 'eslintpluginreact', 48 | 'eslintpluginsimpleimportsort', 49 | 'eslintpluginunusedimports', 50 | 'jest', 51 | 'prettier', 52 | 'tsjest', 53 | 'typescript', 54 | 'vite', 55 | 'vitetsconfigpaths', 56 | ]; 57 | 58 | const filteredArray = [ 59 | 'emotion', 60 | 'mui', 61 | 'firebase', 62 | 'react', 63 | 'simpleicons', 64 | 'babel', 65 | 'testinglibrary', 66 | 'eslint', 67 | 'jest', 68 | 'prettier', 69 | 'typescript', 70 | 'vite', 71 | ]; 72 | 73 | expect(filterExisitingStacks(original)).toEqual(filteredArray); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /src/utils/test/imageConverter.test.ts: -------------------------------------------------------------------------------- 1 | import { htmlToPng } from 'utils/imageConverter'; 2 | 3 | describe('convert html to image', () => { 4 | it('check when given html is null', async () => { 5 | const html = { 6 | current: null, 7 | }; 8 | 9 | const res = await htmlToPng(html); 10 | expect(res).toBe(undefined); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/utils/test/packageJson.test.ts: -------------------------------------------------------------------------------- 1 | import { getPackageJsonURL } from 'utils/packageJson'; 2 | 3 | describe('get package.json location from repository', () => { 4 | it('in multi repo, without slash at last', () => { 5 | const multiRepoUrl = 'https://github.com/msdio/stackticon'; 6 | const packageFileInMultiRepo = 7 | 'https://raw.githubusercontent.com/msdio/stackticon/HEAD/package.json'; 8 | 9 | expect(getPackageJsonURL(multiRepoUrl)).toBe(packageFileInMultiRepo); 10 | }); 11 | 12 | it('in multi repo, with slash at last', () => { 13 | const multiRepoUrl = 'https://github.com/msdio/stackticon/'; 14 | const packageFileInMultiRepo = 15 | 'https://raw.githubusercontent.com/msdio/stackticon/HEAD/package.json'; 16 | 17 | expect(getPackageJsonURL(multiRepoUrl)).toBe(packageFileInMultiRepo); 18 | }); 19 | 20 | it('in mono repo, without slash at last', () => { 21 | const monoRepoUrl = 'https://github.com/msdio/Tamago/tree/main/client'; 22 | const packageFileInMonoRepo = 23 | 'https://raw.githubusercontent.com/msdio/Tamago/HEAD/client/package.json'; 24 | 25 | expect(getPackageJsonURL(monoRepoUrl)).toBe(packageFileInMonoRepo); 26 | }); 27 | 28 | it('in mono repo, with slash at last', () => { 29 | const monoRepoUrl = 'https://github.com/msdio/Tamago/tree/main/client/'; 30 | const packageFileInMonoRepo = 31 | 'https://raw.githubusercontent.com/msdio/Tamago/HEAD/client/package.json'; 32 | 33 | expect(getPackageJsonURL(monoRepoUrl)).toBe(packageFileInMonoRepo); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/utils/test/resultUrl.test.ts: -------------------------------------------------------------------------------- 1 | import { makeUrlIntoBracket, makeUrlIntoImgTag } from 'utils/resultUrl'; 2 | 3 | describe('url copy result', () => { 4 | const url = 'https://image-url'; 5 | 6 | it('copy option : copy for readme', () => { 7 | const copyForReadme = makeUrlIntoBracket(url); 8 | 9 | expect(copyForReadme).toBe(`[![stackticon](${url})](https://github.com/msdio/stackticon)`); 10 | }); 11 | 12 | it('copy option: copy with img tag', () => { 13 | const copyWithImgTag = makeUrlIntoImgTag(url); 14 | 15 | expect(copyWithImgTag).toBe( 16 | `stackticon`, 17 | ); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/utils/test/string.test.ts: -------------------------------------------------------------------------------- 1 | import { capitalize, makeIntoSlug } from 'utils/string'; 2 | 3 | test('capitalize first letter in given string', () => { 4 | const input = 'stackticon'; 5 | 6 | expect(capitalize(input)).toBe('Stackticon'); 7 | }); 8 | 9 | describe('make string into slug', () => { 10 | it('which has hyphen', () => { 11 | const input = 'styled-components'; 12 | 13 | expect(makeIntoSlug(input)).toBe('styledcomponents'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src", 4 | "target": "ESNext", 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "module": "ESNext", 14 | "moduleResolution": "bundler", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "noEmit": true, 18 | "jsx": "react-jsx", 19 | "paths": { 20 | "@common/*": ["components/@common/*"] 21 | } 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react'; 2 | import { defineConfig } from 'vite'; 3 | import tsconfigPaths from 'vite-tsconfig-paths'; 4 | 5 | // const renderChunks = (deps: Record) => { 6 | // const chunks = {}; 7 | // Object.keys(deps).forEach((key) => { 8 | // if (['react', 'react-router', 'react-dom', 'pretendard'].includes(key)) { 9 | // return; 10 | // } 11 | 12 | // if (key === 'firebase') { 13 | // chunks[key + '/app'] = [key + '/app']; 14 | // chunks[key + '/storage'] = [key + '/storage']; 15 | // } else { 16 | // chunks[key] = [key]; 17 | // } 18 | // }); 19 | 20 | // return chunks; 21 | // }; 22 | 23 | // https://vitejs.dev/config/ 24 | export default defineConfig({ 25 | plugins: [react(), tsconfigPaths()], 26 | base: '/stackticon/', 27 | // build: { 28 | // sourcemap: false, 29 | // rollupOptions: { 30 | // output: { 31 | // manualChunks: { 32 | // vendor: ['react', 'react-router', 'react-dom'], 33 | // ...renderChunks(dependencies), 34 | // }, 35 | // }, 36 | // }, 37 | // }, 38 | }); 39 | --------------------------------------------------------------------------------