mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-05 13:35:52 +02:00
enhance(frontend): ウィジェットの設定画面を改良 (#17033)
* enhance(frontend): ウィジェットの設定画面を改良 * Update Changelog * fix lint
This commit is contained in:
172
packages/frontend/src/components/MkWidgetSettingsDialog.vue
Normal file
172
packages/frontend/src/components/MkWidgetSettingsDialog.vue
Normal file
@@ -0,0 +1,172 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<MkModalWindow
|
||||
ref="dialog"
|
||||
:width="1000"
|
||||
:height="600"
|
||||
:scroll="false"
|
||||
:withOkButton="true"
|
||||
@close="cancel()"
|
||||
@ok="save()"
|
||||
@closed="emit('closed')"
|
||||
>
|
||||
<template #header><i class="ti ti-icons"></i> {{ widgetName }}</template>
|
||||
|
||||
<MkPreviewWithControls>
|
||||
<template #preview>
|
||||
<div :class="$style.previewWrapper">
|
||||
<div class="_acrylic" :class="$style.previewTitle">{{ i18n.ts.preview }}</div>
|
||||
|
||||
<div ref="resizerRootEl" :class="$style.previewResizerRoot" inert>
|
||||
<div
|
||||
ref="resizerEl"
|
||||
:class="$style.previewResizer"
|
||||
:style="{ transform: widgetStyle }"
|
||||
>
|
||||
<component
|
||||
:is="`widget-${widgetName}`"
|
||||
:key="currentId"
|
||||
:widget="{ name: widgetName, id: '__PREVIEW__', data: settings }"
|
||||
></component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #controls>
|
||||
<div class="_spacer">
|
||||
<MkForm v-model="settings" :form="form"/>
|
||||
</div>
|
||||
</template>
|
||||
</MkPreviewWithControls>
|
||||
</MkModalWindow>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, useTemplateRef, ref, computed, watch, onBeforeUnmount, onMounted } from 'vue';
|
||||
import { deepClone } from '@/utility/clone.js';
|
||||
import { genId } from '@/utility/id.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||
import MkPreviewWithControls from './MkPreviewWithControls.vue';
|
||||
import MkForm from '@/components/MkForm.vue';
|
||||
import type { Form } from '@/utility/form.js';
|
||||
|
||||
const props = defineProps<{
|
||||
widgetName: string;
|
||||
form: Form;
|
||||
currentSettings: Record<string, any>;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'saved', settings: Record<string, any>): void;
|
||||
(ev: 'canceled'): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const dialog = useTemplateRef('dialog');
|
||||
|
||||
const settings = reactive<Record<string, any>>(deepClone(props.currentSettings));
|
||||
const currentId = ref(genId());
|
||||
|
||||
watch(settings, () => {
|
||||
currentId.value = genId();
|
||||
});
|
||||
|
||||
function save() {
|
||||
emit('saved', deepClone(settings));
|
||||
dialog.value?.close();
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
emit('canceled');
|
||||
dialog.value?.close();
|
||||
}
|
||||
|
||||
//#region プレビューのリサイズ
|
||||
const resizerRootEl = useTemplateRef('resizerRootEl');
|
||||
const resizerEl = useTemplateRef('resizerEl');
|
||||
const widgetHeight = ref(0);
|
||||
const widgetScale = ref(1);
|
||||
const widgetStyle = computed(() => {
|
||||
return `translate(-50%, -50%) scale(${widgetScale.value})`;
|
||||
});
|
||||
const ro1 = new ResizeObserver(() => {
|
||||
widgetHeight.value = resizerEl.value!.clientHeight;
|
||||
calcScale();
|
||||
});
|
||||
const ro2 = new ResizeObserver(() => {
|
||||
calcScale();
|
||||
});
|
||||
|
||||
function calcScale() {
|
||||
if (!resizerRootEl.value) return;
|
||||
const previewWidth = resizerRootEl.value.clientWidth - 40; // 左右の余白 20pxずつ
|
||||
const previewHeight = resizerRootEl.value.clientHeight - 40; // 上下の余白 20pxずつ
|
||||
const widgetWidth = 280;
|
||||
const scale = Math.min(previewWidth / widgetWidth, previewHeight / widgetHeight.value, 1); // 拡大はしないので1を上限に
|
||||
widgetScale.value = scale;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (resizerEl.value) {
|
||||
ro1.observe(resizerEl.value);
|
||||
}
|
||||
if (resizerRootEl.value) {
|
||||
ro2.observe(resizerRootEl.value);
|
||||
}
|
||||
calcScale();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
ro1.disconnect();
|
||||
ro2.disconnect();
|
||||
});
|
||||
//#endregion
|
||||
</script>
|
||||
|
||||
<style module>
|
||||
.previewContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
|
||||
.previewTitle {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
.previewWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
|
||||
.previewResizerRoot {
|
||||
position: relative;
|
||||
flex: 1 0;
|
||||
}
|
||||
|
||||
.previewResizer {
|
||||
position: absolute;
|
||||
container-type: inline-size;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 280px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user