mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-05 12:25:54 +02:00
enhance(frontend): 画像エフェクトのUI改善 (#16191)
* enhance(frontend): 画像エフェクトの改善 * enhance: i18n colorClampAdvanced * fix: missing translation * enhance: i18n blockNoise * fix lint * fix: narrow down fx defs types * fix * fix: watermark用エフェクトは別で定義し直す * fix lint * ImageEffectorをwatermarkに隠蔽 * watermark関連の定義を完全に分離 * refactor * fix * ぼかし効果 -> スムージング * refactor: remove unnecessary `as const` * Update Changelog
This commit is contained in:
@@ -14,73 +14,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div :class="$style.root" class="_gaps">
|
||||
<div v-for="[k, v] in Object.entries(fx.params)" :key="k">
|
||||
<MkSwitch
|
||||
v-if="v.type === 'boolean'"
|
||||
v-model="layer.params[k]"
|
||||
>
|
||||
<template #label>{{ fx.params[k].label ?? k }}</template>
|
||||
</MkSwitch>
|
||||
<MkRange
|
||||
v-else-if="v.type === 'number'"
|
||||
v-model="layer.params[k]"
|
||||
continuousUpdate
|
||||
:min="v.min"
|
||||
:max="v.max"
|
||||
:step="v.step"
|
||||
:textConverter="fx.params[k].toViewValue"
|
||||
@thumbDoubleClicked="() => {
|
||||
if (fx.params[k].default != null) {
|
||||
layer.params[k] = fx.params[k].default;
|
||||
} else {
|
||||
layer.params[k] = v.min;
|
||||
}
|
||||
}"
|
||||
>
|
||||
<template #label>{{ fx.params[k].label ?? k }}</template>
|
||||
</MkRange>
|
||||
<MkRadios
|
||||
v-else-if="v.type === 'number:enum'"
|
||||
v-model="layer.params[k]"
|
||||
>
|
||||
<template #label>{{ fx.params[k].label ?? k }}</template>
|
||||
<option v-for="item in v.enum" :value="item.value">{{ item.label }}</option>
|
||||
</MkRadios>
|
||||
<div v-else-if="v.type === 'seed'">
|
||||
<MkRange
|
||||
v-model="layer.params[k]"
|
||||
continuousUpdate
|
||||
type="number"
|
||||
:min="0"
|
||||
:max="10000"
|
||||
:step="1"
|
||||
>
|
||||
<template #label>{{ fx.params[k].label ?? k }}</template>
|
||||
</MkRange>
|
||||
</div>
|
||||
<MkInput
|
||||
v-else-if="v.type === 'color'"
|
||||
:modelValue="getHex(layer.params[k])"
|
||||
type="color"
|
||||
@update:modelValue="v => { const c = getRgb(v); if (c != null) layer.params[k] = c; }"
|
||||
>
|
||||
<template #label>{{ fx.params[k].label ?? k }}</template>
|
||||
</MkInput>
|
||||
</div>
|
||||
</div>
|
||||
<MkImageEffectorFxForm v-model="layer.params" :paramDefs="fx.params" />
|
||||
</MkFolder>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkRadios from '@/components/MkRadios.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkRange from '@/components/MkRange.vue';
|
||||
import MkImageEffectorFxForm from '@/components/MkImageEffectorFxForm.vue';
|
||||
import { FXS } from '@/utility/image-effector/fxs.js';
|
||||
|
||||
const layer = defineModel<ImageEffectorLayer>('layer', { required: true });
|
||||
@@ -94,28 +36,4 @@ const emit = defineEmits<{
|
||||
(e: 'swapUp'): void;
|
||||
(e: 'swapDown'): void;
|
||||
}>();
|
||||
|
||||
function getHex(c: [number, number, number]) {
|
||||
return `#${c.map(x => (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];
|
||||
}
|
||||
</script>
|
||||
|
||||
<style module>
|
||||
.root {
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
95
packages/frontend/src/components/MkImageEffectorFxForm.vue
Normal file
95
packages/frontend/src/components/MkImageEffectorFxForm.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_gaps">
|
||||
<div v-for="v, k in paramDefs" :key="k">
|
||||
<MkSwitch
|
||||
v-if="v.type === 'boolean'"
|
||||
v-model="params[k]">
|
||||
<template #label>{{ v.label ?? k }}</template>
|
||||
<template v-if="v.caption != null" #caption>{{ v.caption }}</template>
|
||||
</MkSwitch>
|
||||
<MkRange
|
||||
v-else-if="v.type === 'number'"
|
||||
v-model="params[k]"
|
||||
continuousUpdate
|
||||
:min="v.min"
|
||||
:max="v.max"
|
||||
:step="v.step"
|
||||
:textConverter="v.toViewValue"
|
||||
@thumbDoubleClicked="() => {
|
||||
params[k] = v.default;
|
||||
}"
|
||||
>
|
||||
<template #label>{{ v.label ?? k }}</template>
|
||||
<template v-if="v.caption != null" #caption>{{ v.caption }}</template>
|
||||
</MkRange>
|
||||
<MkRadios v-else-if="v.type === 'number:enum'" v-model="params[k]">
|
||||
<template #label>{{ v.label ?? k }}</template>
|
||||
<template v-if="v.caption != null" #caption>{{ v.caption }}</template>
|
||||
<option v-for="item in v.enum" :value="item.value">
|
||||
<i v-if="item.icon" :class="item.icon"></i>
|
||||
<template v-else>{{ item.label }}</template>
|
||||
</option>
|
||||
</MkRadios>
|
||||
<div v-else-if="v.type === 'seed'">
|
||||
<MkRange v-model="params[k]" continuousUpdate type="number" :min="0" :max="10000" :step="1">
|
||||
<template #label>{{ v.label ?? k }}</template>
|
||||
<template v-if="v.caption != null" #caption>{{ v.caption }}</template>
|
||||
</MkRange>
|
||||
</div>
|
||||
<MkInput v-else-if="v.type === 'color'" :modelValue="getHex(params[k])" type="color" @update:modelValue="v => { const c = getRgb(v); if (c != null) params[k] = c; }">
|
||||
<template #label>{{ v.label ?? k }}</template>
|
||||
<template v-if="v.caption != null" #caption>{{ v.caption }}</template>
|
||||
</MkInput>
|
||||
</div>
|
||||
<div v-if="Object.keys(paramDefs).length === 0" :class="$style.nothingToConfigure">
|
||||
{{ i18n.ts._imageEffector.nothingToConfigure }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkRadios from '@/components/MkRadios.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkRange from '@/components/MkRange.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import type { ImageEffectorRGB, ImageEffectorFxParamDefs } from '@/utility/image-effector/ImageEffector.js';
|
||||
|
||||
defineProps<{
|
||||
paramDefs: ImageEffectorFxParamDefs;
|
||||
}>();
|
||||
|
||||
const params = defineModel<Record<string, any>>({ required: true });
|
||||
|
||||
function getHex(c: ImageEffectorRGB) {
|
||||
return `#${c.map(x => (x * 255).toString(16).padStart(2, '0')).join('')}`;
|
||||
}
|
||||
|
||||
function getRgb(hex: string | number): ImageEffectorRGB | 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 ImageEffectorRGB;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style module>
|
||||
.nothingToConfigure {
|
||||
opacity: 0.7;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -44,6 +44,11 @@ const y = defineModel<string>('y', { default: 'center' });
|
||||
height: 32px;
|
||||
background: var(--MI_THEME-panel);
|
||||
border-radius: 4px;
|
||||
transition: background 0.1s ease;
|
||||
|
||||
&:not(.active):hover {
|
||||
background: var(--MI_THEME-buttonHoverBg);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: var(--MI_THEME-accentedBg);
|
||||
|
||||
Reference in New Issue
Block a user