feat: mii editor

This commit is contained in:
limes
2026-08-14 16:55:54 +02:00
parent 1113874dbe
commit b6c8f1bcac
7 changed files with 64 additions and 46 deletions

View File

@@ -67,7 +67,7 @@ There are no fully required configuration variables, the app can runs minimally
The website is currently in a refactor, here is what is still left on the frontend:
- [x] Styling for progress page
- [x] Styling for progress on main page
- [ ] Account: Mii editor
- [X] Account: Mii editor
- [ ] Account: View account
- [ ] Account: Delete account
- [ ] Account: Link discord account

8
package-lock.json generated
View File

@@ -19,7 +19,7 @@
"@pinia/nuxt": "^1.0.1",
"@pretendonetwork/error-codes": "^1.2.2",
"@pretendonetwork/grpc": "^2.5.4",
"@pretendonetwork/mii-js": "^1.0.9",
"@pretendonetwork/mii-js": "^1.0.11",
"@vueuse/core": "^14.4.0",
"base64-arraybuffer": "^1.0.2",
"better-sqlite3": "^12.11.1",
@@ -5367,9 +5367,9 @@
}
},
"node_modules/@pretendonetwork/mii-js": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@pretendonetwork/mii-js/-/mii-js-1.0.10.tgz",
"integrity": "sha512-HNpZV4cnipwt9FsV7H3sLfxN9ob38AYE3g9s3X1zajAeKOkB6tnveSjAmDKnUgQAvqJUdIwowzJmQ0u6bcEVMA==",
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@pretendonetwork/mii-js/-/mii-js-1.0.11.tgz",
"integrity": "sha512-X0GlNkbj5ePgUPQ1dM4B8Sxw4E2u7gXX+0GLvdAIcC9fK8qD9LNe9iu9ymrm2kXAx1YeeX5lRw6+dgp8FLtTcA==",
"license": "AGPL-3.0-only",
"dependencies": {
"bit-buffer": "^0.2.5",

View File

@@ -24,7 +24,7 @@
"@pinia/nuxt": "^1.0.1",
"@pretendonetwork/error-codes": "^1.2.2",
"@pretendonetwork/grpc": "^2.5.4",
"@pretendonetwork/mii-js": "^1.0.9",
"@pretendonetwork/mii-js": "^1.0.11",
"@vueuse/core": "^14.4.0",
"base64-arraybuffer": "^1.0.2",
"better-sqlite3": "^12.11.1",

View File

@@ -22,7 +22,8 @@ export default defineEventHandler(async (event): Promise<GetApiAuthMe> => {
mii: data.mii
? {
imageUrl: `${config.public.cdnBaseUrl}/mii/${data.pid}/normal_face.png`,
name: data.mii.name
name: data.mii.name,
data: data.mii.data
}
: null
};

View File

@@ -14,6 +14,7 @@ export type GetApiAuthMe = {
mii: {
imageUrl: string;
name: string;
data: string;
} | null;
};

View File

@@ -65,7 +65,7 @@ async function updateMii() {
await apiFetch('/api/account/update', {
method: 'PATCH',
body: {
mii: { name: 'steve', primary: 'Y', data: 'AwAAQMjn20WghCBw2qjhdwOzuI0n2QAAAFhzAHQAZQB2AGUAAAAAAAAAAAAAACoWCgBLBQFoRBgm\r\nNEYUjRIJaA0AACkAUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAALaT' }
mii: { name: 'steve', primary: 'Y', data: 'AwAAQOlVognnx0GC2qjhdwOzuI0n2QAAAGBzAHQAZQB2AGUAAAAAAAAAAAAAAEBAAAAhAQJoRBgmNEYUgRIXaA0AACkAUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAANeC' }
} satisfies ApiAccountUpdateRequest
});
await refresh();

View File

@@ -1,20 +1,22 @@
<script setup lang="ts">
// eslint-disable-next-line import/no-unresolved -- i genuinely don't know why this errors
import Mii from '@pretendonetwork/mii-js';
import { decode } from 'base64-arraybuffer';
// we don't want the navbar or the footer
import type { Subtab, Tab } from '@/utils/miieditor';
// eslint-disable-next-line import/no-unresolved -- i genuinely don't know why this errors either
import BirthdaySetter from '@/components/BirthdaySetter/BirthdaySetter.vue';
// eslint-disable-next-line import/no-unresolved -- i genuinely don't know why this errors either either
import { miiEditorJSON } from '@/utils/miieditor';
import type { ApiAccountUpdateRequest } from '~~/shared/api-types';
import type { Subtab, Tab } from '@/utils/miieditor';
const { t } = useI18n();
definePageMeta({
layout: 'slot-only'
layout: 'slot-only',
needsAuth: true
});
const { data: profile } = await useApiFetch('/api/auth/me');
console.log('miidata', profile.value?.mii?.data);
useHead({
title: 'Mii Editor | Pretendo Network'
});
@@ -22,7 +24,7 @@ useHead({
// TODO: literally all the backend stuff
const fallbackMiiData =
'AwAAQOlVognnx0GC2qjhdwOzuI0n2QAAAGBzAHQAZQB2AGUAAAAAAAAAAAAAAEBAAAAhAQJoRBgmNEYUgRIXaA0AACkAUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAANeC';
const miiDataString: string = fallbackMiiData;
const miiDataString: string = profile.value?.mii?.data || fallbackMiiData;
// keeps track of tabs and pagination
const activeTab = ref<string>('faceType');
@@ -37,19 +39,8 @@ const loadingCanvas = ref<boolean>(false);
// these are used in the save modal
const saving = ref<boolean>(false);
let oldMii: Mii | null = null;
const oldMiiNeutralUrl = computed(() => {
return oldMii?.studioUrl({
width: 512,
bgColor: '13173300'
});
});
const oldMiiSorrowUrl = computed(() => {
return oldMii?.studioUrl({
width: 512,
bgColor: '13173300',
expression: 'sorrow'
});
});
let oldMiiNeutralUrl = '';
let oldMiiSorrowUrl = '';
const newMiiNeutralUrl = computed(() => {
return mii.value.studioUrl({
width: 512,
@@ -78,6 +69,16 @@ function initializeMiiData(encodedUserMiiData: string) {
mii.value = tempMii;
// we also initialize the old mii variable, which will be used to render the miis in the save dialogue
oldMii = tempMii;
oldMiiNeutralUrl = oldMii?.studioUrl({
width: 512,
bgColor: '13173300'
});
oldMiiSorrowUrl = oldMii?.studioUrl({
width: 512,
bgColor: '13173300',
expression: 'sorrow'
});
} catch (err) {
console.error('failed to decode mii data', err);
console.groupEnd();
@@ -280,14 +281,26 @@ function getSubPageFromValue(value: string, newSubTab: string, newTab: string) {
async function handleSave() {
saving.value = true;
console.log('TODO: logic', mii.value);
try {
await apiFetch('/api/account/update', {
method: 'PATCH',
body: {
mii: { name: mii.value.miiName, primary: 'Y', data: mii.value.encode().toString('base64') }
} satisfies ApiAccountUpdateRequest
});
setTimeout(() => {
setTimeout(() => {
// TODO - Make this prettier
alert(t('miiEditor.miiSaved'));
alert(t('miiEditor.miiSaved'));
navigateTo('/account');
}, 5000);
navigateTo('/account');
}, 3000);
// await refresh();
} catch (error: unknown) {
const err = getApiError(error);
alert(`${err.code}: ${err.message}`);
}
}
</script>
@@ -423,7 +436,11 @@ async function handleSave() {
</button>
</div>
<div
v-for="subtab in tab.subTabs?.filter(s => s.name === activeSubTab && s.type !== 'slider')?.filter(s => (s?.values?.length || 0) > 1)"
v-for="subtab in tab.subTabs
?.filter(
(s) => s.name === activeSubTab && s.type !== 'slider',
)
?.filter((s) => (s?.values?.length || 0) > 1)"
:key="subtab.name"
class="pagination"
>
@@ -862,7 +879,6 @@ div.params-wrapper {
max-width: 36rem;
height: auto;
aspect-ratio: 39/40;
}
div.tabs,
@@ -897,7 +913,7 @@ div.subtabs .subtabbtn {
border-radius: 0.4rem;
background: none;
padding: 0.4rem;
color: var(--text-shade-1)
color: var(--text-shade-1);
}
div.subtabs .subtabbtn {
@@ -1309,7 +1325,7 @@ input[type="range"].invert {
.params-resizer {
max-width: 28rem;
height: auto;
gap: .5rem
gap: 0.5rem;
}
form.params .tab {
@@ -1330,7 +1346,7 @@ input[type="range"].invert {
}
.subtab.info {
grid-template-rows: repeat(3, auto) 1fr;
grid-template-rows: repeat(3, auto) 1fr;
}
.subtab.info .icons input[type="checkbox"] {
@@ -1373,12 +1389,12 @@ input[type="range"].invert {
box-shadow: inset 0 0 0 0.25rem var(--accent-shade-1);
}
fieldset input[type="radio"]:checked + label {
background: var(--bg-shade-4);
box-shadow:
inset 0 0 0 0.2rem var(--accent-shade-1),
inset 0 0 0 0.3rem var(--bg-shade-1);
}
fieldset input[type="radio"]:checked + label {
background: var(--bg-shade-4);
box-shadow:
inset 0 0 0 0.2rem var(--accent-shade-1),
inset 0 0 0 0.3rem var(--bg-shade-1);
}
fieldset .subpage.has-sliders {
gap: 1rem;
@@ -1391,7 +1407,7 @@ input[type="range"].invert {
@media screen and (max-width: 400px) {
.params-wrapper {
padding: .0rem 0.5rem;
padding: 0rem 0.5rem;
}
.mii-comparison .icon {
@@ -1400,7 +1416,7 @@ input[type="range"].invert {
div.tabs,
div.subtabs {
gap: .2rem
gap: 0.2rem;
}
div.tabs .tabbtn,