Skip to content

frontend

Mile Shi edited this page May 26, 2025 · 1 revision

Frontend Development Guide

This guide covers frontend development for the Intelligent IDE VS Code extension, built with TypeScript and the VS Code Extension API.

Frontend Overview

The frontend is a VS Code extension that provides:

  • User authentication and management
  • Course management interface
  • File upload and organization
  • Command palette integration
  • Real-time collaboration features
  • Custom webview components

Technology Stack

  • Language: TypeScript 4.8+
  • Platform: VS Code Extension API
  • Build Tool: esbuild
  • Package Manager: npm
  • Testing: VS Code Extension Test Suite

Project Structure

Based on the actual frontend structure:

frontend/intelligent-ide/
├── package.json              # Extension manifest and configuration
├── tsconfig.json            # TypeScript configuration
├── esbuild.js              # Build configuration
├── .gitignore              # Git ignore rules
├── .vscodeignore           # VS Code packaging ignore
├── CHANGELOG.md            # Change log
├── README.md               # Documentation
└── src/                    # Source code
    ├── extension.ts        # Main extension entry point
    ├── commands/           # Command implementations
    │   ├── loginCommand.ts    # Login command logic
    │   ├── registerCommand.ts # Registration command logic
    │   └── updateCommand.ts   # Update command logic
    ├── services/           # Backend API integration
    │   └── userService.ts     # User-related API calls
    ├── utils/              # Utility functions
    │   └── responseParser.ts  # Response data parsing
    ├── resources/          # Configuration and resources
    │   └── configs/
    │       └── config.ts      # API endpoints and settings
    └── test/               # Test files
        └── extension.test.ts  # Extension tests

Getting Started

Prerequisites

  1. VS Code: Latest version recommended
  2. Node.js: 16.0.0+ for development
  3. npm: Package management

Setup Instructions

  1. Navigate to Frontend Directory

    cd frontend/intelligent-ide
  2. Install Dependencies

    npm install
  3. Compile TypeScript

    npm run compile
  4. Launch Extension Development Host

    • Open the project in VS Code
    • Ensure you're in the intelligent-ide directory
    • Press F5 to launch Extension Development Host
    • A new VS Code window opens with the extension loaded

Verification

  1. Check Extension Activation

    • Look for "Congratulations, your extension 'intelligent-ide' is now active!" in the Debug Console
    • Window title should show "[Extension Development Host]"
  2. Test Commands

    • Press Ctrl+Shift+P (or Cmd+Shift+P on Mac)
    • Type "intelligent-ide" to see available commands:
      • intelligent-ide.login
      • intelligent-ide.register
      • intelligent-ide.update

Extension Architecture

Main Entry Point

The extension.ts file is the main entry point:

// src/extension.ts
import * as vscode from 'vscode';
import { registerLoginCommand } from './commands/loginCommand';
import { registerRegisterCommand } from './commands/registerCommand';
import { registerUpdateCommand } from './commands/updateCommand';

export function activate(context: vscode.ExtensionContext) {
    console.log('Congratulations, your extension "intelligent-ide" is now active!');
    
    // Register all commands
    registerLoginCommand(context);
    registerRegisterCommand(context);
    registerUpdateCommand(context);
}

export function deactivate() {
    // Cleanup when extension is deactivated
}

Command Pattern Implementation

Each command follows a consistent pattern:

// src/commands/loginCommand.ts
import * as vscode from 'vscode';
import { UserService } from '../services/userService';
import { parseResponse } from '../utils/responseParser';

export function registerLoginCommand(context: vscode.ExtensionContext) {
    const disposable = vscode.commands.registerCommand(
        'intelligent-ide.login',
        async () => {
            try {
                // Get user input
                const username = await vscode.window.showInputBox({
                    prompt: 'Enter your username',
                    placeHolder: 'Username'
                });

                if (!username) {
                    return;
                }

                const password = await vscode.window.showInputBox({
                    prompt: 'Enter your password',
                    placeHolder: 'Password',
                    password: true
                });

                if (!password) {
                    return;
                }

                // Call backend service
                const userService = new UserService();
                const response = await userService.login(username, password);
                const result = parseResponse(response);

                if (result.success) {
                    // Store token securely
                    await context.globalState.update('accessToken', result.data.access_token);
                    vscode.window.showInformationMessage('Login successful!');
                } else {
                    vscode.window.showErrorMessage(`Login failed: ${result.message}`);
                }

            } catch (error) {
                vscode.window.showErrorMessage(`Login error: ${error}`);
            }
        }
    );

    context.subscriptions.push(disposable);
}

Service Layer

The service layer handles backend communication:

// src/services/userService.ts
import { API_BASE_URL } from '../resources/configs/config';

export interface LoginRequest {
    username: string;
    password: string;
}

export interface RegisterRequest {
    username: string;
    email: string;
    password: string;
    verification_code: string;
    role: 'teacher' | 'student';
}

export class UserService {
    private baseUrl: string;

    constructor() {
        this.baseUrl = API_BASE_URL;
    }

    async login(username: string, password: string): Promise<any> {
        const response = await fetch(`${this.baseUrl}/api/user/login`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ username, password })
        });

        return response.json();
    }

    async register(registerData: RegisterRequest): Promise<any> {
        const response = await fetch(`${this.baseUrl}/api/user/register`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(registerData)
        });

        return response.json();
    }

    async getVerificationCode(email: string): Promise<any> {
        const response = await fetch(`${this.baseUrl}/api/user/register/code?email=${email}`, {
            method: 'GET'
        });

        return response.json();
    }

    async updateUser(updateData: any, token: string): Promise<any> {
        const response = await fetch(`${this.baseUrl}/api/user`, {
            method: 'PUT',
            headers: {
                'Content-Type': 'application/json',
                'Access-Token': token
            },
            body: JSON.stringify(updateData)
        });

        return response.json();
    }

    async getCurrentUser(token: string): Promise<any> {
        const response = await fetch(`${this.baseUrl}/api/user`, {
            method: 'GET',
            headers: {
                'Access-Token': token
            }
        });

        return response.json();
    }
}

Configuration Management

// src/resources/configs/config.ts
export const API_BASE_URL = 'http://localhost:8080';

export const CONFIG = {
    api: {
        baseUrl: API_BASE_URL,
        timeout: 30000,
        retries: 3
    },
    ui: {
        defaultTheme: 'dark',
        animations: true
    },
    cache: {
        tokenExpiry: 24 * 60 * 60 * 1000, // 24 hours
        maxCacheSize: 100
    }
};

Utility Functions

// src/utils/responseParser.ts
export interface ParsedResponse {
    success: boolean;
    data?: any;
    message?: string;
    error?: string;
}

export function parseResponse(response: any): ParsedResponse {
    try {
        if (response.status === 'success' || response.access_token) {
            return {
                success: true,
                data: response,
                message: response.message || 'Operation successful'
            };
        } else {
            return {
                success: false,
                message: response.message || 'Unknown error occurred',
                error: response.error
            };
        }
    } catch (error) {
        return {
            success: false,
            message: 'Failed to parse response',
            error: error instanceof Error ? error.message : 'Unknown error'
        };
    }
}

export function validateEmail(email: string): boolean {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
}

export function validatePassword(password: string): boolean {
    // At least 8 characters, 1 uppercase, 1 lowercase, 1 number
    const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8,}$/;
    return passwordRegex.test(password);
}

Advanced Features

State Management

// State management using VS Code's global state
export class StateManager {
    constructor(private context: vscode.ExtensionContext) {}

    async setToken(token: string): Promise<void> {
        await this.context.globalState.update('accessToken', token);
    }

    getToken(): string | undefined {
        return this.context.globalState.get('accessToken');
    }

    async clearToken(): Promise<void> {
        await this.context.globalState.update('accessToken', undefined);
    }

    async setUserData(userData: any): Promise<void> {
        await this.context.globalState.update('userData', userData);
    }

    getUserData(): any {
        return this.context.globalState.get('userData');
    }
}

Error Handling

// Error handling utilities
export class ErrorHandler {
    static handle(error: any, context?: string): void {
        let message = 'An unexpected error occurred';
        
        if (error instanceof Error) {
            message = error.message;
        } else if (typeof error === 'string') {
            message = error;
        }

        if (context) {
            message = `${context}: ${message}`;
        }

        vscode.window.showErrorMessage(message);
        console.error('Extension Error:', error);
    }

    static async handleAsync(operation: () => Promise<void>, context?: string): Promise<void> {
        try {
            await operation();
        } catch (error) {
            this.handle(error, context);
        }
    }
}

Custom Webview Components

// Creating custom webviews for complex UI
export class CourseWebviewProvider implements vscode.WebviewViewProvider {
    public static readonly viewType = 'intelligent-ide.courseView';

    constructor(private readonly _extensionUri: vscode.Uri) {}

    public resolveWebviewView(
        webviewView: vscode.WebviewView,
        context: vscode.WebviewViewResolveContext,
        _token: vscode.CancellationToken,
    ) {
        webviewView.webview.options = {
            enableScripts: true,
            localResourceRoots: [this._extensionUri]
        };

        webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);

        // Handle messages from webview
        webviewView.webview.onDidReceiveMessage(async (data) => {
            switch (data.type) {
                case 'loadCourses':
                    await this.loadCourses(webviewView.webview);
                    break;
                case 'createCourse':
                    await this.createCourse(data.courseData);
                    break;
            }
        });
    }

    private _getHtmlForWebview(webview: vscode.Webview) {
        return `<!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Course Management</title>
        </head>
        <body>
            <div id="app">
                <h2>My Courses</h2>
                <div id="course-list"></div>
                <button onclick="createCourse()">Create New Course</button>
            </div>
            <script>
                const vscode = acquireVsCodeApi();
                
                function createCourse() {
                    vscode.postMessage({
                        type: 'createCourse',
                        courseData: { name: 'New Course' }
                    });
                }
                
                // Load courses on startup
                vscode.postMessage({ type: 'loadCourses' });
            </script>
        </body>
        </html>`;
    }

    private async loadCourses(webview: vscode.Webview) {
        // Load courses from backend and update webview
    }

    private async createCourse(courseData: any) {
        // Create course via backend API
    }
}

File System Integration

// Working with VS Code file system
export class FileManager {
    static async uploadFile(uri: vscode.Uri, courseId: number): Promise<void> {
        try {
            const fileData = await vscode.workspace.fs.readFile(uri);
            const fileName = path.basename(uri.fsPath);
            
            // Convert to base64 for upload
            const base64Data = Buffer.from(fileData).toString('base64');
            
            // Upload to backend
            const userService = new UserService();
            const token = await this.getToken();
            
            if (token) {
                await userService.uploadFile({
                    fileName,
                    fileData: base64Data,
                    courseId
                }, token);
                
                vscode.window.showInformationMessage(`File ${fileName} uploaded successfully`);
            }
        } catch (error) {
            ErrorHandler.handle(error, 'File upload');
        }
    }

    private static async getToken(): Promise<string | undefined> {
        // Get token from global state
        return vscode.workspace.getConfiguration('intelligent-ide').get('token');
    }
}

Testing

Extension Testing Setup

// src/test/extension.test.ts
import * as assert from 'assert';
import * as vscode from 'vscode';
import { activate } from '../extension';

suite('Extension Test Suite', () => {
    vscode.window.showInformationMessage('Start all tests.');

    test('Extension should be present', () => {
        assert.ok(vscode.extensions.getExtension('publisher.intelligent-ide'));
    });

    test('Should activate extension', async () => {
        const extension = vscode.extensions.getExtension('publisher.intelligent-ide');
        if (extension) {
            await extension.activate();
            assert.ok(extension.isActive);
        }
    });

    test('Should register commands', async () => {
        const commands = await vscode.commands.getCommands();
        assert.ok(commands.includes('intelligent-ide.login'));
        assert.ok(commands.includes('intelligent-ide.register'));
        assert.ok(commands.includes('intelligent-ide.update'));
    });
});

Running Tests

# Run extension tests
npm run test

# Compile and run tests
npm run compile && npm run test

Build and Package

Build Configuration

// esbuild.js
const esbuild = require('esbuild');

const production = process.argv.includes('--production');
const watch = process.argv.includes('--watch');

async function main() {
    const ctx = await esbuild.context({
        entryPoints: ['src/extension.ts'],
        bundle: true,
        format: 'cjs',
        minify: production,
        sourcemap: !production,
        sourcesContent: false,
        platform: 'node',
        outfile: 'out/extension.js',
        external: ['vscode'],
        logLevel: 'silent',
        plugins: [{
            name: 'umd2esm',
            setup(build) {
                build.onResolve({ filter: /^(vscode-.*|estree-walker|d3-color)/ }, args => {
                    const pathUmdMay = require.resolve(args.path, { paths: [args.resolveDir] })
                    const pathEsm = pathUmdMay.replace('/umd/', '/esm/')
                    return { path: pathEsm }
                })
            },
        }],
    });

    if (watch) {
        await ctx.watch();
    } else {
        await ctx.rebuild();
        await ctx.dispose();
    }
}

main().catch(e => {
    console.error(e);
    process.exit(1);
});

Package Commands

// package.json scripts
{
  "scripts": {
    "compile": "esbuild src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node",
    "watch": "npm run compile -- --watch",
    "package": "vsce package",
    "test": "node ./out/test/runTest.js"
  }
}

Extension Manifest

// package.json (key sections)
{
  "name": "intelligent-ide",
  "displayName": "Intelligent IDE",
  "description": "Educational collaboration platform for VS Code",
  "version": "0.0.1",
  "engines": {
    "vscode": "^1.60.0"
  },
  "categories": ["Education", "Other"],
  "activationEvents": [
    "onCommand:intelligent-ide.login"
  ],
  "main": "./out/extension.js",
  "contributes": {
    "commands": [
      {
        "command": "intelligent-ide.login",
        "title": "Login to Intelligent IDE"
      },
      {
        "command": "intelligent-ide.register",
        "title": "Register for Intelligent IDE"
      },
      {
        "command": "intelligent-ide.update",
        "title": "Update Profile"
      }
    ],
    "views": {
      "explorer": [
        {
          "id": "intelligent-ide.courseView",
          "name": "Courses",
          "when": "intelligent-ide.authenticated"
        }
      ]
    }
  }
}

Best Practices

Code Organization

  1. Separation of Concerns: Keep commands, services, and utilities separate
  2. Error Handling: Implement comprehensive error handling
  3. User Feedback: Provide clear user feedback for all operations
  4. Security: Store sensitive data securely using VS Code's state management

Performance Optimization

  1. Lazy Loading: Load resources only when needed
  2. Caching: Cache frequently accessed data
  3. Async Operations: Use async/await for non-blocking operations
  4. Resource Cleanup: Properly dispose of resources

Development Workflow

  1. Hot Reload: Use watch mode during development
  2. Debugging: Utilize VS Code's debugging features
  3. Testing: Write tests for all major functionality
  4. Documentation: Document all public APIs and complex logic

This frontend development guide provides comprehensive information for contributing to and extending the Intelligent IDE VS Code extension.

Clone this wiki locally