148 lines
11 KiB
JavaScript
148 lines
11 KiB
JavaScript
// First-run guidance uses the same native connection and preferences as downloads.
|
|
export function createOnboarding({ call, connectHelper, getHelper, applyPreferences, onClose }) {
|
|
const $ = id => document.getElementById(id);
|
|
const dialog = $("setup");
|
|
let step = 0, working = false, saved = {};
|
|
let videoFormat = "mp4", audioFormat = "m4a";
|
|
|
|
function error(message = "") {
|
|
$("setup-error").textContent = message;
|
|
$("setup-error").hidden = !message;
|
|
}
|
|
function formats(remember = true) {
|
|
if (remember && $("setup-format").value) {
|
|
if ($("setup-format").dataset.mode === "audio") audioFormat = $("setup-format").value;
|
|
else videoFormat = $("setup-format").value;
|
|
}
|
|
const audio = $("setup-mode").value === "audio";
|
|
const items = audio ? [["m4a", "M4A · AAC"], ["mp3", "MP3"], ["opus", "Opus"], ["flac", "FLAC"], ["wav", "WAV"]]
|
|
: [["mp4", "MP4"], ["mkv", "MKV"], ["webm", "WebM"]];
|
|
$("setup-format").replaceChildren(...items.map(([value, label]) => new Option(label, value)));
|
|
$("setup-format").dataset.mode = audio ? "audio" : "video";
|
|
$("setup-format").value = audio ? audioFormat : videoFormat;
|
|
$("setup-quality-field").hidden = audio;
|
|
$("setup-format-hint").textContent = audio ? "Сохранится только звуковая дорожка. Формат не повышает исходное качество звука."
|
|
: $("setup-mode").value === "video_only" ? "Сохранится видео без звука. Если выбранного качества нет, используем ближайшее доступное."
|
|
: "Отдельный звук автоматически добавится к видео. Если выбранного качества нет, используем ближайшее доступное.";
|
|
}
|
|
function ready() { const helper = getHelper(); return helper.status === "running" && helper.hardware?.ready === true; }
|
|
function render() {
|
|
const helper = getHelper(), hardware = helper.hardware;
|
|
const checking = helper.status === "checking";
|
|
$("setup-helper").dataset.state = ready() ? "running" : helper.status;
|
|
$("setup-helper-status").textContent = ready() ? "Помощник и компоненты готовы" : checking ? "Проверяем помощник…"
|
|
: helper.status === "incompatible" ? "Нужно обновить версии" : helper.status === "running" ? "Комплект нужно восстановить" : helper.status === "missing" ? "Нужно установить помощник" : "Подключите помощник";
|
|
const dependencies = hardware?.dependencies || [];
|
|
$("setup-helper-message").textContent = ready()
|
|
? `${dependencies.filter(item => item.status === "installed").length}/${dependencies.length} компонентов готовы. Дополнительные программы устанавливать не нужно.`
|
|
: helper.message || "Скачайте установщик, установите комплект и проверьте подключение.";
|
|
const helperVersion = hardware?.version || hardware?.bundle?.version;
|
|
$("setup-versions").textContent = `Расширение ${chrome.runtime.getManifest().version} · Помощник ${helperVersion || "не проверен"}${ready() ? " · Версии совместимы" : ""}`;
|
|
$("setup-versions").dataset.state = helper.status;
|
|
$("setup-install").hidden = ready();
|
|
$("setup-install-steps").hidden = ready() || helper.compatibility?.update === "extension";
|
|
$("setup-install").textContent = helper.compatibility?.update === "extension" ? "Обновить расширение" : helper.compatibility?.update === "helper" ? "Обновить помощник" : helper.status === "running" && !ready() ? "Скачать для восстановления" : "Скачать в релизах";
|
|
$("setup-check").disabled = working || checking;
|
|
$("setup-pick-folder").disabled = working || !ready();
|
|
$("setup-pick-work-folder").disabled = working || !ready();
|
|
$("setup-check").textContent = checking ? "Проверяем…" : "Проверить подключение";
|
|
$("setup-gpu").hidden = !ready();
|
|
if (ready()) {
|
|
const engines = [hardware.nvenc?.length && "NVIDIA NVENC", hardware.amf?.length && "AMD AMF"].filter(Boolean);
|
|
$("setup-gpu").textContent = `${hardware.gpu || "Видеокарта не определена"}. ${engines.length ? `Проверено ускорение: ${engines.join(", ")}.` : "Ускорение недоступно, конвертация будет выполняться на CPU."}`;
|
|
}
|
|
const updates = ready() && hardware.supportsComponentUpdates === true;
|
|
$("setup-updates-row").hidden = $("setup-updates-note").hidden = !updates;
|
|
$("setup-updates").disabled = working;
|
|
if (!working) $("setup-updates").checked = hardware?.updateSettings?.enabled === true;
|
|
$("setup-back").hidden = step === 0;
|
|
$("setup-back").disabled = working;
|
|
$("setup-next").disabled = working || ([1, 2].includes(step) && !ready());
|
|
$("setup-next").textContent = ["Начать настройку", "Продолжить", "Сохранить и продолжить", "Готово"][step];
|
|
$("setup-skip").hidden = step === 3;
|
|
$("setup-close").disabled = $("setup-skip").disabled = working;
|
|
// A typed or selected destination is never replaced by a health refresh.
|
|
if (!$("setup-folder").value && ready()) $("setup-folder").value = saved.folder || hardware.defaultFolder || "";
|
|
}
|
|
function showStep(next) {
|
|
step = next; error();
|
|
const titles = ["Сохраняйте ролики в один клик", "Подключим локальный помощник", "Выберите настройки скачивания", "Всё готово к скачиванию"];
|
|
$("setup-title").textContent = titles[step];
|
|
for (const section of dialog.querySelectorAll("[data-setup-step]")) section.hidden = Number(section.dataset.setupStep) !== step;
|
|
for (const [index, item] of [...dialog.querySelectorAll(".setup-progress li")].entries()) {
|
|
item.dataset.state = index < step ? "complete" : index === step ? "current" : "pending";
|
|
if (index === step) item.setAttribute("aria-current", "step"); else item.removeAttribute("aria-current");
|
|
}
|
|
$("setup-saved").textContent = `Файлы будут сохраняться в ${saved.folder || "выбранную папку"}.${saved.workFolder ? ` Загрузка и обработка — в ${saved.workFolder}.` : ""}`;
|
|
$("setup-title").focus(); $("setup-title").parentElement.scrollTop = 0; render();
|
|
}
|
|
async function close(state) {
|
|
if (working) return;
|
|
try {
|
|
await chrome.storage.local.set({ onboarding: { version: 1, state, finishedAt: Date.now() } });
|
|
dialog.close(); onClose();
|
|
} catch (failure) { error(`Не удалось сохранить настройку: ${failure.message}`); }
|
|
}
|
|
async function run(task) {
|
|
if (working) return;
|
|
working = true; error(); render();
|
|
try { await task(); } catch (failure) { error(failure.message); }
|
|
finally { working = false; render(); }
|
|
}
|
|
$("setup-check").addEventListener("click", () => run(connectHelper));
|
|
$("setup-install").addEventListener("click", () => {
|
|
$("setup-helper-message").textContent = "Скачайте YouTubeDL-Helper-Setup.exe на открывшейся странице релизов проекта, запустите его и вернитесь сюда для проверки подключения.";
|
|
});
|
|
$("setup-updates").addEventListener("change", () => {
|
|
const enabled = $("setup-updates").checked;
|
|
run(async () => {
|
|
const settings = await call("update_settings", { enabled });
|
|
if (getHelper().hardware) getHelper().hardware.updateSettings = settings;
|
|
});
|
|
});
|
|
$("setup-pick-folder").addEventListener("click", () => run(async () => {
|
|
const result = await call("pick_folder", { current: $("setup-folder").value });
|
|
if (result.folder) $("setup-folder").value = result.folder;
|
|
}));
|
|
$("setup-pick-work-folder").addEventListener("click", () => run(async () => {
|
|
const result = await call("pick_folder", { current: $("setup-work-folder").value || $("setup-folder").value });
|
|
if (result.folder) $("setup-work-folder").value = result.folder;
|
|
}));
|
|
$("setup-mode").addEventListener("change", () => formats());
|
|
$("setup-back").addEventListener("click", () => showStep(step - 1));
|
|
$("setup-next").addEventListener("click", async () => {
|
|
if (working || ([1, 2].includes(step) && !ready())) return;
|
|
if (step === 3) { await close("complete"); return; }
|
|
if (step === 2) {
|
|
if (!$("setup-folder").value.trim()) { error("Выберите папку для сохранения файлов."); $("setup-folder").focus(); return; }
|
|
await run(async () => {
|
|
formats();
|
|
const quality = $("setup-quality").value;
|
|
const preferences = { ...saved, folder: $("setup-folder").value.trim(), workFolder: $("setup-work-folder").value.trim(), mode: $("setup-mode").value,
|
|
videoContainer: videoFormat, audioContainer: audioFormat, codec: "auto", engine: "auto", preset: "balanced",
|
|
bitrate: saved.bitrate || 256, videoId: quality === "best" ? "best" : "setup-quality", audioId: "best",
|
|
quality: quality === "best" ? null : { height: Number(quality), fps: 30, codec: "" } };
|
|
await chrome.storage.local.set({ preferences }); saved = preferences;
|
|
applyPreferences(preferences); showStep(3);
|
|
});
|
|
} else showStep(step + 1);
|
|
});
|
|
for (const id of ["setup-close", "setup-skip"]) $(id).addEventListener("click", () => close("skipped"));
|
|
dialog.addEventListener("cancel", event => { event.preventDefault(); close("skipped"); });
|
|
return {
|
|
get isOpen() { return dialog.open; },
|
|
render,
|
|
async open() {
|
|
if (dialog.open) return;
|
|
const stored = await chrome.storage.local.get("preferences"); saved = stored.preferences || {};
|
|
videoFormat = saved.videoContainer || "mp4"; audioFormat = saved.audioContainer || "m4a";
|
|
$("setup-folder").value = saved.folder || getHelper().hardware?.defaultFolder || "";
|
|
$("setup-work-folder").value = saved.workFolder || "";
|
|
$("setup-mode").value = ["video", "video_only", "audio"].includes(saved.mode) ? saved.mode : "video";
|
|
$("setup-quality").value = saved.videoId !== "best" && saved.quality?.height ? String(saved.quality.height) : "best";
|
|
if (!$("setup-quality").value) $("setup-quality").value = "best";
|
|
formats(false); dialog.showModal(); showStep(0);
|
|
},
|
|
};
|
|
}
|