mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-02 17:55:52 +02:00
fix/refactor(frontend): 画像編集機能の修正・型強化 (#16156)
* enhance: refine uploadFile * fix: missing locale * refactor: harden types * refactor: シェーダーファイルをlazy-loadingできるように * fix(frontend): omit console.log in production environment * fix: glslのバージョン表記は最初の行になければならない * fix: シェーダーの読み込みが完了してからレンダリングを行うように * fix merge failure * fix: ウォーターマークのプリセットがない場合にdividerが2重に表示される問題を修正 * fix: アップローダーダイアログの機能設定でウォーターマークが無効な場合でもデフォルトのプリセットが適用されてしまう問題を修正 * fix lint * Revert "fix: シェーダーの読み込みが完了してからレンダリングを行うように" This reverts commite06f37a7d4. * Revert "fix: glslのバージョン表記は最初の行になければならない" This reverts commitafcc37d886. * Revert "refactor: シェーダーファイルをlazy-loadingできるように" This reverts commita1ab2fa38c. * fix: ウォーターマークのFX定義を分ける * Update packages/frontend/src/components/MkWatermarkEditorDialog.vue * Update packages/frontend/src/components/MkWatermarkEditorDialog.vue * Update packages/frontend/src/components/MkWatermarkEditorDialog.vue --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
This commit is contained in:
@@ -51,7 +51,10 @@ if (props.fileId) {
|
||||
}
|
||||
|
||||
function selectButton(ev: MouseEvent) {
|
||||
selectFile(ev.currentTarget ?? ev.target).then(async (file) => {
|
||||
selectFile({
|
||||
anchorElement: ev.currentTarget ?? ev.target,
|
||||
multiple: false,
|
||||
}).then(async (file) => {
|
||||
if (!file) return;
|
||||
if (props.validate && !await props.validate(file)) return;
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ const dialog = useTemplateRef('dialog');
|
||||
async function cancel() {
|
||||
if (layers.length > 0) {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.ts._imageEffector.discardChangesConfirm,
|
||||
});
|
||||
if (canceled) return;
|
||||
@@ -132,7 +133,7 @@ function onLayerDelete(layer: ImageEffectorLayer) {
|
||||
|
||||
const canvasEl = useTemplateRef('canvasEl');
|
||||
|
||||
let renderer: ImageEffector | null = null;
|
||||
let renderer: ImageEffector<typeof FXS> | null = null;
|
||||
let imageBitmap: ImageBitmap | null = null;
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
@@ -120,7 +120,7 @@ import { formatTimeString } from '@/utility/format-time-string.js';
|
||||
import { Autocomplete } from '@/utility/autocomplete.js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { selectFiles } from '@/utility/drive.js';
|
||||
import { selectFile } from '@/utility/drive.js';
|
||||
import { store } from '@/store.js';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
@@ -437,7 +437,11 @@ function focus() {
|
||||
function chooseFileFrom(ev) {
|
||||
if (props.mock) return;
|
||||
|
||||
selectFiles(ev.currentTarget ?? ev.target, i18n.ts.attachFile).then(files_ => {
|
||||
selectFile({
|
||||
anchorElement: ev.currentTarget ?? ev.target,
|
||||
multiple: true,
|
||||
label: i18n.ts.attachFile,
|
||||
}).then(files_ => {
|
||||
for (const file of files_) {
|
||||
files.value.push(file);
|
||||
}
|
||||
|
||||
@@ -79,8 +79,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</MkModalWindow>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export type UploaderDialogFeatures = {
|
||||
effect?: boolean;
|
||||
watermark?: boolean;
|
||||
crop?: boolean;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineAsyncComponent, markRaw, onMounted, onUnmounted, ref, triggerRef, useTemplateRef, watch } from 'vue';
|
||||
import { computed, markRaw, onMounted, onUnmounted, ref, triggerRef, useTemplateRef, watch } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { genId } from '@/utility/id.js';
|
||||
import { readAndCompressImage } from '@misskey-dev/browser-image-resizer';
|
||||
@@ -91,7 +99,6 @@ import { i18n } from '@/i18n.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import bytes from '@/filters/bytes.js';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import { isWebpSupported } from '@/utility/isWebpSupported.js';
|
||||
import { uploadFile, UploadAbortedError } from '@/utility/drive.js';
|
||||
import * as os from '@/os.js';
|
||||
@@ -131,17 +138,26 @@ const props = withDefaults(defineProps<{
|
||||
files: File[];
|
||||
folderId?: string | null;
|
||||
multiple?: boolean;
|
||||
features?: UploaderDialogFeatures;
|
||||
}>(), {
|
||||
multiple: true,
|
||||
});
|
||||
|
||||
const uploaderFeatures = computed<Required<UploaderDialogFeatures>>(() => {
|
||||
return {
|
||||
effect: props.features?.effect ?? true,
|
||||
watermark: props.features?.watermark ?? true,
|
||||
crop: props.features?.crop ?? true,
|
||||
};
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'done', driveFiles: Misskey.entities.DriveFile[]): void;
|
||||
(ev: 'canceled'): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const items = ref<{
|
||||
type UploaderItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
uploadName?: string;
|
||||
@@ -152,13 +168,15 @@ const items = ref<{
|
||||
uploaded: Misskey.entities.DriveFile | null;
|
||||
uploadFailed: boolean;
|
||||
aborted: boolean;
|
||||
compressionLevel: number;
|
||||
compressionLevel: 0 | 1 | 2 | 3;
|
||||
compressedSize?: number | null;
|
||||
preprocessedFile?: Blob | null;
|
||||
file: File;
|
||||
watermarkPresetId: string | null;
|
||||
abort?: (() => void) | null;
|
||||
}[]>([]);
|
||||
};
|
||||
|
||||
const items = ref<UploaderItem[]>([]);
|
||||
|
||||
const dialog = useTemplateRef('dialog');
|
||||
|
||||
@@ -252,7 +270,7 @@ async function done() {
|
||||
dialog.value?.close();
|
||||
}
|
||||
|
||||
function showMenu(ev: MouseEvent, item: typeof items.value[0]) {
|
||||
function showMenu(ev: MouseEvent, item: UploaderItem) {
|
||||
const menu: MenuItem[] = [];
|
||||
|
||||
menu.push({
|
||||
@@ -272,7 +290,13 @@ function showMenu(ev: MouseEvent, item: typeof items.value[0]) {
|
||||
},
|
||||
});
|
||||
|
||||
if (CROPPING_SUPPORTED_TYPES.includes(item.file.type) && !item.preprocessing && !item.uploading && !item.uploaded) {
|
||||
if (
|
||||
uploaderFeatures.value.crop &&
|
||||
CROPPING_SUPPORTED_TYPES.includes(item.file.type) &&
|
||||
!item.preprocessing &&
|
||||
!item.uploading &&
|
||||
!item.uploaded
|
||||
) {
|
||||
menu.push({
|
||||
icon: 'ti ti-crop',
|
||||
text: i18n.ts.cropImage,
|
||||
@@ -292,7 +316,13 @@ function showMenu(ev: MouseEvent, item: typeof items.value[0]) {
|
||||
});
|
||||
}
|
||||
|
||||
if (IMAGE_EDITING_SUPPORTED_TYPES.includes(item.file.type) && !item.preprocessing && !item.uploading && !item.uploaded) {
|
||||
if (
|
||||
uploaderFeatures.value.effect &&
|
||||
IMAGE_EDITING_SUPPORTED_TYPES.includes(item.file.type) &&
|
||||
!item.preprocessing &&
|
||||
!item.uploading &&
|
||||
!item.uploaded
|
||||
) {
|
||||
menu.push({
|
||||
icon: 'ti ti-sparkles',
|
||||
text: i18n.ts._imageEffector.title + ' (BETA)',
|
||||
@@ -318,7 +348,13 @@ function showMenu(ev: MouseEvent, item: typeof items.value[0]) {
|
||||
});
|
||||
}
|
||||
|
||||
if (WATERMARK_SUPPORTED_TYPES.includes(item.file.type) && !item.preprocessing && !item.uploading && !item.uploaded) {
|
||||
if (
|
||||
uploaderFeatures.value.watermark &&
|
||||
WATERMARK_SUPPORTED_TYPES.includes(item.file.type) &&
|
||||
!item.preprocessing &&
|
||||
!item.uploading &&
|
||||
!item.uploaded
|
||||
) {
|
||||
function changeWatermarkPreset(presetId: string | null) {
|
||||
item.watermarkPresetId = presetId;
|
||||
preprocess(item).then(() => {
|
||||
@@ -338,13 +374,13 @@ function showMenu(ev: MouseEvent, item: typeof items.value[0]) {
|
||||
}, {
|
||||
type: 'divider',
|
||||
}, ...prefer.s.watermarkPresets.map(preset => ({
|
||||
type: 'radioOption',
|
||||
type: 'radioOption' as const,
|
||||
text: preset.name,
|
||||
active: computed(() => item.watermarkPresetId === preset.id),
|
||||
action: () => changeWatermarkPreset(preset.id),
|
||||
})), {
|
||||
type: 'divider',
|
||||
}, {
|
||||
})), ...(prefer.s.watermarkPresets.length > 0 ? [{
|
||||
type: 'divider' as const,
|
||||
}] : []), {
|
||||
type: 'button',
|
||||
icon: 'ti ti-plus',
|
||||
text: i18n.ts.add,
|
||||
@@ -397,8 +433,7 @@ function showMenu(ev: MouseEvent, item: typeof items.value[0]) {
|
||||
text: i18n.ts.high,
|
||||
active: computed(() => item.compressionLevel === 3),
|
||||
action: () => changeCompressionLevel(3),
|
||||
},
|
||||
],
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -590,9 +625,9 @@ function initializeFile(file: File) {
|
||||
uploaded: null,
|
||||
uploadFailed: false,
|
||||
compressionLevel: prefer.s.defaultImageCompressionLevel,
|
||||
watermarkPresetId: prefer.s.defaultWatermarkPresetId,
|
||||
watermarkPresetId: uploaderFeatures.value.watermark ? prefer.s.defaultWatermarkPresetId : null,
|
||||
file: markRaw(file),
|
||||
};
|
||||
} satisfies UploaderItem;
|
||||
items.value.push(item);
|
||||
preprocess(item).then(() => {
|
||||
triggerRef(items);
|
||||
|
||||
@@ -262,10 +262,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, useTemplateRef, watch, onMounted, onUnmounted } from 'vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import type { WatermarkPreset } from '@/utility/watermark.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
@@ -275,11 +275,10 @@ import MkPositionSelector from '@/components/MkPositionSelector.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { selectFile } from '@/utility/drive.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const layer = defineModel<WatermarkPreset['layers'][number]>('layer', { required: true });
|
||||
|
||||
const driveFile = ref();
|
||||
const driveFile = ref<Misskey.entities.DriveFile | null>(null);
|
||||
const driveFileError = ref(false);
|
||||
onMounted(async () => {
|
||||
if (layer.value.type === 'image' && layer.value.imageId != null) {
|
||||
@@ -294,7 +293,15 @@ onMounted(async () => {
|
||||
});
|
||||
|
||||
function chooseFile(ev: MouseEvent) {
|
||||
selectFile(ev.currentTarget ?? ev.target, i18n.ts.selectFile).then((file) => {
|
||||
selectFile({
|
||||
anchorElement: ev.currentTarget ?? ev.target,
|
||||
multiple: false,
|
||||
label: i18n.ts.selectFile,
|
||||
features: {
|
||||
watermark: false,
|
||||
},
|
||||
}).then((file) => {
|
||||
if (layer.value.type !== 'image') return;
|
||||
if (!file.type.startsWith('image')) {
|
||||
os.alert({
|
||||
type: 'warning',
|
||||
|
||||
@@ -124,7 +124,7 @@ function createStripeLayer(): WatermarkPreset['layers'][number] {
|
||||
angle: 0.5,
|
||||
frequency: 10,
|
||||
threshold: 0.1,
|
||||
black: false,
|
||||
color: [1, 1, 1],
|
||||
opacity: 0.75,
|
||||
};
|
||||
}
|
||||
@@ -140,7 +140,7 @@ function createPolkadotLayer(): WatermarkPreset['layers'][number] {
|
||||
majorOpacity: 0.75,
|
||||
minorOpacity: 0.5,
|
||||
minorDivisions: 4,
|
||||
black: false,
|
||||
color: [1, 1, 1],
|
||||
opacity: 0.75,
|
||||
};
|
||||
}
|
||||
@@ -151,7 +151,7 @@ function createCheckerLayer(): WatermarkPreset['layers'][number] {
|
||||
type: 'checker',
|
||||
angle: 0.5,
|
||||
scale: 3,
|
||||
black: false,
|
||||
color: [1, 1, 1],
|
||||
opacity: 0.75,
|
||||
};
|
||||
}
|
||||
@@ -177,6 +177,7 @@ const dialog = useTemplateRef('dialog');
|
||||
|
||||
async function cancel() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'question',
|
||||
text: i18n.ts._watermarkEditor.quitWithoutSaveConfirm,
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
Reference in New Issue
Block a user