mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-20 10:35:40 +02:00
wip
This commit is contained in:
@@ -31,23 +31,35 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
<div v-show="selectedId != null" :class="$style.preview" class="_panel">
|
||||
<canvas ref="canvas" :class="$style.canvas"></canvas>
|
||||
<button :class="$style.unselectButton" @click="selectedId = null">x</button>
|
||||
<MkButton :class="$style.unselectButton" small iconOnly @click="selectedId = null"><i class="ti ti-x"></i></MkButton>
|
||||
<MkButton :class="$style.customizeButton" small iconOnly @click=""><i class="ti ti-settings"></i></MkButton>
|
||||
<div v-if="selectedObjectDef != null && selectedInstanceId != null" :class="$style.customize">
|
||||
<XObjectCustomizeForm :schema="selectedObjectDef.options.schema" :options="selectedObjectOptionsState" @update="(k, v) => { engine.updateObjectOption(k, v); triggerRef(selectedObjectOptionsState) }"></XObjectCustomizeForm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</MkModalWindow>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive, nextTick, shallowRef } from 'vue';
|
||||
import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive, nextTick, shallowRef, computed, triggerRef } from 'vue';
|
||||
import XObjectCustomizeForm from './room.object-customize-form.vue';
|
||||
import type { RoomObjectInstance, RoomStateObject } from '@/world/room/object.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { OBJECT_DEFS } from '@/world/room/object-defs.js';
|
||||
import { createRoomObjectPreviewEngine, RoomObjectPreviewEngine } from '@/world/room/previewEngine.js';
|
||||
import { camelToKebab } from '@/world/utility.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
|
||||
// TODO: instanceのidと紛らわしいのでid -> typeにする
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'ok', id: string): void;
|
||||
(ev: 'ok', ctx: {
|
||||
id: string;
|
||||
options: any;
|
||||
}): void;
|
||||
(ev: 'cancel'): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
@@ -55,6 +67,9 @@ const emit = defineEmits<{
|
||||
const dialog = useTemplateRef('dialog');
|
||||
const canvas = useTemplateRef('canvas');
|
||||
const selectedId = ref<string | null>(null);
|
||||
const selectedInstanceId = ref<string | null>(null);
|
||||
const selectedObjectOptionsState = shallowRef<RoomStateObject | null>(null);
|
||||
const selectedObjectDef = computed(() => OBJECT_DEFS.find(def => def.id === selectedId.value) ?? null);
|
||||
const engine = shallowRef<RoomObjectPreviewEngine | null>(null);
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -70,17 +85,27 @@ onUnmounted(() => {
|
||||
});
|
||||
|
||||
watch(selectedId, (newId) => {
|
||||
if (newId == null) return;
|
||||
|
||||
nextTick(() => {
|
||||
engine.value!.load(newId);
|
||||
engine.value!.resize();
|
||||
});
|
||||
if (newId == null) {
|
||||
engine.value!.clear();
|
||||
selectedInstanceId.value = null;
|
||||
selectedObjectOptionsState.value = null;
|
||||
} else {
|
||||
nextTick(() => {
|
||||
engine.value!.load(newId).then(res => {
|
||||
selectedInstanceId.value = res.id;
|
||||
selectedObjectOptionsState.value = res.options;
|
||||
engine.value!.resize();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function ok() {
|
||||
if (selectedId.value == null) return;
|
||||
emit('ok', selectedId.value);
|
||||
emit('ok', {
|
||||
id: selectedId.value,
|
||||
options: selectedObjectOptionsState.value,
|
||||
});
|
||||
dialog.value?.close();
|
||||
}
|
||||
|
||||
@@ -146,8 +171,20 @@ async function cancel() {
|
||||
}
|
||||
|
||||
.unselectButton {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
}
|
||||
|
||||
.customizeButton {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
.customize {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
59
packages/frontend/src/pages/room.object-customize-form.vue
Normal file
59
packages/frontend/src/pages/room.object-customize-form.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div :class="$style.root">
|
||||
<div class="_gaps">
|
||||
<div v-for="[k, s] in Object.entries(schema)" :key="k">
|
||||
<div>{{ s.label }}</div>
|
||||
<div v-if="s.type === 'color'">
|
||||
<MkInput :modelValue="getHex(options[k])" type="color" @update:modelValue="v => { const c = getRgb(v); if (c != null) emit('update', k, c); }"></MkInput>
|
||||
</div>
|
||||
<div v-else-if="s.type === 'boolean'">
|
||||
<MkSwitch :modelValue="options[k]" @update:modelValue="v => emit('update', k, v)"></MkSwitch>
|
||||
</div>
|
||||
<div v-else-if="s.type === 'enum'">
|
||||
<MkSelect :items="s.enum.map(e => ({ label: e, value: e }))" :modelValue="options[k]" @update:modelValue="v => emit('update', k, v)"></MkSelect>
|
||||
</div>
|
||||
<div v-else-if="s.type === 'range'">
|
||||
<MkRange :continuousUpdate="true" :min="s.min" :max="s.max" :step="s.step" :modelValue="options[k]" @update:modelValue="v => emit('update', k, v)"></MkRange>
|
||||
</div>
|
||||
<div v-else-if="s.type === 'image'">
|
||||
<MkInput type="text" :modelValue="options[k]" @update:modelValue="v => emit('update', k, v)"></MkInput>
|
||||
</div>
|
||||
<div v-else-if="s.type === 'seed'">
|
||||
<MkRange :continuousUpdate="true" :min="0" :max="1000" :step="1" :modelValue="options[k]" @update:modelValue="v => emit('update', k, v)"></MkRange>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineAsyncComponent, nextTick, onMounted, onUnmounted, ref, shallowRef, useTemplateRef, watch } from 'vue';
|
||||
import type { ObjectDef } from '@/world/room/object.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import * as os from '@/os.js';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkRange from '@/components/MkRange.vue';
|
||||
import { getHex, getRgb } from '@/world/utility.js';
|
||||
|
||||
const props = defineProps<{
|
||||
schema: ObjectDef['options']['schema'];
|
||||
options: any;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'update', k: string, v: any): void;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
}
|
||||
</style>
|
||||
@@ -44,26 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div v-if="controller.isReady.value && controller.isEditMode.value && controller.selected.value != null && !controller.grabbing.value" :key="controller.selected.value.objectId" class="_panel" :class="$style.overlayObjectInfoPanel">
|
||||
{{ controller.selected.value.objectDef.name }}
|
||||
|
||||
<div class="_gaps">
|
||||
<div v-for="[k, s] in Object.entries(controller.selected.value.objectDef.options.schema)" :key="k">
|
||||
<div>{{ s.label }}</div>
|
||||
<div v-if="s.type === 'color'">
|
||||
<MkInput :modelValue="getHex(controller.selected.value.objectState.options[k])" type="color" @update:modelValue="v => { const c = getRgb(v); if (c != null) controller.updateObjectOption(controller.selected.value.objectId, k, c); }"></MkInput>
|
||||
</div>
|
||||
<div v-else-if="s.type === 'boolean'">
|
||||
<MkSwitch :modelValue="controller.selected.value.objectState.options[k]" @update:modelValue="v => controller.updateObjectOption(controller.selected.value.objectId, k, v)"></MkSwitch>
|
||||
</div>
|
||||
<div v-else-if="s.type === 'enum'">
|
||||
<MkSelect :items="s.enum.map(e => ({ label: e, value: e }))" :modelValue="controller.selected.value.objectState.options[k]" @update:modelValue="v => controller.updateObjectOption(controller.selected.value.objectId, k, v)"></MkSelect>
|
||||
</div>
|
||||
<div v-else-if="s.type === 'range'">
|
||||
<MkRange :continuousUpdate="true" :min="s.min" :max="s.max" :step="s.step" :modelValue="controller.selected.value.objectState.options[k]" @update:modelValue="v => controller.updateObjectOption(controller.selected.value.objectId, k, v)"></MkRange>
|
||||
</div>
|
||||
<div v-else-if="s.type === 'image'">
|
||||
<MkInput type="text" :modelValue="controller.selected.value.objectState.options[k]" @update:modelValue="v => controller.updateObjectOption(controller.selected.value.objectId, k, v)"></MkInput>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<XObjectCustomizeForm :schema="controller.selected.value.objectDef.options.schema" :options="controller.selected.value.objectState.options" @update="(k, v) => controller.updateObjectOption(controller.selected.value.objectId, k, v)"></XObjectCustomizeForm>
|
||||
</div>
|
||||
|
||||
<div v-if="isRoomSettingsOpen" class="_panel" :class="$style.overlayObjectInfoPanel">
|
||||
@@ -119,6 +100,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineAsyncComponent, nextTick, onMounted, onUnmounted, ref, shallowRef, useTemplateRef, watch } from 'vue';
|
||||
import XObjectCustomizeForm from './room.object-customize-form.vue';
|
||||
import { definePage } from '@/page.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { ensureSignin } from '@/i';
|
||||
@@ -129,7 +111,7 @@ import MkInput from '@/components/MkInput.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkRange from '@/components/MkRange.vue';
|
||||
import { RoomController } from '@/world/room/controller.js';
|
||||
import { cm } from '@/world/utility.js';
|
||||
import { cm, getHex, getRgb } from '@/world/utility.js';
|
||||
import { deepClone } from '@/utility/clone.js';
|
||||
|
||||
const canvas = useTemplateRef('canvas');
|
||||
@@ -283,7 +265,7 @@ async function addObject(ev: PointerEvent) {
|
||||
const { dispose } = await os.popupAsyncWithDialog(import('./room.add-object-dialog.vue').then(x => x.default), {
|
||||
}, {
|
||||
ok: async (res) => {
|
||||
controller.addObject(res);
|
||||
controller.addObject(res.id, res.options);
|
||||
canvas.value!.focus();
|
||||
},
|
||||
closed: () => {
|
||||
@@ -306,24 +288,6 @@ function exitEditMode() {
|
||||
controller.exitEditMode();
|
||||
}
|
||||
|
||||
function getHex(c: [number, number, number]) {
|
||||
return `#${c.map(x => Math.round(x * 255).toString(16).padStart(2, '0')).join('')}`;
|
||||
}
|
||||
|
||||
function getRgb(hex: string | number): [number, number, number] | null {
|
||||
if (
|
||||
typeof hex === 'number' ||
|
||||
typeof hex !== 'string' ||
|
||||
!/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(hex)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const m = hex.slice(1).match(/[0-9a-fA-F]{2}/g);
|
||||
if (m == null) return [0, 0, 0];
|
||||
return m.map(x => parseInt(x, 16) / 255) as [number, number, number];
|
||||
}
|
||||
|
||||
function save() {
|
||||
latestData = deepClone(controller.roomState.value);
|
||||
localStorage.setItem('roomData', JSON.stringify(latestData));
|
||||
|
||||
Reference in New Issue
Block a user