Files
xas-test-library/console/static/related-files.js
T

64 lines
3.2 KiB
JavaScript

import {api, el, select, input, loading, note, errorBox, link, scriptUrl} from './ui.js';
const roles = {entry: '执行入口', support: '配套文件', self_test: '套件内部验证', variant: '相关实现 / 场景'};
export function relatedFiles(ctx, script) {
const content = el('div', {class: 'related-files'});
const panel = el('details', {class: 'panel disclosure'},
el('summary', {}, `相关文件 · ${script.file_count || 1} 个`), content);
let loaded = false;
let busy = false;
async function load() {
if (busy || loaded || !panel.open) return;
busy = true;
content.replaceChildren(loading('正在读取项目文件…'));
try {
const {items} = await api(`${scriptUrl(script.id)}/files`, {}, ctx.signal);
if (!ctx.alive) return;
const search = input({type: 'search', placeholder: '按文件名或路径查找', 'aria-label': '搜索相关文件'});
const choice = select('查看相关文件', []);
const info = el('p', {class: 'source-info mono'});
const feedback = el('div');
const download = link('下载此文件', '', 'button small');
const code = el('pre', {class: 'code', tabindex: '0', 'aria-label': '相关文件源代码,只读'});
content.replaceChildren(note('这里展示测试入口、配套模块和相关实现。查看文件不会改变本次测试的执行入口。'),
search, choice, info, download, feedback, code);
let generation = 0;
async function show() {
const ticket = ++generation;
const file = items.find(item => item.id === choice.value);
download.hidden = true;
feedback.replaceChildren();
info.textContent = file ? `${roles[file.role] || '配套文件'} · ${file.source_path}` : '';
code.textContent = file ? '正在读取…' : '没有匹配的文件。';
if (!file) return;
try {
const data = await api(`${scriptUrl(file.id)}/code`, {}, ctx.signal);
if (!ctx.alive || ticket !== generation) return;
code.textContent = data.code || '(空文件)';
download.href = `${scriptUrl(file.id)}/download`;
download.hidden = Boolean(data.redacted);
if (data.redacted) feedback.append(note('疑似凭据已隐藏,该文件不可下载。'));
} catch (error) {
if (ctx.alive && ticket === generation) { code.textContent = ''; feedback.replaceChildren(errorBox(error, show)); }
}
}
function filter() {
const term = search.value.toLowerCase();
const selected = choice.value;
const matching = items.filter(file => (file.name + file.source_path).toLowerCase().includes(term));
choice.replaceChildren(...matching.map(file => el('option', {value: file.id}, `${roles[file.role]} · ${file.relative_path}`)));
if (matching.some(file => file.id === selected)) choice.value = selected;
show();
}
choice.addEventListener('change', show);
search.addEventListener('input', filter);
loaded = true;
filter();
} catch (error) { if (ctx.alive) content.replaceChildren(errorBox(error, load)); }
finally { busy = false; }
}
panel.addEventListener('toggle', load);
return panel;
}