diff options
| author | かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> | 2025-06-04 16:22:09 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-06-04 16:22:09 +0900 |
| commit | b43dfa260b1416da153d28bcd46a8bcbce02c18d (patch) | |
| tree | 1e4e08092b9f8870db15d267d20b0dd4c9828f95 /packages/frontend/src/utility | |
| parent | 🎨 (diff) | |
| download | misskey-b43dfa260b1416da153d28bcd46a8bcbce02c18d.tar.gz misskey-b43dfa260b1416da153d28bcd46a8bcbce02c18d.tar.bz2 misskey-b43dfa260b1416da153d28bcd46a8bcbce02c18d.zip | |
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 commit e06f37a7d453ca581858252eae422d8a9e470dc3.
* Revert "fix: glslのバージョン表記は最初の行になければならない"
This reverts commit afcc37d886106c4acd545e4c2922e67f94e1037b.
* Revert "refactor: シェーダーファイルをlazy-loadingできるように"
This reverts commit a1ab2fa38c2b7485c069f9cd089bc7de59416c9d.
* 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>
Diffstat (limited to 'packages/frontend/src/utility')
| -rw-r--r-- | packages/frontend/src/utility/drive.ts | 26 | ||||
| -rw-r--r-- | packages/frontend/src/utility/image-effector/ImageEffector.ts | 14 | ||||
| -rw-r--r-- | packages/frontend/src/utility/watermark.ts | 23 |
3 files changed, 41 insertions, 22 deletions
diff --git a/packages/frontend/src/utility/drive.ts b/packages/frontend/src/utility/drive.ts index 0e10f80145..bc1813f48c 100644 --- a/packages/frontend/src/utility/drive.ts +++ b/packages/frontend/src/utility/drive.ts @@ -16,6 +16,7 @@ import { instance } from '@/instance.js'; import { globalEvents } from '@/events.js'; import { getProxiedImageUrl } from '@/utility/media-proxy.js'; import { genId } from '@/utility/id.js'; +import type { UploaderDialogFeatures } from '@/components/MkUploaderDialog.vue'; type UploadReturnType = { filePromise: Promise<Misskey.entities.DriveFile>; @@ -155,6 +156,7 @@ export function uploadFile(file: File | Blob, options: { export function chooseFileFromPcAndUpload( options: { multiple?: boolean; + features?: UploaderDialogFeatures; folderId?: string | null; } = {}, ): Promise<Misskey.entities.DriveFile[]> { @@ -163,6 +165,7 @@ export function chooseFileFromPcAndUpload( if (files.length === 0) return; os.launchUploader(files, { folderId: options.folderId, + features: options.features, }).then(driveFiles => { res(driveFiles); }); @@ -194,7 +197,7 @@ export function chooseFileFromUrl(): Promise<Misskey.entities.DriveFile> { type: 'url', placeholder: i18n.ts.uploadFromUrlDescription, }).then(({ canceled, result: url }) => { - if (canceled) return; + if (canceled || url == null) return; const marker = genId(); @@ -221,7 +224,7 @@ export function chooseFileFromUrl(): Promise<Misskey.entities.DriveFile> { }); } -function select(anchorElement: HTMLElement | EventTarget | null, label: string | null, multiple: boolean): Promise<Misskey.entities.DriveFile[]> { +function select(anchorElement: HTMLElement | EventTarget | null, label: string | null, multiple: boolean, features?: UploaderDialogFeatures): Promise<Misskey.entities.DriveFile[]> { return new Promise((res, rej) => { os.popupMenu([label ? { text: label, @@ -229,7 +232,7 @@ function select(anchorElement: HTMLElement | EventTarget | null, label: string | } : undefined, { text: i18n.ts.upload, icon: 'ti ti-upload', - action: () => chooseFileFromPcAndUpload({ multiple }).then(files => res(files)), + action: () => chooseFileFromPcAndUpload({ multiple, features }).then(files => res(files)), }, { text: i18n.ts.fromDrive, icon: 'ti ti-cloud', @@ -242,12 +245,19 @@ function select(anchorElement: HTMLElement | EventTarget | null, label: string | }); } -export function selectFile(anchorElement: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile> { - return select(anchorElement, label, false).then(files => files[0]); -} +type SelectFileOptions<M extends boolean> = { + anchorElement: HTMLElement | EventTarget | null; + multiple: M; + label?: string | null; + features?: UploaderDialogFeatures; +}; -export function selectFiles(anchorElement: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile[]> { - return select(anchorElement, label, true); +export async function selectFile< + M extends boolean, + MR extends M extends true ? Misskey.entities.DriveFile[] : Misskey.entities.DriveFile +>(opts: SelectFileOptions<M>): Promise<MR> { + const files = await select(opts.anchorElement, opts.label ?? null, opts.multiple ?? false, opts.features); + return opts.multiple ? (files as MR) : (files[0]! as MR); } export async function createCroppedImageDriveFileFromImageDriveFile(imageDriveFile: Misskey.entities.DriveFile, options: { diff --git a/packages/frontend/src/utility/image-effector/ImageEffector.ts b/packages/frontend/src/utility/image-effector/ImageEffector.ts index fe253017e5..80e3ff65de 100644 --- a/packages/frontend/src/utility/image-effector/ImageEffector.ts +++ b/packages/frontend/src/utility/image-effector/ImageEffector.ts @@ -57,7 +57,7 @@ function getValue<T extends keyof ParamTypeToPrimitive>(params: Record<string, a return params[k]; } -export class ImageEffector { +export class ImageEffector<IEX extends ReadonlyArray<ImageEffectorFx<any, any, any>>> { private gl: WebGL2RenderingContext; private canvas: HTMLCanvasElement | null = null; private renderTextureProgram: WebGLProgram; @@ -70,7 +70,7 @@ export class ImageEffector { private shaderCache: Map<string, WebGLProgram> = new Map(); private perLayerResultTextures: Map<string, WebGLTexture> = new Map(); private perLayerResultFrameBuffers: Map<string, WebGLFramebuffer> = new Map(); - private fxs: ImageEffectorFx[]; + private fxs: [...IEX]; private paramTextures: Map<string, { texture: WebGLTexture; width: number; height: number; }> = new Map(); constructor(options: { @@ -78,7 +78,7 @@ export class ImageEffector { renderWidth: number; renderHeight: number; image: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement; - fxs: ImageEffectorFx[]; + fxs: [...IEX]; }) { this.canvas = options.canvas; this.renderWidth = options.renderWidth; @@ -230,7 +230,7 @@ export class ImageEffector { gl: gl, program: shaderProgram, params: Object.fromEntries( - Object.entries(fx.params).map(([key, param]) => { + Object.entries(fx.params as ImageEffectorFxParamDefs).map(([key, param]) => { return [key, layer.params[key] ?? param.default]; }), ), @@ -238,7 +238,7 @@ export class ImageEffector { width: this.renderWidth, height: this.renderHeight, textures: Object.fromEntries( - Object.entries(fx.params).map(([k, v]) => { + Object.entries(fx.params as ImageEffectorFxParamDefs).map(([k, v]) => { if (v.type !== 'texture') return [k, null]; const param = getValue<typeof v.type>(layer.params, k); if (param == null) return [k, null]; @@ -329,7 +329,7 @@ export class ImageEffector { unused.delete(textureKey); if (this.paramTextures.has(textureKey)) continue; - console.log(`Baking texture of <${textureKey}>...`); + if (_DEV_) console.log(`Baking texture of <${textureKey}>...`); const texture = v.type === 'text' ? await createTextureFromText(this.gl, v.text) : v.type === 'url' ? await createTextureFromUrl(this.gl, v.url) : null; if (texture == null) continue; @@ -339,7 +339,7 @@ export class ImageEffector { } for (const k of unused) { - console.log(`Dispose unused texture <${k}>...`); + if (_DEV_) console.log(`Dispose unused texture <${k}>...`); this.gl.deleteTexture(this.paramTextures.get(k)!.texture); this.paramTextures.delete(k); } diff --git a/packages/frontend/src/utility/watermark.ts b/packages/frontend/src/utility/watermark.ts index 8ee93181a6..f0b38684f0 100644 --- a/packages/frontend/src/utility/watermark.ts +++ b/packages/frontend/src/utility/watermark.ts @@ -3,13 +3,20 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { FX_watermarkPlacement } from './image-effector/fxs/watermarkPlacement.js'; -import { FX_stripe } from './image-effector/fxs/stripe.js'; -import { FX_polkadot } from './image-effector/fxs/polkadot.js'; -import { FX_checker } from './image-effector/fxs/checker.js'; -import type { ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js'; +import { FX_watermarkPlacement } from '@/utility/image-effector/fxs/watermarkPlacement.js'; +import { FX_stripe } from '@/utility/image-effector/fxs/stripe.js'; +import { FX_polkadot } from '@/utility/image-effector/fxs/polkadot.js'; +import { FX_checker } from '@/utility/image-effector/fxs/checker.js'; +import type { ImageEffectorFx, ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js'; import { ImageEffector } from '@/utility/image-effector/ImageEffector.js'; +const WATERMARK_FXS = [ + FX_watermarkPlacement, + FX_stripe, + FX_polkadot, + FX_checker, +] as const satisfies ImageEffectorFx<string, any>[]; + export type WatermarkPreset = { id: string; name: string; @@ -64,7 +71,7 @@ export type WatermarkPreset = { }; export class WatermarkRenderer { - private effector: ImageEffector; + private effector: ImageEffector<typeof WATERMARK_FXS>; private layers: WatermarkPreset['layers'] = []; constructor(options: { @@ -78,7 +85,7 @@ export class WatermarkRenderer { renderWidth: options.renderWidth, renderHeight: options.renderHeight, image: options.image, - fxs: [FX_watermarkPlacement, FX_stripe, FX_polkadot, FX_checker], + fxs: WATERMARK_FXS, }); } @@ -157,6 +164,8 @@ export class WatermarkRenderer { opacity: layer.opacity, }, }; + } else { + throw new Error(`Unknown layer type`); } }); } |