Initial curated chimera source from a8ae5b1db07fb49926c94585e2718e5130330974
This commit is contained in:
commit
c737a37f71
531 files changed
+119270
No files matched your search
@@ -0,0 +1,317 @@
|
||||
'use strict';
|
||||
|
||||
// ── Dashboard Interactive Mini-Widgets ────────────────────────────────────────
|
||||
// Renders and drives interactive tool tiles pinned to the dashboard.
|
||||
// Called from connection.js renderPinnedTiles() for cards with type:'widget'.
|
||||
|
||||
// Off-screen canvas used by the XAvatar widget for pixel conversion
|
||||
const _dwXaCanvas = document.createElement('canvas');
|
||||
let _dwXaFilename = 'avatar';
|
||||
|
||||
// ── Render ─────────────────────────────────────────────────────────────────────
|
||||
// Returns an innerHTML string for a given pinnable card.
|
||||
function _renderWidgetTile(card) {
|
||||
switch (card.id) {
|
||||
case 'psnotify':
|
||||
return `
|
||||
<div class="dash-widget-hdr">📢 Send Notification</div>
|
||||
<input class="dash-widget-input" data-dw-key="psnotify-msg" placeholder="Message…" maxlength="120">
|
||||
<input class="dash-widget-input" data-dw-key="psnotify-sub" placeholder="Sub-message (optional)…" maxlength="120">
|
||||
<div class="dash-widget-row">
|
||||
<button class="btn btn-sm btn-teal dash-widget-btn" data-dw-action="psnotify-send">📢 Send</button>
|
||||
<span class="dash-widget-status" data-dw-status="psnotify"></span>
|
||||
</div>
|
||||
<div class="dash-widget-row" style="margin-top:4px">
|
||||
<button class="btn btn-sm dash-pinned-tile-open" data-nav-page="psnotify">Open →</button>
|
||||
</div>`;
|
||||
|
||||
case 'payload-mgr':
|
||||
return `
|
||||
<div class="dash-widget-hdr">⇨ Quick Payload Send</div>
|
||||
<select class="dash-widget-select" data-dw-key="payload-file">
|
||||
<option value="">Loading payloads…</option>
|
||||
</select>
|
||||
<div class="dash-widget-row">
|
||||
<span class="dash-widget-port-lbl">Port</span>
|
||||
<input type="number" class="dash-widget-port-input" data-dw-key="payload-port" value="9021" min="1" max="65535">
|
||||
<button class="btn btn-sm btn-teal dash-widget-btn" data-dw-action="payload-send">⇨ Send</button>
|
||||
<span class="dash-widget-status" data-dw-status="payload"></span>
|
||||
</div>
|
||||
<div class="dash-widget-row" style="margin-top:4px">
|
||||
<button class="btn btn-sm dash-pinned-tile-open" data-nav-page="jailbreak">Open →</button>
|
||||
</div>`;
|
||||
|
||||
case 'xavatar-convert':
|
||||
return `
|
||||
<div class="dash-widget-hdr">👤 XAvatar Quick Send</div>
|
||||
<div class="dash-widget-drop" data-dw-drop="xavatar">Drop image or click to browse</div>
|
||||
<div class="dash-widget-row">
|
||||
<button class="btn btn-sm dash-widget-btn" data-dw-action="xavatar-browse">Browse…</button>
|
||||
<button class="btn btn-sm btn-teal dash-widget-btn" data-dw-action="xavatar-send" disabled>↗ Send to PS5</button>
|
||||
<span class="dash-widget-status" data-dw-status="xavatar"></span>
|
||||
</div>
|
||||
<div class="dash-widget-row" style="margin-top:4px">
|
||||
<button class="btn btn-sm dash-pinned-tile-open" data-nav-page="xavatar">Open →</button>
|
||||
</div>`;
|
||||
|
||||
case 'dash-savemgr':
|
||||
return `
|
||||
<div class="dash-widget-hdr">💾 Save Manager</div>
|
||||
<div class="dash-widget-savelist" data-dw-list="savemgr">Click Reload to fetch saves.</div>
|
||||
<div class="dash-widget-row">
|
||||
<button class="btn btn-sm dash-widget-btn" data-dw-action="savemgr-reload">↻ Reload</button>
|
||||
<span class="dash-widget-status" data-dw-status="savemgr"></span>
|
||||
<button class="btn btn-sm dash-pinned-tile-open" data-nav-page="savemgr">Open →</button>
|
||||
</div>`;
|
||||
|
||||
default:
|
||||
// Fallback: plain nav tile for unknown widget IDs
|
||||
return `
|
||||
<div class="dash-pinned-tile-title">${escHtml(card.label)}</div>
|
||||
<div class="dash-pinned-tile-hint">${escHtml(card.hint)}</div>
|
||||
<button class="btn btn-sm dash-pinned-tile-open" data-nav-page="${escHtml(card.page)}">Open →</button>`;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Post-render init ───────────────────────────────────────────────────────────
|
||||
// Called immediately after the tile is appended to the DOM.
|
||||
function _initWidgetTile(card, tileEl) {
|
||||
switch (card.id) {
|
||||
case 'payload-mgr':
|
||||
_dwPopulatePayloadSelect(tileEl);
|
||||
break;
|
||||
case 'xavatar-convert':
|
||||
_dwWireXavatarDrop(tileEl);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Payload select population ──────────────────────────────────────────────────
|
||||
async function _dwPopulatePayloadSelect(tileEl) {
|
||||
const sel = tileEl.querySelector('[data-dw-key="payload-file"]');
|
||||
if (!sel) return;
|
||||
try {
|
||||
const files = await window.pork.payloadListLocal();
|
||||
if (!files.length) {
|
||||
sel.innerHTML = '<option value="">No local payloads</option>';
|
||||
return;
|
||||
}
|
||||
sel.innerHTML = files.map(f =>
|
||||
`<option value="${escHtml(f.local_path)}|${escHtml(f.name)}">${escHtml(f.name)}</option>`
|
||||
).join('');
|
||||
} catch (_) {
|
||||
sel.innerHTML = '<option value="">Error loading payloads</option>';
|
||||
}
|
||||
}
|
||||
|
||||
// ── XAvatar drop-zone wiring ───────────────────────────────────────────────────
|
||||
function _dwWireXavatarDrop(tileEl) {
|
||||
const drop = tileEl.querySelector('[data-dw-drop="xavatar"]');
|
||||
if (!drop) return;
|
||||
|
||||
drop.addEventListener('click', () => {
|
||||
const inp = document.createElement('input');
|
||||
inp.type = 'file';
|
||||
inp.accept = 'image/*';
|
||||
inp.onchange = e => {
|
||||
const file = e.target.files[0];
|
||||
if (file) _dwLoadXavatarFile(file, tileEl);
|
||||
};
|
||||
inp.click();
|
||||
});
|
||||
|
||||
drop.addEventListener('dragover', e => { e.preventDefault(); drop.classList.add('dw-drag-over'); });
|
||||
drop.addEventListener('dragleave', () => drop.classList.remove('dw-drag-over'));
|
||||
drop.addEventListener('drop', e => {
|
||||
e.preventDefault();
|
||||
drop.classList.remove('dw-drag-over');
|
||||
const file = e.dataTransfer?.files[0];
|
||||
if (file && file.type.startsWith('image/')) _dwLoadXavatarFile(file, tileEl);
|
||||
});
|
||||
}
|
||||
|
||||
async function _dwLoadXavatarFile(file, tileEl) {
|
||||
const status = tileEl.querySelector('[data-dw-status="xavatar"]');
|
||||
const sendBtn = tileEl.querySelector('[data-dw-action="xavatar-send"]');
|
||||
const drop = tileEl.querySelector('[data-dw-drop="xavatar"]');
|
||||
if (status) status.textContent = 'Loading…';
|
||||
try {
|
||||
const bmp = await createImageBitmap(file);
|
||||
_dwXaCanvas.width = bmp.width;
|
||||
_dwXaCanvas.height = bmp.height;
|
||||
_dwXaCanvas.getContext('2d').drawImage(bmp, 0, 0);
|
||||
_dwXaFilename = file.name.replace(/\.[^.]+$/, '');
|
||||
if (drop) drop.textContent = file.name;
|
||||
if (sendBtn) sendBtn.disabled = false;
|
||||
if (status) status.textContent = 'Ready';
|
||||
} catch (err) {
|
||||
if (status) status.textContent = 'Load failed';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Save Manager ───────────────────────────────────────────────────────────────
|
||||
async function _dwSavemgrLoad(btn) {
|
||||
const tile = btn.closest('.dash-pinned-tile');
|
||||
const listEl = tile?.querySelector('[data-dw-list="savemgr"]');
|
||||
const status = tile?.querySelector('[data-dw-status="savemgr"]');
|
||||
if (!listEl) return;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Loading…';
|
||||
if (status) status.textContent = '';
|
||||
try {
|
||||
const r = await window.pork.savemgrRequest('/api/saves');
|
||||
const saves = typeof r === 'string' ? JSON.parse(r) : r;
|
||||
if (!saves || !saves.length) {
|
||||
listEl.textContent = 'No saves found.';
|
||||
if (status) status.textContent = '0 saves';
|
||||
} else {
|
||||
listEl.innerHTML = saves.slice(0, 8).map((s, i) =>
|
||||
`<div class="dash-dw-save-row">
|
||||
<span class="dash-dw-save-name" title="${escHtml(s.titleId || '')}">${escHtml(s.name || s.title || s.titleId || `Save ${i}`)}</span>
|
||||
<button class="btn btn-xs dash-widget-btn" data-dw-action="savemgr-dl" data-sm-idx="${i}">⬇ ZIP</button>
|
||||
</div>`
|
||||
).join('');
|
||||
if (status) status.textContent = `${saves.length} save${saves.length !== 1 ? 's' : ''}`;
|
||||
}
|
||||
} catch (err) {
|
||||
listEl.textContent = 'Error: ' + err.message;
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = '↻ Reload';
|
||||
}
|
||||
}
|
||||
|
||||
async function _dwSavemgrDl(btn) {
|
||||
const idx = btn.dataset.smIdx;
|
||||
const tile = btn.closest('.dash-pinned-tile');
|
||||
const status = tile?.querySelector('[data-dw-status="savemgr"]');
|
||||
btn.disabled = true;
|
||||
btn.textContent = '…';
|
||||
if (status) status.textContent = 'Mounting…';
|
||||
try {
|
||||
await window.pork.savemgrRequest('/api/mount?idx=' + idx);
|
||||
if (status) status.textContent = 'Downloading…';
|
||||
await window.pork.savemgrDownload('/api/download', `save_${idx}.zip`);
|
||||
if (status) status.textContent = '✓ Saved';
|
||||
setTimeout(() => { if (status) status.textContent = ''; }, 4000);
|
||||
} catch (err) {
|
||||
if (status) status.textContent = 'Error: ' + err.message;
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = '⬇ ZIP';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Event delegation ───────────────────────────────────────────────────────────
|
||||
document.addEventListener('click', async e => {
|
||||
// "Open →" buttons inside widget tiles — navigate to the full page
|
||||
const navBtn = e.target.closest('.dash-pinned-tile-open[data-nav-page]');
|
||||
if (navBtn && navBtn.closest('.dash-pinned-tile--widget')) {
|
||||
e.stopPropagation();
|
||||
navigate(navBtn.dataset.navPage);
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = e.target.closest('.dash-widget-btn[data-dw-action]');
|
||||
if (!btn) return;
|
||||
e.stopPropagation();
|
||||
|
||||
switch (btn.dataset.dwAction) {
|
||||
case 'psnotify-send': await _dwPsnotifySend(btn); break;
|
||||
case 'payload-send': await _dwPayloadSend(btn); break;
|
||||
case 'xavatar-browse': _dwXavatarBrowseClick(btn); break;
|
||||
case 'xavatar-send': await _dwXavatarSend(btn); break;
|
||||
case 'savemgr-reload': await _dwSavemgrLoad(btn); break;
|
||||
case 'savemgr-dl': await _dwSavemgrDl(btn); break;
|
||||
}
|
||||
});
|
||||
|
||||
// ── PSNotify action ────────────────────────────────────────────────────────────
|
||||
async function _dwPsnotifySend(btn) {
|
||||
const tile = btn.closest('.dash-pinned-tile');
|
||||
const msg = tile?.querySelector('[data-dw-key="psnotify-msg"]')?.value?.trim() || '';
|
||||
const sub = tile?.querySelector('[data-dw-key="psnotify-sub"]')?.value?.trim() || '';
|
||||
const status = tile?.querySelector('[data-dw-status="psnotify"]');
|
||||
if (!msg) { if (status) { status.textContent = 'Message required'; status.style.color = 'var(--red)'; } return; }
|
||||
btn.disabled = true;
|
||||
if (status) { status.textContent = 'Sending…'; status.style.color = ''; }
|
||||
try {
|
||||
await window.pork.psnotifySend(msg, sub);
|
||||
if (status) { status.textContent = '✓ Sent'; status.style.color = 'var(--green)'; }
|
||||
if (tile) {
|
||||
const msgEl = tile.querySelector('[data-dw-key="psnotify-msg"]');
|
||||
const subEl = tile.querySelector('[data-dw-key="psnotify-sub"]');
|
||||
if (msgEl) msgEl.value = '';
|
||||
if (subEl) subEl.value = '';
|
||||
}
|
||||
setTimeout(() => { if (status) { status.textContent = ''; status.style.color = ''; } }, 3000);
|
||||
} catch (err) {
|
||||
if (status) { status.textContent = 'Error: ' + err.message; status.style.color = 'var(--red)'; }
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Payload action ─────────────────────────────────────────────────────────────
|
||||
async function _dwPayloadSend(btn) {
|
||||
const tile = btn.closest('.dash-pinned-tile');
|
||||
const sel = tile?.querySelector('[data-dw-key="payload-file"]');
|
||||
const portEl = tile?.querySelector('[data-dw-key="payload-port"]');
|
||||
const status = tile?.querySelector('[data-dw-status="payload"]');
|
||||
const val = sel?.value || '';
|
||||
if (!val) { if (status) { status.textContent = 'Pick a payload'; status.style.color = 'var(--red)'; } return; }
|
||||
const [localPath, filename] = val.split('|');
|
||||
const port = parseInt(portEl?.value || '9021', 10);
|
||||
btn.disabled = true;
|
||||
if (status) { status.textContent = 'Sending…'; status.style.color = ''; }
|
||||
try {
|
||||
await window.pork.payloadTcpSend({ localPath, port, filename });
|
||||
if (status) { status.textContent = `✓ ${filename} sent`; status.style.color = 'var(--green)'; }
|
||||
setTimeout(() => { if (status) { status.textContent = ''; status.style.color = ''; } }, 4000);
|
||||
} catch (err) {
|
||||
if (status) { status.textContent = 'Error: ' + err.message; status.style.color = 'var(--red)'; }
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ── XAvatar actions ────────────────────────────────────────────────────────────
|
||||
function _dwXavatarBrowseClick(btn) {
|
||||
const tile = btn.closest('.dash-pinned-tile');
|
||||
const inp = document.createElement('input');
|
||||
inp.type = 'file';
|
||||
inp.accept = 'image/*';
|
||||
inp.onchange = e => {
|
||||
const file = e.target.files[0];
|
||||
if (file) _dwLoadXavatarFile(file, tile);
|
||||
};
|
||||
inp.click();
|
||||
}
|
||||
|
||||
async function _dwXavatarSend(btn) {
|
||||
const tile = btn.closest('.dash-pinned-tile');
|
||||
const status = tile?.querySelector('[data-dw-status="xavatar"]');
|
||||
if (_dwXaCanvas.width === 0) return;
|
||||
btn.disabled = true;
|
||||
if (status) { status.textContent = 'Converting…'; status.style.color = ''; }
|
||||
try {
|
||||
const ctx = _dwXaCanvas.getContext('2d');
|
||||
const imgData = ctx.getImageData(0, 0, _dwXaCanvas.width, _dwXaCanvas.height);
|
||||
const rgba = new Uint8Array(imgData.data.buffer);
|
||||
const res = await window.pork.xavatarConvertCanvas(rgba, _dwXaCanvas.width, _dwXaCanvas.height, _dwXaFilename);
|
||||
if (!res || !res.ok) throw new Error((res && res.error) || 'Conversion failed');
|
||||
if (status) status.textContent = 'Uploading…';
|
||||
const pad = n => String(n).padStart(5, '0');
|
||||
const rnd = pad(Math.floor(Math.random() * 100000));
|
||||
const name = `chimera${rnd}.xavatar`;
|
||||
await window.pork.xavatarUploadToPs5(res.buffer?.buffer || res.buffer, name);
|
||||
if (status) { status.textContent = `✓ Queued: ${name}`; status.style.color = 'var(--green)'; }
|
||||
setTimeout(() => { if (status) { status.textContent = ''; status.style.color = ''; } }, 5000);
|
||||
} catch (err) {
|
||||
if (status) { status.textContent = 'Error: ' + err.message; status.style.color = 'var(--red)'; }
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user