Rybená Logo

Suporte TypeScript

A API Rybená oferece suporte completo a TypeScript, permitindo que você aproveite os benefícios de tipagem estática, autocompletar e verificação de erros em tempo de compilação.

Definições de Tipos

Para usar TypeScript com a API Rybená, você precisa declarar os tipos globais. Adicione o seguinte ao seu arquivo de declarações de tipos (ex: src/types/rybena.d.ts):

declare global {
  interface Window {
    RybenaApi?: {
      getInstance: () => RybenaApiInstance;
    };
    RybenaDOM?: {
      getInstance: () => RybenaDOMInstance;
    };
  }
}

interface RybenaApiInstance {
  // Player Control
  openPlayer: () => void;
  closePlayer: () => void;
  setToNormalMode: () => void;
  setToTransparentMode: () => void;
  setCoordinates: (x: number, y: number) => void;
  setSize: (value: number) => void;
  
  // Translation Control
  translate: (text: string) => void;
  switchToLibras: () => void;
  switchToVoz: () => void;
  pause: () => void;
  play: () => void;
  stop: () => void;
  setSpeed: (speed: number) => void;
  
  // Event Handlers
  handleLoaded: (callback: () => void) => void;
  handleTranslate: (callback: () => void) => void;
  isTranslating: () => boolean;
  
  // Accessibility Features
  // Visual Adjustments
  toggleLineHeight: () => void;
  nextLineHeight: () => void;
  previousLineHeight: () => void;
  startLineHeight: () => void;
  stopLineHeight: () => void;
  
  toggleLetterSpacing: () => void;
  nextLetterSpacing: () => void;
  previousLetterSpacing: () => void;
  startLetterSpacing: () => void;
  stopLetterSpacing: () => void;
  
  toggleZoom: () => void;
  nextZoom: () => void;
  previousZoom: () => void;
  startZoom: () => void;
  stopZoom: () => void;
  
  toggleDarkContrast: () => void;
  startDarkContrast: () => void;
  stopDarkContrast: () => void;
  
  toggleLightContrast: () => void;
  startLightContrast: () => void;
  stopLightContrast: () => void;
  
  toggleInvertedContrast: () => void;
  startInvertedContrast: () => void;
  stopInvertedContrast: () => void;
  
  toggleHighSaturation: () => void;
  startHighSaturation: () => void;
  stopHighSaturation: () => void;
  
  toggleLowSaturation: () => void;
  startLowSaturation: () => void;
  stopLowSaturation: () => void;
  
  toggleNoSaturation: () => void;
  startNoSaturation: () => void;
  stopNoSaturation: () => void;
  
  // Reading Aids
  toggleCursorSize: () => void;
  startCursorSize: () => void;
  stopCursorSize: () => void;
  
  toggleCursorGuide: () => void;
  startCursorGuide: () => void;
  stopCursorGuide: () => void;
  
  toggleAmplifyCursor: () => void;
  startAmplifyCursor: () => void;
  stopAmplifyCursor: () => void;
  
  toggleDislexiaFont: () => void;
  startDislexiaFont: () => void;
  stopDislexiaFont: () => void;
  
  toggleLinkHighlight: () => void;
  startLinkHighlight: () => void;
  stopLinkHighlight: () => void;
  
  toggleTitleHighlight: () => void;
  startTitleHighlight: () => void;
  stopTitleHighlight: () => void;
  
  toggleReadingMask: () => void;
  startReadingMask: () => void;
  stopReadingMask: () => void;
  
  toggleReadingMode: () => void;
  startReadingMode: () => void;
  stopReadingMode: () => void;
  
  // Navigation & Other
  toggleDictionary: () => void;
  startDictionary: () => void;
  stopDictionary: () => void;
  
  toggleImageDescription: () => void;
  startImageDescription: () => void;
  stopImageDescription: () => void;
  
  togglePauseAnimations: () => void;
  startPauseAnimations: () => void;
  stopPauseAnimations: () => void;
  
  toggleKeyboardNavigation: () => void;
  startKeyboardNavigation: () => void;
  stopKeyboardNavigation: () => void;
}

interface RybenaDOMInstance {
  getRybenaScripts: (mode?: string) => void;
}

export {};

Tipos Auxiliares

Você pode criar tipos auxiliares para melhorar a segurança do seu código:

// Tipos de velocidade de tradução
type TranslationSpeed = 0.5 | 0.75 | 1.0 | 1.25 | 1.5;

// Tipos de modo de tradução
type TranslationMode = 'libras' | 'voz';

// Tipos de idioma
type Language = 'ptBR' | 'esES' | 'enUS';

// Tipos de contraste
type ContrastMode = 'dark' | 'light' | 'inverted';

// Tipos de saturação
type SaturationMode = 'high' | 'low' | 'none';

// Interface para configurações da Rybená
interface RybenaConfig {
  language?: Language;
  mode?: TranslationMode;
  speed?: TranslationSpeed;
  autoOpen?: boolean;
}

// Interface para preferências de acessibilidade
interface AccessibilityPreferences {
  lineHeight?: boolean;
  letterSpacing?: boolean;
  zoom?: boolean;
  contrast?: ContrastMode;
  saturation?: SaturationMode;
  cursorSize?: boolean;
  cursorGuide?: boolean;
  dislexiaFont?: boolean;
  linkHighlight?: boolean;
  titleHighlight?: boolean;
  readingMask?: boolean;
  readingMode?: boolean;
  dictionary?: boolean;
  imageDescription?: boolean;
  pauseAnimations?: boolean;
  keyboardNavigation?: boolean;
}

Exemplos com TypeScript

Exemplo 1: Hook Customizado React

Este exemplo mostra como criar um hook customizado React com TypeScript:

import { useEffect, useState, useCallback, useRef } from 'react';

type TranslationMode = 'libras' | 'voz';
type TranslationSpeed = 0.5 | 0.75 | 1.0 | 1.25 | 1.5;

interface UseRybenaReturn {
  isLoaded: boolean;
  isTranslating: boolean;
  translate: (text: string) => void;
  pause: () => void;
  play: () => void;
  stop: () => void;
  setMode: (mode: TranslationMode) => void;
  setSpeed: (speed: TranslationSpeed) => void;
  openPlayer: () => void;
  closePlayer: () => void;
}

export function useRybena(): UseRybenaReturn {
  const [isLoaded, setIsLoaded] = useState<boolean>(false);
  const [isTranslating, setIsTranslating] = useState<boolean>(false);
  const apiRef = useRef<RybenaApiInstance | null>(null);
  
  useEffect(() => {
    // Carrega o script Rybená
    const script = document.createElement('script');
    script.src = 'https://cdn.rybena.com.br/dom/master/latest/rybena.js?mode=api';
    script.async = true;
    
    script.onload = () => {
      if (window.RybenaApi) {
        const api = window.RybenaApi.getInstance();
        apiRef.current = api;
        
        api.handleLoaded(() => {
          setIsLoaded(true);
          api.openPlayer();
          api.switchToLibras();
        });
        
        api.handleTranslate(() => {
          setIsTranslating(false);
        });
      }
    };
    
    document.head.appendChild(script);
    
    return () => {
      if (document.head.contains(script)) {
        document.head.removeChild(script);
      }
    };
  }, []);
  
  const translate = useCallback((text: string) => {
    if (apiRef.current && text.trim()) {
      setIsTranslating(true);
      apiRef.current.translate(text);
    }
  }, []);
  
  const pause = useCallback(() => {
    if (apiRef.current) {
      apiRef.current.pause();
    }
  }, []);
  
  const play = useCallback(() => {
    if (apiRef.current) {
      apiRef.current.play();
    }
  }, []);
  
  const stop = useCallback(() => {
    if (apiRef.current) {
      apiRef.current.stop();
      setIsTranslating(false);
    }
  }, []);
  
  const setMode = useCallback((mode: TranslationMode) => {
    if (apiRef.current) {
      if (mode === 'libras') {
        apiRef.current.switchToLibras();
      } else {
        apiRef.current.switchToVoz();
      }
    }
  }, []);
  
  const setSpeed = useCallback((speed: TranslationSpeed) => {
    if (apiRef.current) {
      apiRef.current.setSpeed(speed);
    }
  }, []);
  
  const openPlayer = useCallback(() => {
    if (apiRef.current) {
      apiRef.current.openPlayer();
    }
  }, []);
  
  const closePlayer = useCallback(() => {
    if (apiRef.current) {
      apiRef.current.closePlayer();
    }
  }, []);
  
  return {
    isLoaded,
    isTranslating,
    translate,
    pause,
    play,
    stop,
    setMode,
    setSpeed,
    openPlayer,
    closePlayer,
  };
}

Exemplo 2: Classe de Serviço

Este exemplo mostra como criar uma classe de serviço com TypeScript:

type TranslationMode = 'libras' | 'voz';
type TranslationSpeed = 0.5 | 0.75 | 1.0 | 1.25 | 1.5;

interface RybenaServiceConfig {
  language?: 'ptBR' | 'esES' | 'enUS';
  mode?: TranslationMode;
  speed?: TranslationSpeed;
  autoOpen?: boolean;
}

class RybenaService {
  private api: RybenaApiInstance | null = null;
  private isLoaded: boolean = false;
  private config: RybenaServiceConfig;
  
  constructor(config: RybenaServiceConfig = {}) {
    this.config = {
      language: config.language || 'ptBR',
      mode: config.mode || 'libras',
      speed: config.speed || 1.0,
      autoOpen: config.autoOpen ?? true,
    };
  }
  
  async initialize(): Promise<void> {
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = `https://cdn.rybena.com.br/dom/master/latest/rybena.js?mode=api&lang=${this.config.language}`;
      script.async = true;
      
      script.onload = () => {
        if (window.RybenaApi) {
          this.api = window.RybenaApi.getInstance();
          
          this.api.handleLoaded(() => {
            this.isLoaded = true;
            
            if (this.config.autoOpen) {
              this.api.openPlayer();
            }
            
            if (this.config.mode === 'libras') {
              this.api.switchToLibras();
            } else {
              this.api.switchToVoz();
            }
            
            if (this.config.speed) {
              this.api.setSpeed(this.config.speed);
            }
            
            resolve();
          });
        } else {
          reject(new Error('Failed to load Rybena API'));
        }
      };
      
      script.onerror = () => {
        reject(new Error('Failed to load Rybena script'));
      };
      
      document.head.appendChild(script);
    });
  }
  
  translate(text: string): void {
    if (!this.api || !this.isLoaded) {
      throw new Error('Rybena API not initialized');
    }
    
    if (!text.trim()) {
      throw new Error('Text cannot be empty');
    }
    
    this.api.translate(text);
  }
  
  pause(): void {
    if (!this.api || !this.isLoaded) {
      throw new Error('Rybena API not initialized');
    }
    
    this.api.pause();
  }
  
  play(): void {
    if (!this.api || !this.isLoaded) {
      throw new Error('Rybena API not initialized');
    }
    
    this.api.play();
  }
  
  stop(): void {
    if (!this.api || !this.isLoaded) {
      throw new Error('Rybena API not initialized');
    }
    
    this.api.stop();
  }
  
  setMode(mode: TranslationMode): void {
    if (!this.api || !this.isLoaded) {
      throw new Error('Rybena API not initialized');
    }
    
    if (mode === 'libras') {
      this.api.switchToLibras();
    } else {
      this.api.switchToVoz();
    }
  }
  
  setSpeed(speed: TranslationSpeed): void {
    if (!this.api || !this.isLoaded) {
      throw new Error('Rybena API not initialized');
    }
    
    this.api.setSpeed(speed);
  }
  
  isTranslating(): boolean {
    if (!this.api || !this.isLoaded) {
      return false;
    }
    
    return this.api.isTranslating();
  }
  
  openPlayer(): void {
    if (!this.api || !this.isLoaded) {
      throw new Error('Rybena API not initialized');
    }
    
    this.api.openPlayer();
  }
  
  closePlayer(): void {
    if (!this.api || !this.isLoaded) {
      throw new Error('Rybena API not initialized');
    }
    
    this.api.closePlayer();
  }
  
  onTranslateComplete(callback: () => void): void {
    if (!this.api || !this.isLoaded) {
      throw new Error('Rybena API not initialized');
    }
    
    this.api.handleTranslate(callback);
  }
}

// Uso
const rybenaService = new RybenaService({
  language: 'ptBR',
  mode: 'libras',
  speed: 1.0,
  autoOpen: true,
});

await rybenaService.initialize();
rybenaService.translate('Olá, mundo!');

Exemplo 3: Gerenciador de Acessibilidade

Este exemplo mostra como criar um gerenciador de acessibilidade com TypeScript:

interface AccessibilityPreferences {
  lineHeight?: boolean;
  letterSpacing?: boolean;
  zoom?: boolean;
  contrast?: 'dark' | 'light' | 'inverted';
  saturation?: 'high' | 'low' | 'none';
  cursorSize?: boolean;
  cursorGuide?: boolean;
  dislexiaFont?: boolean;
  linkHighlight?: boolean;
  titleHighlight?: boolean;
  readingMask?: boolean;
  readingMode?: boolean;
  dictionary?: boolean;
  imageDescription?: boolean;
  pauseAnimations?: boolean;
  keyboardNavigation?: boolean;
}

class AccessibilityManager {
  private api: RybenaApiInstance | null = null;
  private preferences: AccessibilityPreferences = {};
  
  constructor(api: RybenaApiInstance) {
    this.api = api;
    this.loadPreferences();
  }
  
  private loadPreferences(): void {
    const saved = localStorage.getItem('rybenaAccessibility');
    if (saved) {
      try {
        this.preferences = JSON.parse(saved);
        this.applyPreferences();
      } catch (error) {
        console.error('Failed to load accessibility preferences:', error);
      }
    }
  }
  
  private savePreferences(): void {
    localStorage.setItem('rybenaAccessibility', JSON.stringify(this.preferences));
  }
  
  private applyPreferences(): void {
    if (!this.api) return;
    
    // Visual Adjustments
    if (this.preferences.lineHeight) {
      this.api.toggleLineHeight();
    }
    
    if (this.preferences.letterSpacing) {
      this.api.toggleLetterSpacing();
    }
    
    if (this.preferences.zoom) {
      this.api.toggleZoom();
    }
    
    if (this.preferences.contrast === 'dark') {
      this.api.toggleDarkContrast();
    } else if (this.preferences.contrast === 'light') {
      this.api.toggleLightContrast();
    } else if (this.preferences.contrast === 'inverted') {
      this.api.toggleInvertedContrast();
    }
    
    if (this.preferences.saturation === 'high') {
      this.api.toggleHighSaturation();
    } else if (this.preferences.saturation === 'low') {
      this.api.toggleLowSaturation();
    } else if (this.preferences.saturation === 'none') {
      this.api.toggleNoSaturation();
    }
    
    // Reading Aids
    if (this.preferences.cursorSize) {
      this.api.toggleCursorSize();
    }
    
    if (this.preferences.cursorGuide) {
      this.api.toggleCursorGuide();
    }
    
    if (this.preferences.dislexiaFont) {
      this.api.toggleDislexiaFont();
    }
    
    if (this.preferences.linkHighlight) {
      this.api.toggleLinkHighlight();
    }
    
    if (this.preferences.titleHighlight) {
      this.api.toggleTitleHighlight();
    }
    
    if (this.preferences.readingMask) {
      this.api.toggleReadingMask();
    }
    
    if (this.preferences.readingMode) {
      this.api.toggleReadingMode();
    }
    
    // Navigation & Other
    if (this.preferences.dictionary) {
      this.api.toggleDictionary();
    }
    
    if (this.preferences.imageDescription) {
      this.api.toggleImageDescription();
    }
    
    if (this.preferences.pauseAnimations) {
      this.api.togglePauseAnimations();
    }
    
    if (this.preferences.keyboardNavigation) {
      this.api.toggleKeyboardNavigation();
    }
  }
  
  toggleLineHeight(): void {
    if (!this.api) return;
    this.api.toggleLineHeight();
    this.preferences.lineHeight = !this.preferences.lineHeight;
    this.savePreferences();
  }
  
  toggleLetterSpacing(): void {
    if (!this.api) return;
    this.api.toggleLetterSpacing();
    this.preferences.letterSpacing = !this.preferences.letterSpacing;
    this.savePreferences();
  }
  
  toggleZoom(): void {
    if (!this.api) return;
    this.api.toggleZoom();
    this.preferences.zoom = !this.preferences.zoom;
    this.savePreferences();
  }
  
  setContrast(mode: 'dark' | 'light' | 'inverted' | null): void {
    if (!this.api) return;
    
    // Remove contraste anterior se existir
    if (this.preferences.contrast) {
      if (this.preferences.contrast === 'dark') {
        this.api.toggleDarkContrast();
      } else if (this.preferences.contrast === 'light') {
        this.api.toggleLightContrast();
      } else if (this.preferences.contrast === 'inverted') {
        this.api.toggleInvertedContrast();
      }
    }
    
    // Aplica novo contraste
    if (mode === 'dark') {
      this.api.toggleDarkContrast();
    } else if (mode === 'light') {
      this.api.toggleLightContrast();
    } else if (mode === 'inverted') {
      this.api.toggleInvertedContrast();
    }
    
    this.preferences.contrast = mode || undefined;
    this.savePreferences();
  }
  
  setSaturation(mode: 'high' | 'low' | 'none' | null): void {
    if (!this.api) return;
    
    // Remove saturação anterior se existir
    if (this.preferences.saturation) {
      if (this.preferences.saturation === 'high') {
        this.api.toggleHighSaturation();
      } else if (this.preferences.saturation === 'low') {
        this.api.toggleLowSaturation();
      } else if (this.preferences.saturation === 'none') {
        this.api.toggleNoSaturation();
      }
    }
    
    // Aplica nova saturação
    if (mode === 'high') {
      this.api.toggleHighSaturation();
    } else if (mode === 'low') {
      this.api.toggleLowSaturation();
    } else if (mode === 'none') {
      this.api.toggleNoSaturation();
    }
    
    this.preferences.saturation = mode || undefined;
    this.savePreferences();
  }
  
  getPreferences(): AccessibilityPreferences {
    return { ...this.preferences };
  }
}

// Uso
RybenaApi.getInstance().handleLoaded(() => {
  const api = RybenaApi.getInstance();
  const accessibilityManager = new AccessibilityManager(api);
  
  // Ativa altura da linha
  accessibilityManager.toggleLineHeight();
  
  // Define contraste escuro
  accessibilityManager.setContrast('dark');
  
  // Obtém preferências atuais
  const prefs = accessibilityManager.getPreferences();
  console.log('Preferências:', prefs);
});

Benefícios do TypeScript

Autocompletar: Com tipos definidos, seu IDE fornecerá autocompletar inteligente para todos os métodos da API Rybená.

Verificação de Erros: TypeScript detectará erros em tempo de compilação, como passar parâmetros incorretos ou chamar métodos que não existem.

Refatoração Segura: Com tipos, você pode refatorar seu código com confiança, sabendo que o TypeScript detectará qualquer quebra.

Documentação Viva: Os tipos servem como documentação, mostrando claramente quais parâmetros cada método aceita e quais valores retorna.

Boas Práticas

Sugestão: Crie tipos auxiliares para valores que têm um conjunto limitado de opções, como TranslationSpeed e TranslationMode.

Atenção: Sempre verifique se a API está carregada antes de chamar métodos. Use isLoaded ou verifique se api não é null.

Dica: Use classes de serviço ou hooks customizados para encapsular a lógica da API Rybená e tornar seu código mais reutilizável.

Sugestão: Salve as preferências do usuário em localStorage e use tipos para garantir que os dados sejam válidos.

Conteúdo Relacionado

Nesta página