Especificação de API e Integração Moodle¶
O funcionamento do Módulo de Cenários Interativos baseia-se em uma arquitetura híbrida. A estrutura visual (layout, polígonos, estilos) é carregada via JSON estático da CDN, enquanto o estado de permissão (acesso liberado/bloqueado) e os links finais são resolvidos dinamicamente consultando a API interna do Moodle.
1. Definição de Cenário (Scenario Definition / Static JSON)¶
Este arquivo atua como a Fonte de Verdade Visual, determinando o layout, os hotspots e os comportamentos da interface:
- Regras Visuais: Instrui o renderizador sobre o posicionamento exato dos elementos.
- Imutabilidade: É um arquivo JSON estático, servido via CDN e otimizado para cache.
- Geometria Avançada: Suporta coordenadas simples e formas complexas (polígonos em perspectiva) e comportamentos de modal (Exemplo: Expandir a visualização de um TV que compõe um cenário).
- Ponto de Integração: A propriedade
moodle.activityIdatua como chave de ligação com o estado dinâmico do Moodle.
1.1 Schema do Objeto (TypeScript: CenarioConfig)¶
A estrutura suporta hotspots retangulares simples, formas complexas (polígonos) e aninhamento para modais (TV expandida).
export interface CenarioConfig {
/** Identificador único do cenário para cache/logs */
id: string;
/** URL absoluta da imagem de fundo */
image: string;
/** Dimensões originais para cálculo de proporção (Reference Resolution) */
width: number;
height: number;
/** Lista de zonas interativas */
hotspots: Hotspot[];
}
export interface Hotspot {
id: string;
// --- Geometria e Estilo ---
/**
* Se definido, usa clip-path: polygon() para formas irregulares (ex: porta em perspectiva).
* Matriz de coordenadas: [[x1, y1], [x2, y2], ...]
*/
points?: number[][];
/** Se points não for definido, usa geometria retangular padrão */
x?: number;
y?: number;
width?: number;
height?: number;
/** Classe CSS para estilização específica (ex: 'glass-door', 'tv-screen') */
variant?: string;
/** Estilo inline para ajustes finos (ex: 'opacity: 0.5; transform: skewY(10deg)') */
customStyle?: string;
// --- Comportamento ---
/** Define o que acontece ao clicar. Default: 'navigate' */
action?: 'navigate' | 'modal';
/** Obrigatório se action === 'modal'. Define o conteúdo da TV/Zoom */
modalData?: CenarioConfig;
// --- Acessibilidade ---
title: string; // Texto do Tooltip (vísivel em mouseover)
ariaLabel: string; // Leitura de tela
// --- Integração Dinâmica (Moodle - Resolução de Link)
moodle?: {
/**
* 'activity': Busca status/link de uma atividade específica (Padrão).
* 'course': Gera link para a raiz do curso atual.
*/
type?: 'activity' | 'course';
/**
* ID do Módulo de Atividade(cmid)
* O JS usará este ID para buscar a URL real e o status de acesso no Moodle.
* Obrigatório se type === 'activity'.
*/
activityId?: number;
};
// --- Fallback (Links Externos)
// Usado apenas se 'moodle.activityId' não for definido ou se for um link externo
link?: {
url: string;
target: '_self' | '_blank';
};
}
1.2 Exemplo de JSON (CDN)¶
Exemplo cobrindo: Porta em perspectiva (Polígono), TV Expandida (Modal) e Botão Voltar (Home).
{
"id": "cenario-home-v1",
"image": "https://cdn.exemplo.com/assets/images/cenario-geral-capa.jpg",
"width": 1920,
"height": 1080,
"hotspots": [
{
"id": "btn_forum_avisos",
"x": 100,
"y": 200,
"width": 150,
"height": 150,
"title": "Avisos Gerais",
"ariaLabel": "Acessar Fórum de Avisos",
"moodle": {
"type": "activity",
"activityId": 1058
}
},
{
"id": "porta_vidro_esquerda",
"title": "Acessar Triagem",
"ariaLabel": "Entrar na sala de Triagem",
"variant": "glass-door",
"points": [ [100, 200], [300, 150], [300, 800], [100, 900] ],
"action": "navigate",
"moodle": {
"type": "activity",
"activityId": 1058
}
},
{
"id": "tv_informativa",
"title": "Ver Materiais Complementares",
"ariaLabel": "Expandir Televisão",
"variant": "tv-screen",
"x": 800, "y": 400, "width": 320, "height": 180,
"action": "modal",
"modalData": {
"id": "modal-tv",
"image": "https://cdn.exemplo.com/assets/tv-expandida.jpg",
"width": 1920, "height": 1080,
"hotspots": [
{
"id": "btn_video_aula",
"x": 200, "y": 500, "width": 300, "height": 100,
"title": "Assistir Vídeo",
"ariaLabel": "Assistir Vídeo",
"moodle": { "activityId": 2001 }
}
]
}
},
{
"id": "btn_sair",
"title": "Voltar ao Curso",
"ariaLabel": "Sair do Cenário",
"variant": "btn-exit",
"x": 1800, "y": 50, "width": 80, "height": 80,
"moodle": {
"type": "course"
}
},
{
"id": "link_externo_biblioteca",
"x": 900,
"y": 800,
"width": 80,
"height": 80,
"title": "Biblioteca Virtual",
"ariaLabel": "Acessar Biblioteca Externa",
"link": {
"url": "https://bce.unb.br",
"target": "_blank"
}
}
]
}
2. Integração com Estado do Curso (Moodle AJAX)¶
O script utiliza a API de serviço do Moodle para obter a árvore de atividades do curso atual e determinar o acesso, permitindo resolver links dinâmicos e verificar permissões de forma segura.
2.1 Detecção de Ambiente¶
Antes de realizar chamadas, o script valida se está rodando dentro do Moodle e extrai as credenciais da sessão:
const isMoodleEnv = typeof window.M !== 'undefined' && window.M.cfg;
const wwwroot = isMoodleEnv ? window.M.cfg.wwwroot : '';
const sesskey = isMoodleEnv ? window.M.cfg.sesskey : '';
const courseId = isMoodleEnv ? document.querySelector('body')?.classList.value.match(/course-(\d+)/)?.[1] : null;
2.2 Requisição de Serviço (core_courseformat_get_state)¶
Esta chamada retorna a árvore completa do curso, incluindo visibilidade e URLs geradas pelo backend.
Endpoint: ${wwwroot}/lib/ajax/service.php?sesskey=${sesskey}
Método: POST
Payload:
[
{
"index": 0,
"methodname": "core_courseformat_get_state",
"args": {
"courseid": 33 // ID capturado do body (HTML)
}
}
]
2.3 Resposta Real (Moodle)¶
O Moodle retorna um array onde o campo data contém uma string JSON (double-encoded). O script deve fazer o parse dessa string para acessar a lista cm (Course Modules).
{
"course": {
"id": "33",
"baseurl": "https://licosc.unb.br/course/view.php?id=33"
},
"cm": [
{
"id": "123",
"name": "Avisos",
"modname": "forum",
"visible": true,
"uservisible": true,
"url": "https://licosc.unb.br/mod/forum/view.php?id=123"
},
{
"id": "456",
"name": "Atividade Prática",
"modname": "middag",
"visible": true,
"uservisible": false,
"url": "https://licosc.unb.br/mod/middag/view.php?id=456"
}
]
}
Nota: O JSON acima foi simplificado para exibir apenas os campos relevantes.
2.4 Lógica de Processamento¶
Para cada hotspot definido no JSON visual:
-
Tipo
course:- Não requer busca no array
cm. - Gera URL direta:
${wwwroot}/course/view.php?id=${courseId}.
- Não requer busca no array
-
Tipo
activity(comactivityId):- Busca: Itera sobre o array
cmda resposta para encontrar o objeto comidcorrespondente. - Resolução de Link: Atualiza a URL do hotspot com o valor de
cm.url. Isso garante integridade mesmo se a URL mudar no servidor (se os IDs forem iguais). - Controle de Acesso: Verifica a propriedade
uservisible.true: O hotspot é habilitado e clicável.false: O hotspot é bloqueado (classe.middag-app-cn-locked), prevenindo o clique e exibindo tooltip de "Acesso Restrito".
- Busca: Itera sobre o array
2.5 Tratamento de Erros (Fail Open)¶
Caso a chamada à API do Moodle falhe (ex: Timeout, Erro 500 ou resposta malformada), o sistema adota uma postura de Fail Open (Liberar Acesso) para não prejudicar a experiência do usuário:
- Estado Visual: Assume que o hotspot está Habilitado (remove bloqueios visuais).
- Resolução de URL:
- Tenta usar o objeto
linkdefinido explicitamente do JSON (se existir). - Se não, tenta construir uma URL genérica:
.../mod/{modname}/view.php?id={activityId}.
- Tenta usar o objeto
- Comportamento: Permite o clique e o aluno acessa o recurso. Se o aluno realmente não tiver permissão, o próprio Moodle exibirá sua página padrão de "Acesso Negado", se necessário.
3. Mock Data (Desenvolvimento/Homologação)¶
Para testar fora do Moodle (localhost/Cloudflare Pages), utilizamos um arquivo estático que simula a estrutura de resposta da API service.php.
Arquivo: /public/mocks/moodle-state.json
[
{
"error": false,
"data": "{\"course\":{\"id\":\"33\"},\"section\":[{\"id\":\"334\",\"title\":\"Geral\"}],\"cm\":[{\"id\":\"1058\",\"name\":\"Avisos\",\"uservisible\":true,\"url\":\"https://licosc.unb.br/mod/forum/view.php?id=1058\"},{\"id\":\"1069\",\"name\":\"Diagnóstico inicial\",\"uservisible\":false,\"url\":\"https://licosc.unb.br/mod/assign/view.php?id=1069\"}]}"
}
]
3.1 Interface TypeScript¶
Para garantir tipagem forte no tratamento dos dados:
export interface MoodleResponse {
error: boolean;
data: string; // JSON stringified interno
}
export interface MoodleStateResponse {
course: {
id: string;
};
cm: MoodleCourseModule[];
}
export interface MoodleCourseModule {
id: string | number; // Moodle retorna string, converter para number na comparação
name: string;
modname: string; // ex: 'page', 'quiz', 'forum'
visible: boolean; // Visibilidade global
uservisible: boolean; // Define se o aluno pode clicar (Checa restrições)
url: string; // URL final resolvida
}
Próximo passo na leitura: Entenda como o componente renderiza visualmente estas regras na Especificação do Componente.