add a costume editor

This commit is contained in:
2026-01-20 21:33:23 -06:00
parent 23cb91f548
commit 6f59e34761
8 changed files with 2441 additions and 11 deletions

View File

@@ -6,6 +6,7 @@ import * as PIXI from "pixi.js-legacy";
import pako from "pako";
import JSZip from "jszip";
import { io } from "socket.io-client";
import { openCostumeEditor, closeCostumeEditor } from "./costumeEditor.js";
import CustomRenderer from "../functions/render.js";
import { setupThemeButton } from "../functions/theme.js";
@@ -64,6 +65,19 @@ const tabButtons = document.querySelectorAll(".tab-button");
const tabContents = document.querySelectorAll(".tab-content");
const fullscreenButton = document.getElementById("fullscreen-button");
// Add this after the costumes-list setup or in your HTML
const createCostumeButton = document.createElement('button');
createCostumeButton.id = 'create-costume-button';
createCostumeButton.className = 'primary';
createCostumeButton.innerHTML = '<i class="fa-solid fa-paintbrush"></i> Create New Costume (WORK IN PROGRESS)';
createCostumeButton.style.margin = '10px';
// Insert it before the costumes list
const costumesTab = document.getElementById('costumes-tab');
if (costumesTab) {
costumesTab.insertBefore(createCostumeButton, costumesList);
}
export const BASE_WIDTH = 480;
export const BASE_HEIGHT = 360;
const MAX_HTTP_BUFFER = 20 * 1024 * 1024;
@@ -109,7 +123,7 @@ createPenGraphics();
window.projectVariables = {};
export const projectVariables = window.projectVariables;
window.sprites = [];
export const sprites = window.sprites;
export let sprites = window.sprites;
export let activeSprite = null;
window.projectSounds = [];
window.projectCostumes = ["default"];
@@ -394,7 +408,9 @@ function deleteSprite(id, emit = false) {
}
});
sprites = sprites.filter(s => s.id !== sprite.id);
window.sprites = sprites.filter(s => s.id !== sprite.id);
sprites.length = 0;
window.sprites.forEach(s => sprites.push(s));
workspace.clear();
@@ -541,7 +557,6 @@ function renderCostumesList() {
const oldName = costume.name;
costume.name = newName;
// ADD THIS CODE:
const oldIndex = window.projectCostumes.indexOf(oldName);
if (oldIndex !== -1 && !window.projectCostumes.includes(newName)) {
window.projectCostumes[oldIndex] = newName;
@@ -573,11 +588,46 @@ function renderCostumesList() {
});
}
// ADD THIS: Edit button
const editBtn = document.createElement("button");
editBtn.innerHTML = '<i class="fa-solid fa-pen-to-square"></i>';
editBtn.className = "button";
editBtn.draggable = false;
editBtn.title = "Edit costume";
editBtn.onclick = () => {
openCostumeEditor(costume, async (dataURL) => {
if (!dataURL) return;
// Update the existing costume
const newTexture = PIXI.Texture.from(dataURL);
costume.texture = newTexture;
// Update sprite if this is the current costume
if (activeSprite.pixiSprite.texture === costume.texture) {
activeSprite.pixiSprite.texture = newTexture;
}
renderCostumesList();
showNotification({ message: '✓ Costume updated' });
if (currentSocket && currentRoom) {
currentSocket.emit("projectUpdate", {
roomId: currentRoom,
type: "updateCostume",
data: {
spriteId: activeSprite.id,
name: costume.name,
texture: dataURL,
},
});
}
});
};
const deleteBtn = createDeleteButton(() => {
const deleted = activeSprite.costumes[index];
activeSprite.costumes.splice(index, 1);
// ADD THIS CODE to remove from global array if not used elsewhere:
if (deleted) {
const existsElsewhere = sprites.some(s =>
s.id !== activeSprite.id && s.costumes.some(c => c.name === deleted.name)
@@ -593,8 +643,6 @@ function renderCostumesList() {
activeSprite.pixiSprite.texture = PIXI.Texture.EMPTY;
}
renderCostumesList();
// ADD THIS LINE to refresh toolbox:
workspace.updateToolbox(document.getElementById('toolbox'));
if (currentSocket && currentRoom && deleted) {
@@ -611,6 +659,7 @@ function renderCostumesList() {
costumeContainer.appendChild(img);
costumeContainer.appendChild(renameableLabel);
costumeContainer.appendChild(editBtn);
costumeContainer.appendChild(deleteBtn);
costumeContainer.appendChild(sizeLabel);
@@ -1776,6 +1825,51 @@ loadButton.addEventListener("click", () => {
});
loadInput.addEventListener("change", loadProject);
// Create new costume with editor
document.getElementById('create-costume-button')?.addEventListener('click', () => {
if (!activeSprite) {
showNotification({ message: 'Please select a sprite first' });
return;
}
openCostumeEditor(null, async (dataURL) => {
if (!dataURL || !activeSprite) return;
const texture = PIXI.Texture.from(dataURL);
let uniqueName = 'costume';
let counter = 1;
const nameExists = name => activeSprite.costumes.some(c => c.name === name);
while (nameExists(uniqueName)) {
counter++;
uniqueName = `costume_${counter}`;
}
activeSprite.costumes.push({ name: uniqueName, texture });
if (!window.projectCostumes.includes(uniqueName)) {
window.projectCostumes.push(uniqueName);
}
workspace.updateToolbox(document.getElementById('toolbox'));
if (currentSocket && currentRoom) {
currentSocket.emit("projectUpdate", {
roomId: currentRoom,
type: "addCostume",
data: {
spriteId: activeSprite.id,
name: uniqueName,
texture: dataURL,
},
});
}
renderCostumesList();
showNotification({ message: '✓ Costume created successfully' });
});
});
document.getElementById("costume-upload").addEventListener("change", e => {
const file = e.target.files[0];
if (!file || !activeSprite) return;