wip
|
|
@ -3570,6 +3570,12 @@ _miWorld:
|
|||
antialiasing: "アンチエイリアス"
|
||||
avatar: "アバター"
|
||||
|
||||
_avatars:
|
||||
_default:
|
||||
body: "ボディ"
|
||||
eyes: "目"
|
||||
mouth: "口"
|
||||
|
||||
_miRoom:
|
||||
snapToGrid: "グリッドにスナップ"
|
||||
gridScale: "グリッドサイズ"
|
||||
|
|
|
|||
|
|
@ -78,5 +78,16 @@ export class WorldAvatarEntityService {
|
|||
.then(users => new Map(users.map(u => [u.id, u])));
|
||||
return Promise.all(avatars.map(avatar => this.packLite(avatar, me, { packedUser: _userMap.get(avatar.userId) })));
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async packDetailedMany(
|
||||
avatars: MiWorldAvatar[],
|
||||
me?: { id: MiUser['id'] } | null | undefined,
|
||||
) {
|
||||
const _users = avatars.map(({ user, userId }) => user ?? userId);
|
||||
const _userMap = await this.userEntityService.packMany(_users, me)
|
||||
.then(users => new Map(users.map(u => [u.id, u])));
|
||||
return Promise.all(avatars.map(avatar => this.packDetailed(avatar, me, { packedUser: _userMap.get(avatar.userId) })));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export const meta = {
|
|||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'WorldAvatarLite',
|
||||
ref: 'WorldAvatarDetailed',
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ export const paramDef = {
|
|||
sinceDate: { type: 'integer' },
|
||||
untilDate: { type: 'integer' },
|
||||
},
|
||||
required: ['userId'],
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -55,8 +55,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
|
||||
const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
|
||||
|
||||
const avatars = await this.worldAvatarService.getMyAvatarsWithPagination(ps.userId, ps.limit, sinceId, untilId);
|
||||
return this.worldAvatarEntityService.packLiteMany(avatars, me);
|
||||
const avatars = await this.worldAvatarService.getMyAvatarsWithPagination(me.id, ps.limit, sinceId, untilId);
|
||||
return this.worldAvatarEntityService.packDetailedMany(avatars, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,27 @@ export type PlayerState = {
|
|||
sit?: string; // id
|
||||
};
|
||||
|
||||
const DEFAULT_FACE_PARTS_EYES = {
|
||||
'_none_': null,
|
||||
'a': '/client-assets/world/avatars/eyes-a.png',
|
||||
'b': '/client-assets/world/avatars/eyes-b.png',
|
||||
'c': '/client-assets/world/avatars/eyes-c.png',
|
||||
'd': '/client-assets/world/avatars/eyes-d.png',
|
||||
'e': '/client-assets/world/avatars/eyes-e.png',
|
||||
'f': '/client-assets/world/avatars/eyes-f.png',
|
||||
'g': '/client-assets/world/avatars/eyes-g.png',
|
||||
};
|
||||
|
||||
const DEFAULT_FACE_PARTS_MOUTH = {
|
||||
'_none_': null,
|
||||
'a': '/client-assets/world/avatars/mouth-a.png',
|
||||
'b': '/client-assets/world/avatars/mouth-b.png',
|
||||
'c': '/client-assets/world/avatars/mouth-c.png',
|
||||
'd': '/client-assets/world/avatars/mouth-d.png',
|
||||
'e': '/client-assets/world/avatars/mouth-e.png',
|
||||
'f': '/client-assets/world/avatars/mouth-f.png',
|
||||
};
|
||||
|
||||
export class PlayerContainer {
|
||||
public id: string;
|
||||
private profile: PlayerProfile;
|
||||
|
|
@ -67,9 +88,27 @@ export class PlayerContainer {
|
|||
|
||||
const avatarTex = new BABYLON.Texture(this.profile.avatarUrl, this.scene, false, false);
|
||||
|
||||
let eyesTex: BABYLON.Texture | null = null;
|
||||
if (this.profile.worldAvatar.eyes.type in DEFAULT_FACE_PARTS_EYES) {
|
||||
const eyesTexPath = DEFAULT_FACE_PARTS_EYES[this.profile.worldAvatar.eyes.type];
|
||||
if (eyesTexPath) {
|
||||
eyesTex = new BABYLON.Texture(eyesTexPath, this.scene, false, false);
|
||||
eyesTex.hasAlpha = true;
|
||||
}
|
||||
}
|
||||
|
||||
let mouthTex: BABYLON.Texture | null = null;
|
||||
if (this.profile.worldAvatar.mouth.type in DEFAULT_FACE_PARTS_MOUTH) {
|
||||
const mouthTexPath = DEFAULT_FACE_PARTS_MOUTH[this.profile.worldAvatar.mouth.type];
|
||||
if (mouthTexPath) {
|
||||
mouthTex = new BABYLON.Texture(mouthTexPath, this.scene, false, false);
|
||||
mouthTex.hasAlpha = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const mesh of this.modelRoot.getChildMeshes()) {
|
||||
if (mesh.name.includes('__AVATAR__')) {
|
||||
const mat = new BABYLON.PBRMaterial(`${mesh.name}-mat`, this.scene);
|
||||
const mat = new BABYLON.PBRMaterial('', this.scene);
|
||||
mat.albedoColor = new BABYLON.Color3(0.5, 0.5, 0.5);
|
||||
mat.albedoTexture = avatarTex;
|
||||
mat.emissiveColor = new BABYLON.Color3(0.5, 0.5, 0.5);
|
||||
|
|
@ -82,6 +121,26 @@ export class PlayerContainer {
|
|||
if (mesh.name.includes('__BODY__')) {
|
||||
mesh.material.albedoColor = new BABYLON.Color3(this.profile.worldAvatar.body.color[0], this.profile.worldAvatar.body.color[1], this.profile.worldAvatar.body.color[2]);
|
||||
}
|
||||
if (mesh.name.includes('__EYES__')) {
|
||||
const mat = new BABYLON.PBRMaterial('', this.scene);
|
||||
mat.albedoColor = new BABYLON.Color3(this.profile.worldAvatar.eyes.color[0], this.profile.worldAvatar.eyes.color[1], this.profile.worldAvatar.eyes.color[2]);
|
||||
mat.albedoTexture = eyesTex;
|
||||
mat.roughness = 1;
|
||||
mat.metallic = 0;
|
||||
mesh.material = mat;
|
||||
}
|
||||
if (mesh.name.includes('__MOUTH__')) {
|
||||
if (mouthTex != null) {
|
||||
const mat = new BABYLON.PBRMaterial('', this.scene);
|
||||
mat.albedoColor = new BABYLON.Color3(this.profile.worldAvatar.mouth.color[0], this.profile.worldAvatar.mouth.color[1], this.profile.worldAvatar.mouth.color[2]);
|
||||
mat.albedoTexture = mouthTex;
|
||||
mat.roughness = 1;
|
||||
mat.metallic = 0;
|
||||
mesh.material = mat;
|
||||
} else {
|
||||
mesh.isVisible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.registerMeshes(this.modelRoot.getChildMeshes());
|
||||
|
|
@ -94,9 +153,6 @@ export class PlayerContainer {
|
|||
{ frame: 90, value: cm(2) },
|
||||
{ frame: 120, value: cm(0) },
|
||||
]);
|
||||
//const easing = new BABYLON.CubicEase();
|
||||
//easing.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);
|
||||
//anim.setEasingFunction(easing);
|
||||
this.modelRootContainerForAnim.animations = [anim];
|
||||
this.animationObserver = this.scene.onAfterAnimationsObservable.add(() => {
|
||||
this.sr.updateMesh(this.modelRootContainerForAnim.getChildMeshes(), false);
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export class AvatarPreviewEngine extends EngineBase<{ // PlayerPreviewEngineに
|
|||
this.scene.autoClear = false;
|
||||
this.scene.skipPointerMovePicking = true;
|
||||
this.scene.skipFrustumClipping = true; // snapshot renderingでは全てのメッシュがアクティブになっている必要があるため
|
||||
this.scene.clearColor = new BABYLON.Color4(0.03, 0.03, 0.03, 1);
|
||||
this.scene.clearColor = new BABYLON.Color4(0.01, 0.01, 0.01, 1);
|
||||
|
||||
this.sr = new BABYLON.SnapshotRenderingHelper(this.scene);
|
||||
|
||||
|
|
@ -160,8 +160,6 @@ export class AvatarPreviewEngine extends EngineBase<{ // PlayerPreviewEngineに
|
|||
this.camera.fov = 0.5;
|
||||
this.camera.lowerRadiusLimit = cm(50);
|
||||
this.camera.upperRadiusLimit = cm(1000);
|
||||
this.camera.useAutoRotationBehavior = true;
|
||||
this.camera.autoRotationBehavior!.idleRotationSpeed = 0.3;
|
||||
//this.camera.mode = BABYLON.Camera.ORTHOGRAPHIC_CAMERA;
|
||||
this.camera.setTarget(new BABYLON.Vector3(0, boundingInfo.centerWorld.y, 0));
|
||||
this.camera.inputs.clear();
|
||||
|
|
|
|||
BIN
packages/frontend/assets/world/avatars/eyes-a.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
packages/frontend/assets/world/avatars/eyes-b.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
packages/frontend/assets/world/avatars/eyes-c.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
packages/frontend/assets/world/avatars/eyes-d.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
packages/frontend/assets/world/avatars/eyes-e.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
packages/frontend/assets/world/avatars/eyes-f.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
packages/frontend/assets/world/avatars/eyes-g.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
packages/frontend/assets/world/avatars/face-parts.af
Normal file
BIN
packages/frontend/assets/world/avatars/mouth-a.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
packages/frontend/assets/world/avatars/mouth-b.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
packages/frontend/assets/world/avatars/mouth-c.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
packages/frontend/assets/world/avatars/mouth-d.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
packages/frontend/assets/world/avatars/mouth-e.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
packages/frontend/assets/world/avatars/mouth-f.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
|
|
@ -9,7 +9,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
:width="1000"
|
||||
:height="600"
|
||||
:scroll="false"
|
||||
:withOkButton="false"
|
||||
:withOkButton="true"
|
||||
@ok="ok"
|
||||
@close="cancel()"
|
||||
@closed="emit('closed')"
|
||||
>
|
||||
|
|
@ -35,9 +36,63 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
:leaveToClass="prefer.s.animation ? $style.transition_options_leaveTo : ''"
|
||||
>
|
||||
<div v-if="showOptions" :class="$style.customize">
|
||||
<MkInput :modelValue="getHex(avatar.body.color)" type="color" :throttle="300" @update:modelValue="v => { const c = getRgb(v); if (c != null) avatar.body.color = c; }">
|
||||
<template #label>{{ i18n.ts.color }}</template>
|
||||
</MkInput>
|
||||
<div class="_gaps">
|
||||
<MkInput v-model="avatarName">
|
||||
<template #label>{{ i18n.ts.name }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts._miWorld._avatars._default.body }}</template>
|
||||
|
||||
<MkInput :modelValue="getHex(avatar.body.color)" type="color" :throttle="300" @update:modelValue="v => { const c = getRgb(v); if (c != null) avatar.body.color = c; }">
|
||||
<template #label>{{ i18n.ts.color }}</template>
|
||||
</MkInput>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts._miWorld._avatars._default.eyes }}</template>
|
||||
|
||||
<MkSelect
|
||||
:items="[
|
||||
{ label: 'a', value: 'a' },
|
||||
{ label: 'b', value: 'b' },
|
||||
{ label: 'c', value: 'c' },
|
||||
{ label: 'd', value: 'd' },
|
||||
{ label: 'e', value: 'e' },
|
||||
{ label: 'f', value: 'f' },
|
||||
{ label: 'g', value: 'g' },
|
||||
]" :modelValue="avatar.eyes.type" @update:modelValue="v => avatar.eyes.type = v"
|
||||
>
|
||||
<template #label>{{ i18n.ts.type }}</template>
|
||||
</MkSelect>
|
||||
|
||||
<MkInput :modelValue="getHex(avatar.eyes.color)" type="color" :throttle="300" @update:modelValue="v => { const c = getRgb(v); if (c != null) avatar.eyes.color = c; }">
|
||||
<template #label>{{ i18n.ts.color }}</template>
|
||||
</MkInput>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #label>{{ i18n.ts._miWorld._avatars._default.mouth }}</template>
|
||||
|
||||
<MkSelect
|
||||
:items="[
|
||||
{ label: i18n.ts.none, value: '_none_' },
|
||||
{ label: 'a', value: 'a' },
|
||||
{ label: 'b', value: 'b' },
|
||||
{ label: 'c', value: 'c' },
|
||||
{ label: 'd', value: 'd' },
|
||||
{ label: 'e', value: 'e' },
|
||||
{ label: 'f', value: 'f' },
|
||||
]" :modelValue="avatar.mouth.type" @update:modelValue="v => avatar.mouth.type = v"
|
||||
>
|
||||
<template #label>{{ i18n.ts.type }}</template>
|
||||
</MkSelect>
|
||||
|
||||
<MkInput :modelValue="getHex(avatar.mouth.color)" type="color" :throttle="300" @update:modelValue="v => { const c = getRgb(v); if (c != null) avatar.mouth.color = c; }">
|
||||
<template #label>{{ i18n.ts.color }}</template>
|
||||
</MkInput>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
|
|
@ -49,11 +104,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<script setup lang="ts">
|
||||
import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive, nextTick, shallowRef, computed, triggerRef, markRaw } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { OBJECT_SCHEMA_DEFS } from 'misskey-world/src/room/object-schema-defs.js';
|
||||
import { getHex, getRgb } from 'misskey-world/src/utility.js';
|
||||
import MkFolder from './MkFolder.vue';
|
||||
import type { Ref } from 'vue';
|
||||
import type { RawOptions } from 'misskey-world/src/room/object.js';
|
||||
import type { RoomAttachments } from 'misskey-world/src/room/type.js';
|
||||
import type { WorldAvatar } from 'misskey-world/src/types.js';
|
||||
import type { AvatarPreviewEngineControllerOptions } from '@/world/avatarPreviewEngineController.js';
|
||||
import { AvatarPreviewEngineController } from '@/world/avatarPreviewEngineController.js';
|
||||
|
|
@ -61,23 +114,26 @@ import { i18n } from '@/i18n.js';
|
|||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||
import * as os from '@/os.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { deepClone } from '@/utility/clone.js';
|
||||
import { store } from '@/store.js';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import { withTimeout } from '@/utility/promise-timeout.js';
|
||||
import { $i } from '@/i.js';
|
||||
import { ensureSignin } from '@/i.js';
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
||||
const props = defineProps<{
|
||||
graphicsQuality: number;
|
||||
avatar: WorldAvatar | null;
|
||||
name: string | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'ok', ctx: {
|
||||
id: string;
|
||||
options: RawOptions;
|
||||
attachments: RoomAttachments;
|
||||
avatar: WorldAvatar;
|
||||
name: string;
|
||||
}): void;
|
||||
(ev: 'cancel'): void;
|
||||
(ev: 'closed'): void;
|
||||
|
|
@ -105,6 +161,8 @@ const avatar: Ref<WorldAvatar> = ref(props.avatar != null ? deepClone(props.avat
|
|||
accessories: [],
|
||||
});
|
||||
|
||||
const avatarName = ref(props.name ?? 'untitled');
|
||||
|
||||
const avatarPreviewEngineControllerOptions = computed<AvatarPreviewEngineControllerOptions>(() => ({
|
||||
graphicsQuality: props.graphicsQuality,
|
||||
fps: null,
|
||||
|
|
@ -121,7 +179,7 @@ watch(avatar, () => {
|
|||
onMounted(async () => {
|
||||
try {
|
||||
await controller.init(canvas.value!, {
|
||||
name: $i.name,
|
||||
name: $i.name ?? $i.username,
|
||||
username: $i.username,
|
||||
avatarUrl: $i.avatarUrl,
|
||||
worldAvatar: avatar.value,
|
||||
|
|
@ -143,24 +201,15 @@ onUnmounted(() => {
|
|||
controller.destroy();
|
||||
});
|
||||
|
||||
function updateObjectOption(k: string, v: any) {
|
||||
controller.updateObjectOption(k, v, attachments);
|
||||
selectedObjectOptionsState.value![k] = v;
|
||||
}
|
||||
//function updateAvatarOption(k: string, v: any) {
|
||||
// avatar.value[k] = v;
|
||||
// controller.updateAvatar(avatar.value);
|
||||
//}
|
||||
|
||||
function ok() {
|
||||
if (selectedId.value == null) return;
|
||||
|
||||
let recentlyUsed = store.s.recentlyUsedRoomObjects;
|
||||
if (recentlyUsed.includes(selectedId.value)) recentlyUsed = recentlyUsed.filter(id => id !== selectedId.value);
|
||||
recentlyUsed.unshift(selectedId.value);
|
||||
if (recentlyUsed.length > 30) recentlyUsed.pop();
|
||||
store.set('recentlyUsedRoomObjects', recentlyUsed);
|
||||
|
||||
emit('ok', {
|
||||
id: selectedId.value,
|
||||
options: deepClone(selectedObjectOptionsState.value!),
|
||||
attachments: deepClone(attachments),
|
||||
avatar: deepClone(avatar.value!),
|
||||
name: avatarName.value,
|
||||
});
|
||||
|
||||
dialog.value?.close();
|
||||
81
packages/frontend/src/components/MkWorldAvatarManager.vue
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div :class="$style.root">
|
||||
<div class="_gaps">
|
||||
<MkPagination v-slot="{items}" :paginator="paginator">
|
||||
<div class="_gaps_s">
|
||||
<div v-for="avatar in items" :key="avatar.id">
|
||||
<div>{{ avatar.name }}</div>
|
||||
<div>{{ avatar.active }}</div>
|
||||
<MkButton small rounded iconOnly @click="editWorldAvatar($event, avatar)"><i class="ti ti-pencil"></i></MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</MkPagination>
|
||||
<MkButton iconOnly rounded style="margin: 0 auto;" @click="createWorldAvatar"><i class="ti ti-plus"></i></MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { markRaw } from 'vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { $i } from '@/i.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import * as os from '@/os.js';
|
||||
import { Paginator } from '@/utility/paginator.js';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
|
||||
const paginator = markRaw(new Paginator('world/avatars/list', {
|
||||
limit: 10,
|
||||
}));
|
||||
|
||||
async function createWorldAvatar(ev: PointerEvent) {
|
||||
const { dispose } = await os.popupAsyncWithDialog(import('./MkWorldAvatarEditDialog.vue').then(x => x.default), {
|
||||
graphicsQuality: prefer.s['world.graphicsQuality'] ?? 0,
|
||||
avatar: null,
|
||||
}, {
|
||||
ok: async (res) => {
|
||||
await os.apiWithDialog('world/avatars/create', {
|
||||
name: res.name,
|
||||
def: res.avatar,
|
||||
});
|
||||
paginator.reload();
|
||||
},
|
||||
closed: () => {
|
||||
dispose();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function editWorldAvatar(ev: PointerEvent, item: Misskey.entities.WorldAvatarsListResponse[number]) {
|
||||
const { dispose } = await os.popupAsyncWithDialog(import('./MkWorldAvatarEditDialog.vue').then(x => x.default), {
|
||||
graphicsQuality: prefer.s['world.graphicsQuality'] ?? 0,
|
||||
avatar: item.def,
|
||||
name: item.name,
|
||||
}, {
|
||||
ok: async (res) => {
|
||||
await os.apiWithDialog('world/avatars/update', {
|
||||
avatarId: item.id,
|
||||
name: res.name,
|
||||
def: res.avatar,
|
||||
});
|
||||
paginator.reload();
|
||||
},
|
||||
closed: () => {
|
||||
dispose();
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style module>
|
||||
.root {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -829,11 +829,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #icon><i class="ti ti-user"></i></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts._miWorld.avatar }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps">
|
||||
<div class="_gaps_s">
|
||||
<MkButton iconOnly rounded style="margin: 0 auto;" @click="createWorldAvatar"><i class="ti ti-plus"></i></MkButton>
|
||||
</div>
|
||||
</div>
|
||||
<MkWorldAvatarManager/>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
|
|
@ -970,6 +966,7 @@ import MkDisableSection from '@/components/MkDisableSection.vue';
|
|||
import FormLink from '@/components/form/link.vue';
|
||||
import MkLink from '@/components/MkLink.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkWorldAvatarManager from '@/components/MkWorldAvatarManager.vue';
|
||||
import { store } from '@/store.js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
|
|
@ -1233,20 +1230,6 @@ function testNotification(): void {
|
|||
}, 300);
|
||||
}
|
||||
|
||||
async function createWorldAvatar(ev: PointerEvent) {
|
||||
const { dispose } = await os.popupAsyncWithDialog(import('../rooms/edit-world-avatar-dialog.vue').then(x => x.default), {
|
||||
graphicsQuality: worldGraphicsQuality.value ?? 0,
|
||||
avatar: null,
|
||||
}, {
|
||||
ok: async (res) => {
|
||||
console.log(res);
|
||||
},
|
||||
closed: () => {
|
||||
dispose();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
|
||||
const headerTabs = computed(() => []);
|
||||
|
|
|
|||
|
|
@ -13319,6 +13319,22 @@ export interface Locale extends ILocale {
|
|||
* アバター
|
||||
*/
|
||||
"avatar": string;
|
||||
"_avatars": {
|
||||
"_default": {
|
||||
/**
|
||||
* ボディ
|
||||
*/
|
||||
"body": string;
|
||||
/**
|
||||
* 目
|
||||
*/
|
||||
"eyes": string;
|
||||
/**
|
||||
* 口
|
||||
*/
|
||||
"mouth": string;
|
||||
};
|
||||
};
|
||||
};
|
||||
"_miRoom": {
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -37246,7 +37246,7 @@ export interface operations {
|
|||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
'application/json': components['schemas']['WorldAvatarLite'][];
|
||||
'application/json': components['schemas']['WorldAvatarDetailed'][];
|
||||
};
|
||||
};
|
||||
/** @description Client error */
|
||||
|
|
|
|||