mirror of
https://gitlab.com/vyla-entertainment/home.git
synced 2026-11-09 19:57:20 -05:00
updated debug
This commit is contained in:
181
file-updater.js
181
file-updater.js
@@ -27,7 +27,9 @@ const sha256Buf = (buf) => crypto.createHash('sha256').update(buf).digest('hex')
|
|||||||
const sha256File = (p) => sha256Buf(fs.readFileSync(p));
|
const sha256File = (p) => sha256Buf(fs.readFileSync(p));
|
||||||
const gitBlobSha = (buf) => crypto.createHash('sha1').update(Buffer.concat([Buffer.from(`blob ${buf.length}\0`), buf])).digest('hex');
|
const gitBlobSha = (buf) => crypto.createHash('sha1').update(Buffer.concat([Buffer.from(`blob ${buf.length}\0`), buf])).digest('hex');
|
||||||
|
|
||||||
function fetchBuffer(url, { headers = {}, maxBytes, hosts, redirects = 0 }) {
|
const hooks = { notice: null };
|
||||||
|
|
||||||
|
function fetchBuffer(url, { headers = {}, maxBytes, hosts, redirects = 0, retries = 0 }) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (redirects > 5) return reject(new Error('Too many redirects'));
|
if (redirects > 5) return reject(new Error('Too many redirects'));
|
||||||
let u;
|
let u;
|
||||||
@@ -48,6 +50,15 @@ function fetchBuffer(url, { headers = {}, maxBytes, hosts, redirects = 0 }) {
|
|||||||
fetchBuffer(next, { headers: keep, maxBytes, hosts, redirects: redirects + 1 }).then(resolve, reject);
|
fetchBuffer(next, { headers: keep, maxBytes, hosts, redirects: redirects + 1 }).then(resolve, reject);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (res.statusCode === 429 && retries < 4) {
|
||||||
|
res.resume();
|
||||||
|
const wait = Math.min(parseInt(res.headers['retry-after'], 10) || 5, 30) * 1000;
|
||||||
|
if (hooks.notice) hooks.notice(`GitLab rate limit (HTTP 429). Retrying in ${Math.round(wait / 1000)}s (${retries + 1}/4)...`);
|
||||||
|
setTimeout(() => {
|
||||||
|
fetchBuffer(url, { headers, maxBytes, hosts, redirects, retries: retries + 1 }).then(resolve, reject);
|
||||||
|
}, wait);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (res.statusCode !== 200) {
|
if (res.statusCode !== 200) {
|
||||||
res.resume();
|
res.resume();
|
||||||
reject(new Error(`HTTP ${res.statusCode}`));
|
reject(new Error(`HTTP ${res.statusCode}`));
|
||||||
@@ -121,12 +132,21 @@ class FileUpdater {
|
|||||||
this.statePath = path.join(appDataPath, 'file-state.json');
|
this.statePath = path.join(appDataPath, 'file-state.json');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set onNotice(fn) {
|
||||||
|
hooks.notice = fn;
|
||||||
|
}
|
||||||
|
|
||||||
sdkHeaders() {
|
sdkHeaders() {
|
||||||
const h = { Accept: 'application/json' };
|
const h = { Accept: 'application/json' };
|
||||||
if (process.env.NPM_TOKEN) h.Authorization = `Bearer ${process.env.NPM_TOKEN}`;
|
if (process.env.NPM_TOKEN) h.Authorization = `Bearer ${process.env.NPM_TOKEN}`;
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
liveHeaders(extra = {}) {
|
||||||
|
const t = this.updateClient && this.updateClient.token;
|
||||||
|
return t ? { ...extra, 'PRIVATE-TOKEN': t } : extra;
|
||||||
|
}
|
||||||
|
|
||||||
target(rel) {
|
target(rel) {
|
||||||
const root = path.resolve(this.appDataPath);
|
const root = path.resolve(this.appDataPath);
|
||||||
const full = path.resolve(root, ...rel.split('/'));
|
const full = path.resolve(root, ...rel.split('/'));
|
||||||
@@ -139,17 +159,28 @@ class FileUpdater {
|
|||||||
const s = JSON.parse(fs.readFileSync(this.statePath, 'utf8'));
|
const s = JSON.parse(fs.readFileSync(this.statePath, 'utf8'));
|
||||||
return {
|
return {
|
||||||
seq: typeof s.seq === 'number' ? s.seq : null,
|
seq: typeof s.seq === 'number' ? s.seq : null,
|
||||||
base: s.base && typeof s.base === 'object' ? s.base : {}
|
base: s.base && typeof s.base === 'object' ? s.base : {},
|
||||||
|
remote: s.remote && typeof s.remote === 'object' ? s.remote : {},
|
||||||
|
local: s.local && typeof s.local === 'object' ? s.local : {},
|
||||||
|
commits: s.commits && typeof s.commits === 'object' ? s.commits : {}
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { seq: null, base: {} };
|
return { seq: null, base: {}, remote: {}, local: {}, commits: {} };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writeState(state) {
|
writeState(state) {
|
||||||
|
const cur = this.readState();
|
||||||
|
const next = {
|
||||||
|
seq: state.seq !== undefined ? state.seq : cur.seq,
|
||||||
|
base: state.base || cur.base,
|
||||||
|
remote: state.remote || cur.remote,
|
||||||
|
local: state.local || cur.local,
|
||||||
|
commits: state.commits || cur.commits
|
||||||
|
};
|
||||||
fs.mkdirSync(this.appDataPath, { recursive: true });
|
fs.mkdirSync(this.appDataPath, { recursive: true });
|
||||||
const tmp = `${this.statePath}.tmp`;
|
const tmp = `${this.statePath}.tmp`;
|
||||||
fs.writeFileSync(tmp, JSON.stringify(state), 'utf8');
|
fs.writeFileSync(tmp, JSON.stringify(next), 'utf8');
|
||||||
fs.renameSync(tmp, this.statePath);
|
fs.renameSync(tmp, this.statePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +200,7 @@ class FileUpdater {
|
|||||||
const dir = path.join(this.appDataPath, root);
|
const dir = path.join(this.appDataPath, root);
|
||||||
if (fs.existsSync(dir)) walk(dir, root);
|
if (fs.existsSync(dir)) walk(dir, root);
|
||||||
}
|
}
|
||||||
this.writeState({ seq: this.readState().seq, base });
|
this.writeState({ seq: this.readState().seq, base, remote: {}, local: {}, commits: {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
zipMatches(zipPath, sha256) {
|
zipMatches(zipPath, sha256) {
|
||||||
@@ -303,7 +334,7 @@ class FileUpdater {
|
|||||||
const out = [];
|
const out = [];
|
||||||
for (let page = 1; page <= 50; page++) {
|
for (let page = 1; page <= 50; page++) {
|
||||||
const url = `${REPO_API}${root}/repository/tree?recursive=true&per_page=100&ref=main&page=${page}`;
|
const url = `${REPO_API}${root}/repository/tree?recursive=true&per_page=100&ref=main&page=${page}`;
|
||||||
const raw = await fetchBuffer(url, { headers: { Accept: 'application/json' }, maxBytes: 5 * 1024 * 1024, hosts: LIVE_HOSTS });
|
return fetchBuffer(`${REPO_API}${root}/repository/files/${file}/raw?ref=main`, { headers: this.liveHeaders(), maxBytes: MAX_FILE, hosts: LIVE_HOSTS });
|
||||||
const items = JSON.parse(raw.toString('utf8'));
|
const items = JSON.parse(raw.toString('utf8'));
|
||||||
if (!Array.isArray(items) || items.length === 0) break;
|
if (!Array.isArray(items) || items.length === 0) break;
|
||||||
out.push(...items);
|
out.push(...items);
|
||||||
@@ -312,34 +343,133 @@ class FileUpdater {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadArchive(root) {
|
||||||
|
const buf = await fetchBuffer(`${REPO_API}${root}/repository/archive.zip?sha=main`, { headers: this.liveHeaders(), maxBytes: 600 * 1024 * 1024, hosts: LIVE_HOSTS });
|
||||||
|
const zip = new AdmZip(buf);
|
||||||
|
const map = new Map();
|
||||||
|
for (const entry of zip.getEntries()) {
|
||||||
|
if (entry.isDirectory) continue;
|
||||||
|
const name = entry.entryName;
|
||||||
|
const i = name.indexOf('/');
|
||||||
|
if (i < 0) continue;
|
||||||
|
map.set(`${root}/${name.slice(i + 1)}`, entry);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
fetchRepoFile(rel) {
|
fetchRepoFile(rel) {
|
||||||
|
const hit = this.archive && this.archive.get(rel);
|
||||||
|
if (hit) return Promise.resolve(hit.getData());
|
||||||
const parts = rel.split('/');
|
const parts = rel.split('/');
|
||||||
const root = parts.shift();
|
const root = parts.shift();
|
||||||
const file = encodeURIComponent(parts.join('/'));
|
const file = encodeURIComponent(parts.join('/'));
|
||||||
return fetchBuffer(`${REPO_API}${root}/repository/files/${file}/raw?ref=main`, { maxBytes: MAX_FILE, hosts: LIVE_HOSTS });
|
return fetchBuffer(`${REPO_API}${root}/repository/files/${file}/raw?ref=main`, { maxBytes: MAX_FILE, hosts: LIVE_HOSTS });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async headCommit(root) {
|
||||||
|
try {
|
||||||
|
const raw = await fetchBuffer(`${REPO_API}${root}/repository/commits/main`, {
|
||||||
|
headers: this.liveHeaders({ Accept: 'application/json' }),
|
||||||
|
maxBytes: 1024 * 1024,
|
||||||
|
hosts: LIVE_HOSTS,
|
||||||
|
retries: 3
|
||||||
|
});
|
||||||
|
const id = JSON.parse(raw.toString('utf8')).id;
|
||||||
|
return /^[0-9a-f]{40}$/.test(id) ? id : null;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localIntact(root, state) {
|
||||||
|
const prefix = `${root}/`;
|
||||||
|
const keys = Object.keys(state.remote).filter((k) => k.startsWith(prefix));
|
||||||
|
if (keys.length === 0) return false;
|
||||||
|
for (const rel of keys) {
|
||||||
|
try {
|
||||||
|
if (sha256File(this.target(rel)) !== state.local[rel]) return false;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
async checkRepos() {
|
async checkRepos() {
|
||||||
const state = this.readState();
|
const state = this.readState();
|
||||||
const changes = [];
|
const changes = [];
|
||||||
|
const remoteIds = {};
|
||||||
|
const checkedRoots = [];
|
||||||
|
const errors = [];
|
||||||
|
const verified = { remote: {}, local: {} };
|
||||||
|
const commits = {};
|
||||||
|
this.archive = new Map();
|
||||||
for (const root of ALLOWED_ROOTS) {
|
for (const root of ALLOWED_ROOTS) {
|
||||||
const tree = await this.repoTree(root);
|
const head = await this.headCommit(root);
|
||||||
for (const item of tree) {
|
if (this.localIntact(root, state) && (!head || head === state.commits[root])) continue;
|
||||||
if (item.type !== 'blob') continue;
|
let map;
|
||||||
const rel = safeRel(`${root}/${item.path}`);
|
try {
|
||||||
if (!rel || !/^[0-9a-f]{40}$/.test(item.id)) continue;
|
map = await this.loadArchive(root);
|
||||||
|
} catch (e) {
|
||||||
|
errors.push(`${root}: ${e.message}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (map.size === 0) {
|
||||||
|
errors.push(`${root}: empty archive`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
checkedRoots.push(root);
|
||||||
|
if (head) commits[root] = head;
|
||||||
|
for (const [key, entry] of map) {
|
||||||
|
const rel = safeRel(key);
|
||||||
|
if (!rel) continue;
|
||||||
|
const data = entry.getData();
|
||||||
|
const id = gitBlobSha(data);
|
||||||
|
remoteIds[rel] = id;
|
||||||
const full = this.target(rel);
|
const full = this.target(rel);
|
||||||
if (fs.existsSync(full) && (state.base[rel] === item.id || gitBlobSha(fs.readFileSync(full)) === item.id)) continue;
|
let ok = false;
|
||||||
changes.push({ path: rel, sha256: item.id, size: 0 });
|
let localHash = null;
|
||||||
|
if (fs.existsSync(full)) {
|
||||||
|
const buf = fs.readFileSync(full);
|
||||||
|
localHash = sha256Buf(buf);
|
||||||
|
ok = gitBlobSha(buf) === id || (state.remote[rel] === id && state.local[rel] === localHash);
|
||||||
|
}
|
||||||
|
if (ok) {
|
||||||
|
verified.remote[rel] = id;
|
||||||
|
verified.local[rel] = localHash;
|
||||||
|
} else {
|
||||||
|
changes.push({ path: rel, sha256: id, size: data.length });
|
||||||
|
this.archive.set(rel, entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { seq: state.seq === null ? 0 : state.seq, changes, removed: [] };
|
if (checkedRoots.length === 0) {
|
||||||
|
if (errors.length) throw new Error(errors.join(' | '));
|
||||||
|
return { seq: state.seq === null ? 0 : state.seq, changes: [], removed: [], verified, commits };
|
||||||
|
}
|
||||||
|
const tracked = new Set([...Object.keys(state.base), ...Object.keys(state.remote)]);
|
||||||
|
const removed = [...tracked].filter((rel) =>
|
||||||
|
checkedRoots.includes(rel.split('/')[0]) &&
|
||||||
|
!remoteIds[rel] &&
|
||||||
|
safeRel(rel) &&
|
||||||
|
fs.existsSync(this.target(rel))
|
||||||
|
);
|
||||||
|
if (changes.length === 0 && removed.length === 0) {
|
||||||
|
for (const k of Object.keys(verified.remote)) {
|
||||||
|
state.remote[k] = verified.remote[k];
|
||||||
|
state.local[k] = verified.local[k];
|
||||||
|
}
|
||||||
|
this.writeState({ remote: state.remote, local: state.local, commits: { ...state.commits, ...commits } });
|
||||||
|
this.archive = null;
|
||||||
|
}
|
||||||
|
return { seq: state.seq === null ? 0 : state.seq, changes, removed, verified, commits };
|
||||||
}
|
}
|
||||||
|
|
||||||
async applyLive(info, replacements, onFile) {
|
async applyLive(info, replacements, onFile) {
|
||||||
const staged = [];
|
const staged = [];
|
||||||
const total = info.changes.length;
|
const total = info.changes.length;
|
||||||
let next = 0;
|
let next = 0;
|
||||||
|
if (!this.archive) this.archive = new Map();
|
||||||
let finished = 0;
|
let finished = 0;
|
||||||
let failure = null;
|
let failure = null;
|
||||||
const worker = async () => {
|
const worker = async () => {
|
||||||
@@ -356,7 +486,8 @@ class FileUpdater {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
await Promise.all(Array.from({ length: Math.min(8, total) }, worker));
|
await Promise.all(Array.from({ length: Math.min(4, total) }, worker));
|
||||||
|
this.archive = null;
|
||||||
if (failure) throw failure;
|
if (failure) throw failure;
|
||||||
|
|
||||||
const done = [];
|
const done = [];
|
||||||
@@ -387,9 +518,23 @@ class FileUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const state = this.readState();
|
const state = this.readState();
|
||||||
for (const s of staged) state.base[s.c.path] = s.c.sha256;
|
for (const s of staged) {
|
||||||
for (const rel of info.removed) delete state.base[rel];
|
state.base[s.c.path] = s.c.sha256;
|
||||||
this.writeState({ seq: info.seq, base: state.base });
|
state.remote[s.c.path] = s.c.sha256;
|
||||||
|
state.local[s.c.path] = sha256Buf(s.data);
|
||||||
|
}
|
||||||
|
for (const rel of info.removed) {
|
||||||
|
delete state.base[rel];
|
||||||
|
delete state.remote[rel];
|
||||||
|
delete state.local[rel];
|
||||||
|
}
|
||||||
|
const v = info.verified || { remote: {}, local: {} };
|
||||||
|
for (const k of Object.keys(v.remote)) {
|
||||||
|
state.remote[k] = v.remote[k];
|
||||||
|
state.local[k] = v.local[k];
|
||||||
|
}
|
||||||
|
state.commits = { ...state.commits, ...(info.commits || {}) };
|
||||||
|
this.writeState({ seq: info.seq, base: state.base, remote: state.remote, local: state.local, commits: state.commits });
|
||||||
}
|
}
|
||||||
|
|
||||||
installedSdk() {
|
installedSdk() {
|
||||||
|
|||||||
9
main.js
9
main.js
@@ -84,6 +84,7 @@ function getReplacementsConfig() {
|
|||||||
|
|
||||||
const updateClient = new UpdateClient(APPDATA_DIR);
|
const updateClient = new UpdateClient(APPDATA_DIR);
|
||||||
const fileUpdater = new FileUpdater(APPDATA_DIR, updateClient);
|
const fileUpdater = new FileUpdater(APPDATA_DIR, updateClient);
|
||||||
|
fileUpdater.onNotice = (message) => sendToTauri('notice', { message });
|
||||||
let lastFilePercent = -1;
|
let lastFilePercent = -1;
|
||||||
fileUpdater.onProgress = (downloaded, total) => {
|
fileUpdater.onProgress = (downloaded, total) => {
|
||||||
const percent = total > 0 ? Math.round((downloaded / total) * 100) : 0;
|
const percent = total > 0 ? Math.round((downloaded / total) * 100) : 0;
|
||||||
@@ -222,7 +223,7 @@ async function start() {
|
|||||||
debugLog(`Update check finished. Found: ${release ? release.version : 'None'}`);
|
debugLog(`Update check finished. Found: ${release ? release.version : 'None'}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugLog(`Update check failed (offline?): ${e.message}`);
|
debugLog(`Update check failed (offline?): ${e.message}`);
|
||||||
sendToTauri('status', { message: `Update server check skipped (${e.message})` });
|
sendToTauri('notice', { message: `Update check skipped: ${e.message}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizeVer = (v) => (v || '').toString().replace(/^v/, '').trim();
|
const normalizeVer = (v) => (v || '').toString().replace(/^v/, '').trim();
|
||||||
@@ -293,10 +294,12 @@ async function start() {
|
|||||||
const [filesCheck, sdkCheck] = await Promise.all([
|
const [filesCheck, sdkCheck] = await Promise.all([
|
||||||
withTimeout(fileUpdater.checkRepos().catch((e) => {
|
withTimeout(fileUpdater.checkRepos().catch((e) => {
|
||||||
debugLog(`Live file check failed: ${e.message}`);
|
debugLog(`Live file check failed: ${e.message}`);
|
||||||
|
sendToTauri('notice', { message: `File check failed: ${e.message}` });
|
||||||
return null;
|
return null;
|
||||||
}), 60000),
|
}), 240000),
|
||||||
withTimeout(fileUpdater.checkSdk().catch((e) => {
|
withTimeout(fileUpdater.checkSdk().catch((e) => {
|
||||||
debugLog(`SDK check failed: ${e.message}`);
|
debugLog(`SDK check failed: ${e.message}`);
|
||||||
|
sendToTauri('notice', { message: `SDK check failed: ${e.message}` });
|
||||||
return null;
|
return null;
|
||||||
}), 12000)
|
}), 12000)
|
||||||
]);
|
]);
|
||||||
@@ -311,7 +314,7 @@ async function start() {
|
|||||||
sendToTauri('status', { message: `Updated ${filesCheck.changes.length} file(s).` });
|
sendToTauri('status', { message: `Updated ${filesCheck.changes.length} file(s).` });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugLog(`File update failed: ${e.message}`);
|
debugLog(`File update failed: ${e.message}`);
|
||||||
sendToTauri('status', { message: `File update failed: ${e.message}` });
|
sendToTauri('notice', { message: `File update failed: ${e.message}` });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,18 @@ fn main() {
|
|||||||
println!("cargo:rerun-if-changed=src/main.rs");
|
println!("cargo:rerun-if-changed=src/main.rs");
|
||||||
println!("cargo:rerun-if-changed=tauri.conf.json");
|
println!("cargo:rerun-if-changed=tauri.conf.json");
|
||||||
println!("cargo:rerun-if-changed=capabilities");
|
println!("cargo:rerun-if-changed=capabilities");
|
||||||
|
for f in [
|
||||||
|
"main.js",
|
||||||
|
"service-manager.js",
|
||||||
|
"file-updater.js",
|
||||||
|
"patcher.js",
|
||||||
|
"crypto-utils.js",
|
||||||
|
"credential-manager.js",
|
||||||
|
"update-client.js",
|
||||||
|
] {
|
||||||
|
println!("cargo:rerun-if-changed=../{}", f);
|
||||||
|
}
|
||||||
|
println!("cargo:rerun-if-changed=../config");
|
||||||
|
|
||||||
if std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "windows" {
|
if std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default() == "windows" {
|
||||||
if let (Ok(manifest_dir), Ok(out_dir)) = (std::env::var("CARGO_MANIFEST_DIR"), std::env::var("OUT_DIR")) {
|
if let (Ok(manifest_dir), Ok(out_dir)) = (std::env::var("CARGO_MANIFEST_DIR"), std::env::var("OUT_DIR")) {
|
||||||
|
|||||||
@@ -85,7 +85,6 @@
|
|||||||
height: 70px;
|
height: 70px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
margin-bottom: 22px;
|
margin-bottom: 22px;
|
||||||
filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.35));
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-user-drag: none;
|
-webkit-user-drag: none;
|
||||||
}
|
}
|
||||||
@@ -101,7 +100,6 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-char {
|
.status-char {
|
||||||
@@ -162,20 +160,16 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
width: 0%;
|
width: 0%;
|
||||||
background: rgba(255, 255, 255, 0.95);
|
background: rgba(255, 255, 255, 0.95);
|
||||||
box-shadow: 0 0 12px rgba(255, 255, 255, 0.5);
|
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
transition: width 0.35s cubic-bezier(0.2, 0.8, 0.2, 1);
|
transition: width 0.35s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#debug-log-box {
|
#debug-log-box {
|
||||||
display: none;
|
display: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 150px;
|
max-height: 150px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background: rgba(255, 59, 48, 0.18);
|
background: rgba(255, 59, 48, 0.18);
|
||||||
border: 1px solid rgba(255, 59, 48, 0.35);
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
font-family: ui-monospace, SFMono-Regular, monospace;
|
font-family: ui-monospace, SFMono-Regular, monospace;
|
||||||
@@ -187,6 +181,20 @@
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notice-line {
|
||||||
|
display: none;
|
||||||
|
max-width: 100%;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(255, 204, 102, 0.14);
|
||||||
|
color: #ffd98a;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -199,6 +207,7 @@
|
|||||||
<div class="loader-card">
|
<div class="loader-card">
|
||||||
<img class="app-icon" src="/white_favicon.png" alt="Vyla" draggable="false">
|
<img class="app-icon" src="/white_favicon.png" alt="Vyla" draggable="false">
|
||||||
<div class="status-text" id="status-message">Starting runtime engine...</div>
|
<div class="status-text" id="status-message">Starting runtime engine...</div>
|
||||||
|
<div class="notice-line" id="notice-line"></div>
|
||||||
<div id="debug-log-box"></div>
|
<div id="debug-log-box"></div>
|
||||||
<div class="file-line" id="file-line"></div>
|
<div class="file-line" id="file-line"></div>
|
||||||
<div class="progress-track" id="progress-track" style="display: none;">
|
<div class="progress-track" id="progress-track" style="display: none;">
|
||||||
@@ -899,6 +908,19 @@ void main() {
|
|||||||
if (data && data.message) setStatus(data.message);
|
if (data && data.message) setStatus(data.message);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
eventApi.listen("notice", (e) => {
|
||||||
|
const data = e.payload || e;
|
||||||
|
const el = document.getElementById("notice-line");
|
||||||
|
if (!el) return;
|
||||||
|
if (data && data.message) {
|
||||||
|
el.textContent = data.message;
|
||||||
|
el.style.display = "block";
|
||||||
|
} else {
|
||||||
|
el.textContent = "";
|
||||||
|
el.style.display = "none";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
eventApi.listen("download-progress", (e) => {
|
eventApi.listen("download-progress", (e) => {
|
||||||
const data = e.payload || e;
|
const data = e.payload || e;
|
||||||
if (data && typeof data.percent === "number") {
|
if (data && typeof data.percent === "number") {
|
||||||
|
|||||||
Reference in New Issue
Block a user