studio/src/community/templateScreenshots.js
МИН 61fba4e174
Some checks failed
CI / Lint + Format (push) Failing after 32s
CI / Build (push) Failing after 37s
CI / Secret scan (push) Failing after 37s
CI / PR size check (push) Has been skipped
fix: починка билда + studio.rublox.pro инфра
Большой консолидирующий коммит после поднятия studio.rublox.pro (28 мая 2026).
Содержит изменения которые делались в процессе подготовки прод-окружения:

Фиксы импортов после выноса из minecraftia:
- Массовая замена путей ../../components → ../components (40+ файлов в src/community/, src/admin-preview/)
- Замена ../KubikonEditor/ → ../editor/, ../KubikonStudio/ → ../community/, ../AdminPreview/ → ../admin-preview/
- API.js скопирован из минки целиком (было 8 экспортов, стало 312)
- Добавлены PLAYER_URL, MyButton_1, недостающие компоненты
- Заменены require() на статические ES-imports в BabylonScene, PrimitiveManager, GameRuntime (Vite не поддерживает CJS require)

Структура ассетов:
- public/kubikon-templates/ → public/assets/kubikon-templates/
- public/kubikon-learn/ → public/assets/kubikon-learn/
- (код искал в /assets/, файлы лежали без /assets/)

Навигация роутов внутри студии:
- /kubikon-studio/docs → /docs (90+ навигационных вызовов sed-replaced)
- /kubikon-editor/X → /edit/X, /kubikon/play/X → /play/X, /kubikon/gd/X → /gd/X

UI:
- Новый компонент StudioHeader (61px, как в минке) + копия favicon
- WithHeader wrapper в App.jsx для всех страниц кроме fullscreen-редактора/плеера
- SSO ticket-flow в AuthContext (auto-redeem #ticket= при загрузке)
- Тёмная тема карточек игр в ВИКИ (фон #1c2231 вместо #fff, картинка впритык)

Документация:
- docs/ONBOARDING.md — путь нового контрибьютора от нуля до PR
- docs/TUTORIAL_ADD_SCRIPT_API.md — как добавить game.* API
- API_USAGE.md — список эндпоинтов backend
- README в подпапках engine/, engine/terrain/, engine/voxel/, engine/robloxterrain/, engine/types/

.gitignore:
- public/wiki/ исключён (73МБ PNG, будут на CDN отдельной задачей)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 05:01:13 +03:00

122 lines
5.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* templateScreenshots.js — генерирует PNG-скриншоты шаблонов offscreen.
*
* Запускается dev-кнопкой «Перегенерировать превью» в KubikonStudio.
* Для каждого шаблона:
* 1) создаёт оффскрин Babylon engine + scene
* 2) грузит state шаблона через scene.loadFromState
* 3) ждёт следующего кадра (модели подгружаются async)
* 4) делает canvas.toDataURL('image/jpeg')
* 5) триггерит скачивание файла
*
* Полученные PNG-файлы пользователь кладёт в
* public/assets/kubikon-templates/<id>.jpg
* И они подхватываются как обычные статические картинки.
*/
import { BabylonScene } from '../editor/engine/BabylonScene';
import { TEMPLATES } from './templates';
const SCREENSHOT_W = 640;
const SCREENSHOT_H = 360;
/**
* Сгенерировать скриншот одного шаблона. Возвращает Promise<dataUrl>.
*/
async function captureOne(template) {
// Временный canvas, добавляем в DOM (Babylon engine требует видимый canvas
// для GL-контекста — но мы скрываем визуально через position:absolute + opacity:0).
const canvas = document.createElement('canvas');
canvas.width = SCREENSHOT_W;
canvas.height = SCREENSHOT_H;
canvas.style.position = 'fixed';
canvas.style.left = '-9999px';
canvas.style.top = '0';
canvas.style.width = `${SCREENSHOT_W}px`;
canvas.style.height = `${SCREENSHOT_H}px`;
canvas.style.opacity = '0';
canvas.style.pointerEvents = 'none';
canvas.tabIndex = -1;
document.body.appendChild(canvas);
let scene = null;
try {
scene = new BabylonScene(canvas);
scene.init();
// Стандартная камера для красивого ракурса (3/4 сверху)
if (scene.camera) {
scene.camera.position.set(60, 60, 60);
// Нацеливаем камеру в (0, 5, 0) через yaw/pitch.
// UniversalCamera использует rotation.y = yaw, rotation.x = pitch.
const tx = 0, ty = 5, tz = 0;
const dx = tx - scene.camera.position.x;
const dy = ty - scene.camera.position.y;
const dz = tz - scene.camera.position.z;
scene.camera.rotation.y = Math.atan2(dx, dz);
const horiz = Math.sqrt(dx * dx + dz * dz);
scene.camera.rotation.x = -Math.atan2(dy, horiz);
scene.camera.rotation.z = 0;
}
const state = template.build();
await scene.loadFromState(state);
// Прогреваем 3 кадра и ждём — модели подгружаются async, материалы
// компилятся в первом render-loop.
for (let i = 0; i < 8; i++) {
scene.scene.render();
await new Promise(res => requestAnimationFrame(res));
}
// Дополнительная пауза 500мс для GLB-моделей
await new Promise(res => setTimeout(res, 500));
for (let i = 0; i < 4; i++) {
scene.scene.render();
await new Promise(res => requestAnimationFrame(res));
}
// Снимок
const dataUrl = canvas.toDataURL('image/jpeg', 0.85);
return dataUrl;
} finally {
// Чистим
try { scene?.dispose?.(); } catch (e) { /* ignore */ }
try { document.body.removeChild(canvas); } catch (e) { /* ignore */ }
}
}
/**
* Триггер скачивания PNG-файла в браузере.
*/
function downloadDataUrl(dataUrl, filename) {
const a = document.createElement('a');
a.href = dataUrl;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
/**
* Сгенерировать скриншоты ВСЕХ шаблонов и скачать их по одному.
* Возвращает Promise который резолвится массивом dataUrl'ов.
*
* @param {(progress:{current:number,total:number,id:string}) => void} onProgress
*/
export async function generateAllTemplateScreenshots(onProgress) {
const results = [];
for (let i = 0; i < TEMPLATES.length; i++) {
const tpl = TEMPLATES[i];
if (onProgress) onProgress({ current: i + 1, total: TEMPLATES.length, id: tpl.id });
// eslint-disable-next-line no-await-in-loop
const dataUrl = await captureOne(tpl);
downloadDataUrl(dataUrl, `${tpl.id}.jpg`);
results.push({ id: tpl.id, dataUrl });
// Небольшая пауза между генерациями чтобы браузер не тормозил
// eslint-disable-next-line no-await-in-loop
await new Promise(res => setTimeout(res, 200));
}
return results;
}