finished Skin UGC
This commit is contained in:
@@ -3484,3 +3484,73 @@ a.deletelink {
|
||||
padding: 0.1rem 0.5rem; font-size: 0.68rem; font-weight: 600; text-transform: uppercase;
|
||||
}
|
||||
.plugins-size { color: var(--md-sys-color-on-surface-variant, #6b7280); font-size: 0.78rem; }
|
||||
|
||||
/* Skins tab */
|
||||
.skin-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
.skin-card {
|
||||
background: var(--md-sys-color-surface-variant, #45475a);
|
||||
border: 1px solid var(--md-sys-color-outline-variant, #3a3f4d);
|
||||
border-radius: 10px;
|
||||
padding: 0.75rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
.skin-preview {
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
object-fit: contain;
|
||||
image-rendering: pixelated;
|
||||
background: repeating-conic-gradient(#2a2e3a 0 25%, #232732 0 50%) 0 0 / 24px 24px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.skin-card-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.4rem;
|
||||
font-size: 0.82rem;
|
||||
color: var(--md-sys-color-on-surface, #cdd6f4);
|
||||
}
|
||||
.skin-card-head strong {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.skin-size-badge {
|
||||
border-radius: 999px;
|
||||
background: #313244;
|
||||
color: #a6adc8;
|
||||
padding: 0.1rem 0.5rem;
|
||||
font-size: 0.68rem;
|
||||
font-weight: 600;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.skin-card-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-top: auto;
|
||||
}
|
||||
.skin-model-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.4rem;
|
||||
font-size: 0.72rem;
|
||||
color: var(--md-sys-color-on-surface-variant, #a6adc8);
|
||||
}
|
||||
.skin-model-select {
|
||||
background: var(--md-sys-color-surface, #1e1e2e);
|
||||
color: var(--md-sys-color-on-surface, #cdd6f4);
|
||||
border: 1px solid var(--md-sys-color-outline-variant, #45475a);
|
||||
border-radius: 6px;
|
||||
padding: 0.15rem 0.3rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.skin-render-btn { width: 100%; }
|
||||
|
||||
|
||||
@@ -173,15 +173,9 @@
|
||||
return baseTextures.concat(featureTextures);
|
||||
}
|
||||
|
||||
async function open(btn) {
|
||||
const member = btn.dataset.member;
|
||||
const name = btn.dataset.name || member;
|
||||
const modelFormat = btn.dataset.modelFormat || '.geo.json';
|
||||
const slug = btn.dataset.projectSlug || '';
|
||||
const opts = { baseUrl: btn.dataset.baseUrl, vanillaBaseUrl: btn.dataset.vanillaBaseUrl, defaultTexture: btn.dataset.defaultTexture || null };
|
||||
if (!member || !opts.baseUrl || busy) return;
|
||||
busy = true;
|
||||
|
||||
// Create (or reset) the full-screen Blockbench overlay + fresh iframe and
|
||||
// wait for the app to be ready. Returns false on failure.
|
||||
async function prepareOverlay(name) {
|
||||
const overlayEl = ensureOverlay();
|
||||
overlayEl.hidden = false;
|
||||
document.querySelector('#bb-overlay-title').textContent = name;
|
||||
@@ -209,12 +203,20 @@
|
||||
};
|
||||
window.addEventListener('message', onMsg);
|
||||
});
|
||||
if (!ready) setStatus('Blockbench failed to start.');
|
||||
return ready;
|
||||
}
|
||||
|
||||
if (!ready) {
|
||||
setStatus('Blockbench failed to start.');
|
||||
busy = false;
|
||||
return;
|
||||
}
|
||||
async function open(btn) {
|
||||
const member = btn.dataset.member;
|
||||
const name = btn.dataset.name || member;
|
||||
const modelFormat = btn.dataset.modelFormat || '.geo.json';
|
||||
const slug = btn.dataset.projectSlug || '';
|
||||
const opts = { baseUrl: btn.dataset.baseUrl, vanillaBaseUrl: btn.dataset.vanillaBaseUrl, defaultTexture: btn.dataset.defaultTexture || null };
|
||||
if (!member || !opts.baseUrl || busy) return;
|
||||
busy = true;
|
||||
|
||||
if (!(await prepareOverlay(name))) { busy = false; return; }
|
||||
|
||||
setStatus('Loading model…');
|
||||
try {
|
||||
@@ -254,6 +256,34 @@
|
||||
busy = false;
|
||||
}
|
||||
|
||||
// Load a Minecraft skin into Blockbench's built-in "Minecraft Skin" project.
|
||||
async function openSkin(btn) {
|
||||
const member = btn.dataset.member;
|
||||
const name = btn.dataset.name || member;
|
||||
const opts = { baseUrl: btn.dataset.baseUrl };
|
||||
if (!member || !opts.baseUrl || busy) return;
|
||||
busy = true;
|
||||
|
||||
if (!(await prepareOverlay(name))) { busy = false; return; }
|
||||
|
||||
setStatus('Loading skin…');
|
||||
try {
|
||||
const select = btn.closest('.skin-card')?.querySelector('.skin-model-select');
|
||||
const model = select ? select.value : 'steve';
|
||||
const img = await loadImage(assetUrl(opts.baseUrl, member));
|
||||
iframe.contentWindow.postMessage({
|
||||
type: 'packs-open-skin',
|
||||
name: name,
|
||||
model: model,
|
||||
dataUrl: imageDataUrl(img),
|
||||
}, window.location.origin);
|
||||
setStatus('Applying…');
|
||||
} catch (e) {
|
||||
setStatus('Could not load skin: ' + e.message);
|
||||
}
|
||||
busy = false;
|
||||
}
|
||||
|
||||
window.addEventListener('message', (e) => {
|
||||
if (e.origin !== window.location.origin) return;
|
||||
if (!e.data) return;
|
||||
@@ -266,5 +296,5 @@
|
||||
}
|
||||
});
|
||||
|
||||
window.PacksPreview = { open: open, close: close };
|
||||
window.PacksPreview = { open: open, openSkin: openSkin, close: close };
|
||||
})();
|
||||
|
||||
@@ -124,8 +124,45 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Open a Minecraft Skin project (Steve/Alex player model) with the skin.
|
||||
async function openSkin(msg) {
|
||||
await waitFor(() => typeof Formats !== 'undefined' && !!Formats.skin, 15000);
|
||||
const model = msg.model === 'alex' ? 'alex' : 'steve';
|
||||
const dialog = Formats.skin.setup_dialog;
|
||||
dialog.show();
|
||||
await dialog.setFormValues({
|
||||
model: model,
|
||||
texture_source: 'upload_texture',
|
||||
texture_file: { content: msg.dataUrl, name: 'skin.png', path: 'skin.png' },
|
||||
layer_template: true,
|
||||
});
|
||||
dialog.confirm();
|
||||
// Reveal every cube (incl. the outer layer) and land in Pose mode.
|
||||
await new Promise((r) => setTimeout(r, 700));
|
||||
try {
|
||||
(typeof Cube !== 'undefined' && Cube.all || []).forEach((c) => { c.visibility = true; });
|
||||
if (typeof Canvas !== 'undefined' && Canvas.updateAll) Canvas.updateAll();
|
||||
const pose = Modes && Modes.options && Modes.options.pose;
|
||||
if (pose && typeof pose.select === 'function') pose.select();
|
||||
} catch (e) {
|
||||
console.error('packs skin post-load', e);
|
||||
}
|
||||
notify('model-open', { name: msg.name || 'skin' });
|
||||
}
|
||||
|
||||
window.addEventListener('message', (e) => {
|
||||
if (!e.data || e.data.type !== 'packs-open-model') return;
|
||||
if (!e.data) return;
|
||||
if (e.data.type === 'packs-open-skin') {
|
||||
openSkin(e.data).then(
|
||||
() => {},
|
||||
(err) => {
|
||||
console.error('packs-open-skin failed', err);
|
||||
notify('model-error', { name: e.data.name || '', error: String(err) });
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (e.data.type !== 'packs-open-model') return;
|
||||
openModel(e.data).then(
|
||||
() => {},
|
||||
(err) => {
|
||||
|
||||
Reference in New Issue
Block a user