Enhance sprite-picker

This commit is contained in:
2025-03-11 17:40:01 -05:00
parent 2c06ae78b4
commit c148272d38
9 changed files with 7522 additions and 29 deletions

View File

@@ -3,6 +3,7 @@
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<link rel="stylesheet" type="text/css" href="https://alttpr.com/css/app.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>

12
package-lock.json generated
View File

@@ -18,7 +18,8 @@
"localforage": "^1.10.0",
"mustache": "^4.2.0",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
"vue-router": "^4.5.0",
"vue-select": "^4.0.0-beta.6"
},
"devDependencies": {
"@modyfi/vite-plugin-yaml": "^1.1.1",
@@ -3143,6 +3144,15 @@
"vue": "^3.2.0"
}
},
"node_modules/vue-select": {
"version": "4.0.0-beta.6",
"resolved": "https://registry.npmjs.org/vue-select/-/vue-select-4.0.0-beta.6.tgz",
"integrity": "sha512-K+zrNBSpwMPhAxYLTCl56gaMrWZGgayoWCLqe5rWwkB8aUbAUh7u6sXjIR7v4ckp2WKC7zEEUY27g6h1MRsIHw==",
"license": "MIT",
"peerDependencies": {
"vue": "3.x"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@@ -19,7 +19,8 @@
"localforage": "^1.10.0",
"mustache": "^4.2.0",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
"vue-router": "^4.5.0",
"vue-select": "^4.0.0-beta.6"
},
"devDependencies": {
"@modyfi/vite-plugin-yaml": "^1.1.1",

View File

@@ -12,8 +12,8 @@ export default class ZSPR {
}
_parse() {
this.gfxOffset = (this.patch[12] << 24) | (this.patch[11] << 16) | (this.patch[10] << 8) | this.patch[9];
this.palOffset = (this.patch[18] << 24) | (this.patch[17] << 16) | (this.patch[16] << 8) | this.patch[15];
this.gfxOffset = ((this.patch[12] << 24) | (this.patch[11] << 16) | (this.patch[10] << 8) | this.patch[9]) >>> 0;
this.palOffset = ((this.patch[18] << 24) | (this.patch[17] << 16) | (this.patch[16] << 8) | this.patch[15]) >>> 0;
const metadataOffset = 0x1D;
const dec8 = new TextDecoder("utf-8");
const dec16 = new TextDecoder("utf-16");

View File

@@ -26,3 +26,13 @@
--bs-btn-color: #880;
--bs-btn-active-color: #550;
}
.sprite-preview-icon {
display: inline;
float: left;
padding-right: 0.7em;
}
.sprite-preview-name {
padding-left: 0.7em;
}

View File

@@ -33,7 +33,6 @@ export default defineComponent({
rom_checksum: "3322EFFC",
baserom: null,
baserom_error: null,
sprite: null,
patch: null,
error: null,
settings: null,
@@ -54,7 +53,6 @@ export default defineComponent({
}
await this.fetchSeed();
},
computed: {
permalink() {
@@ -121,15 +119,10 @@ export default defineComponent({
}.bind(this);
reader.readAsArrayBuffer(file);
},
spriteUpdate(sprite) {
this.sprite = sprite;
},
async patchRom() {
var rom = bps.apply(this.patch, this.baserom);
if (this.sprite) {
this.sprite.apply(rom);
}
await this.$refs.spritepicker.patch(rom);
this.$refs.heartbeep.patch(rom);
this.$refs.heartcolor.patch(rom);
@@ -187,7 +180,7 @@ export default defineComponent({
</li>
<li class="list-group-item">
<div class="mb-2">
<SpritePicker @spriteUpdate="spriteUpdate" />
<SpritePicker ref="spritepicker" />
</div>
</li>
<li class="list-group-item">

View File

@@ -3,59 +3,160 @@ import { defineComponent } from 'vue';
import ZSPR from "@/ZSPR.js";
import localforage from "localforage";
import officialSprites from "@/data/sprites.json";
import axios from "axios";
import vSelect from "vue-select";
export default defineComponent({
components: {
vSelect,
},
data() {
return {
sprite: null,
uploadedZspr: null,
sprite_error: null,
spriteType: null,
officialSprite: null,
};
},
async mounted() {
const spriteType = await localforage.getItem("selected_sprite_type") ?? "default";
this.officialSprite = await localforage.getItem("selected_official_sprite");
const uploaded = await localforage.getItem("uploaded_sprite");
if (uploaded) {
const dataTransfer = new DataTransfer();
dataTransfer.items.add(uploaded);
document.getElementById("sprite-input").files = dataTransfer.files;
await this.uploadSprite(uploaded, false);
}
this.spriteType = spriteType;
},
watch: {
async spriteType(newValue, oldValue) {
await localforage.setItem("selected_sprite_type", newValue);
},
async officialSprite(newValue, oldValue) {
if (newValue) {
this.spriteType = "official";
} else {
this.spriteType = "default";
}
await localforage.setItem("selected_official_sprite", JSON.parse(JSON.stringify(newValue)));
},
},
computed: {
spriteList() {
return officialSprites;
},
},
methods: {
uploadSprite(file) {
async patch(rom) {
if (this.spriteType == "custom" && this.uploadedZspr != null) {
this.uploadedZspr.apply(rom);
} else if (this.spriteType == "official" && this.officialSprite
&& this.officialSprite.file) {
await axios.get(this.officialSprite.file, { responseType: "arraybuffer" })
.then(response => {
const buffer = new Uint8Array(response.data);
const zspr = new ZSPR(buffer);
if (!zspr.valid) {
throw "Downloaded sprite invalid";
return;
}
zspr.apply(rom);
})
.catch(error => {
console.log(error);
});
}
},
uploadSprite(file, updateType) {
if (!file) {
this.sprite_error = null;
this.sprite = null;
this.uploadedZspr = null;
if (updateType) {
this.spriteType = "default";
}
return;
}
const reader = new FileReader();
reader.onload = function() {
reader.onload = async function() {
const buffer = new Uint8Array(reader.result);
const zspr = new ZSPR(buffer);
if (!zspr.valid) {
this.sprite_error = "Invalid sprite file";
this.sprite = null;
this.uploadedZspr = null;
if (updateType) {
this.spriteType = "default";
}
return;
}
this.sprite_error = null;
this.sprite = zspr;
localforage.setItem("sprite", file);
this.$emit("spriteUpdate", this.sprite);
this.uploadedZspr = zspr;
if (updateType) {
this.spriteType = "custom";
}
await localforage.setItem("uploaded_sprite", file);
}.bind(this);
reader.readAsArrayBuffer(file);
},
}
getSpriteClass(name) {
return "icon-custom-" + name.replace(/\W/g, "");
},
},
});
</script>
<template>
<div>
<label for="sprite-input" class="form-label">
Custom Sprite:
<template v-if="sprite">
{{ sprite.spriteName }} by {{ sprite.spriteAuthor }}
</template>
<div class="input-group mt-2">
<div class="input-group-text">
<input name="spriteType" v-model="spriteType" value="default" class="mt-0" type="radio">
</div>
<label class="input-group-text">
Default Link Sprite
</label>
<input id="sprite-input" class="form-control" type="file" accept=".zspr" @change="uploadSprite($event.target.files[0])" />
</div>
<div class="input-group mt-2">
<div class="input-group-text">
<input name="spriteType" v-model="spriteType" value="official" class="mt-0" type="radio">
</div>
<label class="input-group-text" for="spriteSelector">
Sprite:
</label>
<vSelect label="name" :options="spriteList" class="form-control"
id="spriteSelector" v-model="officialSprite">
<template #option="option">
<div class="icon-custom sprite-preview-icon" :class="getSpriteClass(option.name)"></div>
<span class="sprite-preview-name">{{ option.name }}</span>
</template>
<template #selected-option="option">
<div class="icon-custom sprite-preview-icon" :class="getSpriteClass(option.name)"></div>
<span class="sprite-preview-name">{{ option.name }}</span>
</template>
</vSelect>
</div>
<div class="input-group mt-2">
<div class="input-group-text">
<input name="spriteType" v-model="spriteType" value="custom" class="mt-0" type="radio">
</div>
<input id="sprite-input" class="form-control" type="file" accept=".zspr" @change="uploadSprite($event.target.files[0], true)" />
</div>
<div v-if="sprite_error" class="invalid">
{{ sprite_error }}
</div>
</div>
</template>
<style>
#spriteSelector {
padding: 0;
--vs-border-style: none;
}
</style>

7376
src/data/sprites.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@ import router from "./router";
import "bootstrap/dist/css/bootstrap.min.css";
import "bootstrap-icons/font/bootstrap-icons.css";
import "vue-select/dist/vue-select.css";
import "bootstrap";
import "./assets/main.css";