This commit is contained in:
syuilo 2026-05-25 20:26:36 +09:00
commit 46efad5b12
7 changed files with 30 additions and 26 deletions

View file

@ -9,16 +9,18 @@ SPDX-License-Identifier: AGPL-3.0-only
:class="[$style.root]"
>
<img :class="$style.thumbnail" :src="`/client-assets/room/object-thumbs/${camelToKebab(def.id)}.png`"/>
<div :class="$style.name"><MkCondensedLine :minScale="0.5">{{ def.name }}</MkCondensedLine></div>
<div :class="$style.name"><MkCondensedLine :minScale="0.5">{{ OBJECT_UI_DEFS[def.id].name }}</MkCondensedLine></div>
<i v-if="Object.keys(def.options.schema).length > 0" :class="$style.customizable" class="ti ti-tool"></i>
</div>
</template>
<script setup lang="ts">
import type { OBJECT_SCHEMA_DEFS } from '@/world/room/object-schema-defs.js';
import { OBJECT_UI_DEFS } from '@/world/room/object-ui-defs.js';
import { camelToKebab } from '@/world/utility.js';
const props = defineProps<{
def: typeof OBJECT_DEFS[number];
def: typeof OBJECT_SCHEMA_DEFS[string];
}>();
</script>

View file

@ -19,16 +19,16 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.main">
<div class="_spacer _gaps">
<MkInput v-model="searchKeyword" type="search" :placeholder="i18n.ts.search"></MkInput>
<MkFoldableSection v-if="recentlyUsedDefs.length > 0" :expanded="true">
<MkFoldableSection v-if="recentlyUsedSchemas.length > 0" :expanded="true">
<template #header>{{ i18n.ts.recentUsed }}</template>
<div :class="$style.catalogItems">
<XItem v-for="def in recentlyUsedDefs" :key="def.id" :def="def" :class="[$style.catalogItem]" @click="selectedId = def.id"/>
<XItem v-for="def in recentlyUsedSchemas" :key="def.id" :def="def" :class="[$style.catalogItem]" @click="selectedId = def.id"/>
</div>
</MkFoldableSection>
<MkFoldableSection :expanded="true">
<template #header>{{ i18n.ts.all }}</template>
<div :class="$style.catalogItems">
<XItem v-for="def in OBJECT_DEFS" :key="def.id" :def="def" :class="[$style.catalogItem]" @click="selectedId = def.id"/>
<XItem v-for="def in OBJECT_SCHEMA_DEFS" :key="def.id" :def="def" :class="[$style.catalogItem]" @click="selectedId = def.id"/>
</div>
</MkFoldableSection>
</div>
@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.preview" @click.stop>
<canvas ref="canvas" :class="$style.canvas"></canvas>
<MkButton :class="$style.unselectButton" small rounded iconOnly @click="selectedId = null"><i class="ti ti-x"></i></MkButton>
<MkButton v-if="selectedObjectDef != null && Object.keys(selectedObjectDef.options.schema).length > 0" :class="$style.customizeButton" small rounded iconOnly @click="showObjectOptions = !showObjectOptions"><i class="ti ti-tool"></i></MkButton>
<MkButton v-if="selectedObjectSchema != null && Object.keys(selectedObjectSchema.options.schema).length > 0" :class="$style.customizeButton" small rounded iconOnly @click="showObjectOptions = !showObjectOptions"><i class="ti ti-tool"></i></MkButton>
<MkButton :class="$style.addButton" small rounded primary @click="ok"><i class="ti ti-plus"></i></MkButton>
<Transition
@ -53,8 +53,8 @@ SPDX-License-Identifier: AGPL-3.0-only
:enterFromClass="prefer.s.animation ? $style.transition_options_enterFrom : ''"
:leaveToClass="prefer.s.animation ? $style.transition_options_leaveTo : ''"
>
<div v-if="selectedObjectDef != null && selectedInstanceId != null && showObjectOptions" :class="$style.customize" class="_shadow">
<XObjectCustomizeForm :addFileAttachment="addFileAttachment" :schema="selectedObjectDef.options.schema" :options="selectedObjectOptionsState" @update="(k, v) => updateObjectOption(k, v)"></XObjectCustomizeForm>
<div v-if="selectedObjectSchema != null && selectedInstanceId != null && showObjectOptions" :class="$style.customize" class="_shadow">
<XObjectCustomizeForm :addFileAttachment="addFileAttachment" :schema="selectedObjectSchema" :options="selectedObjectOptionsState" @update="(k, v) => updateObjectOption(k, v)"></XObjectCustomizeForm>
</div>
</Transition>
</div>
@ -84,6 +84,7 @@ import MkFoldableSection from '@/components/MkFoldableSection.vue';
import { PreviewEngineController } from '@/world/room/previewEngineController.js';
import MkInput from '@/components/MkInput.vue';
import { withTimeout } from '@/utility/promise-timeout.js';
import { OBJECT_SCHEMA_DEFS } from '@/world/room/object-schema-defs.js';
// TODO: instanceidid -> type
@ -94,7 +95,7 @@ const props = defineProps<{
const emit = defineEmits<{
(ev: 'ok', ctx: {
id: string;
options: any;
options: RawOptions;
attachments: RoomAttachments;
}): void;
(ev: 'cancel'): void;
@ -107,7 +108,7 @@ const selectedId = ref<string | null>(null);
const showPreview = ref(false);
const selectedInstanceId = ref<string | null>(null);
const selectedObjectOptionsState = ref<RawOptions | null>(null);
const selectedObjectDef = computed(() => OBJECT_DEFS.find(def => def.id === selectedId.value) ?? null);
const selectedObjectSchema = computed(() => selectedId.value == null ? null : OBJECT_SCHEMA_DEFS[selectedId.value]);
const showObjectOptions = ref(false);
const searchKeyword = ref('');
@ -128,9 +129,9 @@ const previewEngineControllerOptions = computed<PreviewEngineControllerOptions>(
const controller = markRaw(new PreviewEngineController(previewEngineControllerOptions.value));
const recentlyUsedDefs = computed(() => {
const recentlyUsedSchemas = computed(() => {
const recentlyUsed = store.s.recentlyUsedRoomObjects;
return recentlyUsed.map(id => OBJECT_DEFS.find(def => def.id === id)).filter((def): def is typeof OBJECT_DEFS[number] => def != null);
return recentlyUsed.map(id => OBJECT_SCHEMA_DEFS[id]).filter((def): def is typeof OBJECT_SCHEMA_DEFS[string] => def != null);
});
onMounted(async () => {
@ -205,7 +206,7 @@ function ok() {
emit('ok', {
id: selectedId.value,
options: deepClone(selectedObjectOptionsState.value),
options: deepClone(selectedObjectOptionsState.value!),
attachments: deepClone(attachments),
});

View file

@ -70,10 +70,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
<div v-if="controller.isReady.value && controller.isEditMode.value && controller.selected.value != null && selectedObjectDef != null && !controller.grabbing.value" :key="controller.selected.value.objectId" :class="$style.overlayObjectInfoPanel">
<div style="margin-bottom: 8px; font-weight: bold; text-align: center;">{{ selectedObjectDef.name }}</div>
<div v-if="controller.isReady.value && controller.isEditMode.value && controller.selected.value != null && !controller.grabbing.value" :key="controller.selected.value.objectId" :class="$style.overlayObjectInfoPanel">
<div style="margin-bottom: 8px; font-weight: bold; text-align: center;">{{ OBJECT_UI_DEFS[controller.selected.value.objectState.type].name }}</div>
<XObjectCustomizeForm :addFileAttachment="addFileAttachment" :schema="selectedObjectDef.options.schema" :options="controller.selected.value.objectState.options" @update="(k, v) => updateObjectOption(k, v)"></XObjectCustomizeForm>
<XObjectCustomizeForm :addFileAttachment="addFileAttachment" :schema="OBJECT_SCHEMA_DEFS[controller.selected.value.objectState.type]" :options="controller.selected.value.objectState.options" @update="(k, v) => updateObjectOption(k, v)"></XObjectCustomizeForm>
</div>
<div v-if="isRoomSettingsOpen && controller.isEditMode.value" class="_panel" :class="$style.overlayObjectInfoPanel">
@ -105,6 +105,8 @@ import { prefer } from '@/preferences.js';
import { GRAPHICS_QUALITY } from '@/world/room/utility.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { miLocalStorage } from '@/local-storage.js';
import { OBJECT_UI_DEFS } from '@/world/room/object-ui-defs.js';
import { OBJECT_SCHEMA_DEFS } from '@/world/room/object-schema-defs.js';
const roomSpecVersion = 0;
@ -275,8 +277,6 @@ const roomControllerOptions = computed<RoomControllerOptions>(() => ({
const controller = markRaw(new RoomController(deepClone(initialRoomState), roomControllerOptions.value));
const selectedObjectDef = computed(() => controller.selected.value == null ? null : getObjectDef(controller.selected.value.objectState.type));
onMounted(async () => {
if (!await BABYLON.WebGPUEngine.IsSupportedAsync) {
os.alert({

View file

@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div :class="$style.root">
<div class="_gaps_s">
<MkFolder v-for="[k, s] in Object.entries(schema)" :key="k">
<template #label>{{ s.label }}</template>
<MkFolder v-for="[k, s] in Object.entries(schema.options.schema)" :key="k">
<template #label>{{ OBJECT_UI_DEFS[schema.id].options[k].label }}</template>
<template #suffix>
<span v-if="s.type === 'color'" :style="{ color: getHex(options[k]) }"></span>
<span v-else-if="s.type === 'material'" :style="{ color: getHex(options[k].color) }"></span>
@ -75,7 +75,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { } from 'vue';
import * as Misskey from 'misskey-js';
import type { ObjectDef } from '@/world/room/object.js';
import type { ObjectSchemaDef, RawOptions } from '@/world/room/object.js';
import { i18n } from '@/i18n.js';
import MkButton from '@/components/MkButton.vue';
import MkSelect from '@/components/MkSelect.vue';
@ -87,10 +87,11 @@ import { getHex, getRgb } from '@/world/utility.js';
import { chooseDriveFile } from '@/utility/drive.js';
import MkRadios from '@/components/MkRadios.vue';
import MkFolder from '@/components/MkFolder.vue';
import { OBJECT_UI_DEFS } from '@/world/room/object-ui-defs.js';
const props = defineProps<{
schema: ObjectDef['options']['schema'];
options: any;
schema: ObjectSchemaDef<any>;
options: RawOptions;
addFileAttachment: ((file: Misskey.entities.DriveFile) => void);
}>();

View file

@ -226,7 +226,7 @@ export const OBJECT_DEFS = [
wireNet,
clippedPicture,
wireBasket,
];
] as ObjectDef[];
export function getObjectDef(type: string): ObjectDef {
const def = OBJECT_DEFS.find(x => x.id === type) as ObjectDef | undefined;

View file

@ -223,7 +223,7 @@ export const OBJECT_SCHEMA_DEFS = {
wireNet: wireNet_schema,
clippedPicture: clippedPicture_schema,
wireBasket: wireBasket_schema,
};
} as Record<string, ObjectSchemaDef<any>>;
export function getObjectSchemaDef(type: string): ObjectSchemaDef {
const def = OBJECT_SCHEMA_DEFS[type as keyof typeof OBJECT_SCHEMA_DEFS];

View file

@ -223,7 +223,7 @@ export const OBJECT_UI_DEFS = {
wireNet: wireNet_ui,
clippedPicture: clippedPicture_ui,
wireBasket: wireBasket_ui,
};
} as Record<string, ObjectUiDef>;
export function getObjectUiDef(type: string): ObjectUiDef {
const def = OBJECT_UI_DEFS[type as keyof typeof OBJECT_UI_DEFS];