From 22227fa641f80240ab5f134007067d1b1048095a Mon Sep 17 00:00:00 2001 From: tamaina Date: Wed, 5 Jul 2023 12:15:48 +0900 Subject: [PATCH 1/9] perf(backend): Use addBulk to add deliver queues (#11114) --- packages/backend/src/core/QueueService.ts | 36 +++++++++++++++++-- .../activitypub/ApDeliverManagerService.ts | 19 +++++----- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 5b7359074e..48ff00c8ce 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -8,7 +8,7 @@ import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js'; import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, RelationshipQueue, SystemQueue, WebhookDeliverQueue } from './QueueModule.js'; -import type { DbJobData, RelationshipJobData, ThinUser } from '../queue/types.js'; +import type { DbJobData, DeliverJobData, RelationshipJobData, ThinUser } from '../queue/types.js'; import type httpSignature from '@peertube/http-signature'; import type * as Bull from 'bullmq'; @@ -69,7 +69,7 @@ export class QueueService { if (content == null) return null; if (to == null) return null; - const data = { + const data: DeliverJobData = { user: { id: user.id, }, @@ -88,6 +88,38 @@ export class QueueService { }); } + /** + * ApDeliverManager-DeliverManager.execute()からinboxesを突っ込んでaddBulkしたい + * @param user `{ id: string; }` この関数ではThinUserに変換しないので前もって変換してください + * @param content IActivity | null + * @param inboxes `Map` / key: to (inbox url), value: isSharedInbox (whether it is sharedInbox) + * @returns void + */ + @bindThis + public async deliverMany(user: ThinUser, content: IActivity | null, inboxes: Map) { + const opts = { + attempts: this.config.deliverJobMaxAttempts ?? 12, + backoff: { + type: 'custom', + }, + removeOnComplete: true, + removeOnFail: true, + }; + + await this.deliverQueue.addBulk(Array.from(inboxes.entries()).map(d => ({ + name: d[0], + data: { + user, + content, + to: d[0], + isSharedInbox: d[1], + } as DeliverJobData, + opts, + }))); + + return; + } + @bindThis public inbox(activity: IActivity, signature: httpSignature.IParsedSignature) { const data = { diff --git a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts index 62a2a33a19..66e7761187 100644 --- a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts +++ b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts @@ -7,6 +7,7 @@ import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js'; import { QueueService } from '@/core/QueueService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; +import { ThinUser } from '@/queue/types.js'; interface IRecipe { type: string; @@ -94,7 +95,7 @@ export class ApDeliverManagerService { } class DeliverManager { - private actor: { id: User['id']; host: null; }; + private actor: ThinUser; private activity: any; private recipes: IRecipe[] = []; @@ -111,7 +112,13 @@ class DeliverManager { actor: { id: User['id']; host: null; }, activity: any, ) { - this.actor = actor; + // 型で弾いてはいるが一応ローカルユーザーかチェック + if (actor.host != null) throw new Error('actor.host must be null'); + + // パフォーマンス向上のためキューに突っ込むのはidのみに絞る + this.actor = { + id: actor.id, + }; this.activity = activity; } @@ -155,9 +162,8 @@ class DeliverManager { */ @bindThis public async execute() { - if (!this.userEntityService.isLocalUser(this.actor)) return; - // The value flags whether it is shared or not. + // key: inbox URL, value: whether it is sharedInbox const inboxes = new Map(); /* @@ -201,9 +207,6 @@ class DeliverManager { .forEach(recipe => inboxes.set(recipe.to.inbox!, false)); // deliver - for (const inbox of inboxes) { - // inbox[0]: inbox, inbox[1]: whether it is sharedInbox - this.queueService.deliver(this.actor, this.activity, inbox[0], inbox[1]); - } + this.queueService.deliverMany(this.actor, this.activity, inboxes); } } From 8f94b367322f805bdc91354510cdc3c82e438dca Mon Sep 17 00:00:00 2001 From: "Umisyo(Souta Kusunoki)" <44742896+Umisyo@users.noreply.github.com> Date: Wed, 5 Jul 2023 12:17:52 +0900 Subject: [PATCH 2/9] =?UTF-8?q?refactor:=20ApDeliverManagerService.ts?= =?UTF-8?q?=E3=81=AE=E5=9E=8B=E3=81=A8JSDoc=E3=82=92=E9=81=A9=E5=88=87?= =?UTF-8?q?=E3=81=AB=E7=BD=AE=E3=81=8D=E6=8F=9B=E3=81=88=20(#11096)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: ApDeliverManagerService.ts のanyを適切な型に置き換え Signed-off-by: Umisyo * fix: quote to single quote Signed-off-by: Umisyo * refactor: JSDocを実態に合わせて修正 Signed-off-by: Umisyo * fix: activityのnullを許容するよう変更 Signed-off-by: Umisyo --------- Signed-off-by: Umisyo Co-authored-by: tamaina --- .../activitypub/ApDeliverManagerService.ts | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts index 66e7761187..6e910eb538 100644 --- a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts +++ b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts @@ -7,6 +7,7 @@ import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js'; import { QueueService } from '@/core/QueueService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; +import type { IActivity } from '@/core/activitypub/type.js'; import { ThinUser } from '@/queue/types.js'; interface IRecipe { @@ -22,10 +23,10 @@ interface IDirectRecipe extends IRecipe { to: RemoteUser; } -const isFollowers = (recipe: any): recipe is IFollowersRecipe => +const isFollowers = (recipe: IRecipe): recipe is IFollowersRecipe => recipe.type === 'Followers'; -const isDirect = (recipe: any): recipe is IDirectRecipe => +const isDirect = (recipe: IRecipe): recipe is IDirectRecipe => recipe.type === 'Direct'; @Injectable() @@ -47,11 +48,11 @@ export class ApDeliverManagerService { /** * Deliver activity to followers + * @param actor * @param activity Activity - * @param from Followee */ @bindThis - public async deliverToFollowers(actor: { id: LocalUser['id']; host: null; }, activity: any) { + public async deliverToFollowers(actor: { id: LocalUser['id']; host: null; }, activity: IActivity) { const manager = new DeliverManager( this.userEntityService, this.followingsRepository, @@ -65,11 +66,12 @@ export class ApDeliverManagerService { /** * Deliver activity to user + * @param actor * @param activity Activity * @param to Target user */ @bindThis - public async deliverToUser(actor: { id: LocalUser['id']; host: null; }, activity: any, to: RemoteUser) { + public async deliverToUser(actor: { id: LocalUser['id']; host: null; }, activity: IActivity, to: RemoteUser) { const manager = new DeliverManager( this.userEntityService, this.followingsRepository, @@ -82,7 +84,7 @@ export class ApDeliverManagerService { } @bindThis - public createDeliverManager(actor: { id: User['id']; host: null; }, activity: any) { + public createDeliverManager(actor: { id: User['id']; host: null; }, activity: IActivity | null) { return new DeliverManager( this.userEntityService, this.followingsRepository, @@ -96,11 +98,14 @@ export class ApDeliverManagerService { class DeliverManager { private actor: ThinUser; - private activity: any; + private activity: IActivity | null; private recipes: IRecipe[] = []; /** * Constructor + * @param userEntityService + * @param followingsRepository + * @param queueService * @param actor Actor * @param activity Activity to deliver */ @@ -110,7 +115,7 @@ class DeliverManager { private queueService: QueueService, actor: { id: User['id']; host: null; }, - activity: any, + activity: IActivity | null, ) { // 型で弾いてはいるが一応ローカルユーザーかチェック if (actor.host != null) throw new Error('actor.host must be null'); From 1ab9f096c36da6fcb552a289cdf6e658b434445f Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 5 Jul 2023 13:04:27 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat(frontend):=20deck=20UI=E3=81=AE?= =?UTF-8?q?=E3=82=AB=E3=83=A9=E3=83=A0=E3=81=8B=E3=82=89=E3=82=A2=E3=83=B3?= =?UTF-8?q?=E3=83=86=E3=83=8A=E3=80=81=E3=83=AA=E3=82=B9=E3=83=88=E3=81=AE?= =?UTF-8?q?=E7=B7=A8=E9=9B=86=E7=94=BB=E9=9D=A2=E3=82=92=E9=96=8B=E3=81=91?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(#11104)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add edit antenna button onto deck column * feat: add edit list button onto deck column * docs(changelog): add deck UIのカラムのメニューからアンテナとリストの編集画面を開けるようになりました --- CHANGELOG.md | 1 + locales/index.d.ts | 2 ++ locales/ja-JP.yml | 2 ++ .../frontend/src/ui/deck/antenna-column.vue | 21 ++++++++++++++----- packages/frontend/src/ui/deck/list-column.vue | 21 ++++++++++++++----- 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8fd80063d..c80c21d048 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ ### Client - Fix: サーバーメトリクスが90度傾いている - Fix: sparkle内にリンクを入れるとクリック不能になる問題の修正 +- deck UIのカラムのメニューからアンテナとリストの編集画面を開けるようになりました ### Server - JSON.parse の回数を削減することで、ストリーミングのパフォーマンスを向上しました diff --git a/locales/index.d.ts b/locales/index.d.ts index af6b803278..dea00d783f 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -139,8 +139,10 @@ export interface Locale { "suspendConfirm": string; "unsuspendConfirm": string; "selectList": string; + "editList": string; "selectChannel": string; "selectAntenna": string; + "editAntenna": string; "selectWidget": string; "editWidgets": string; "editWidgetsExit": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index e7202bfbb5..d9d227a0b6 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -136,8 +136,10 @@ unblockConfirm: "ブロック解除しますか?" suspendConfirm: "凍結しますか?" unsuspendConfirm: "解凍しますか?" selectList: "リストを選択" +editList: "リストを編集" selectChannel: "チャンネルを選択" selectAntenna: "アンテナを選択" +editAntenna: "アンテナを編集" selectWidget: "ウィジェットを選択" editWidgets: "ウィジェットを編集" editWidgetsExit: "編集を終了" diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue index d21a9cc580..a1ca32724f 100644 --- a/packages/frontend/src/ui/deck/antenna-column.vue +++ b/packages/frontend/src/ui/deck/antenna-column.vue @@ -44,11 +44,22 @@ async function setAntenna() { }); } -const menu = [{ - icon: 'ti ti-pencil', - text: i18n.ts.selectAntenna, - action: setAntenna, -}]; +function editAntenna() { + os.pageWindow('my/antennas/' + props.column.antennaId); +} + +const menu = [ + { + icon: 'ti ti-pencil', + text: i18n.ts.selectAntenna, + action: setAntenna, + }, + { + icon: 'ti ti-settings', + text: i18n.ts.editAntenna, + action: editAntenna, + }, +]; /* function focus() { diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue index f36dc6151c..3d6256c4fd 100644 --- a/packages/frontend/src/ui/deck/list-column.vue +++ b/packages/frontend/src/ui/deck/list-column.vue @@ -42,9 +42,20 @@ async function setList() { }); } -const menu = [{ - icon: 'ti ti-pencil', - text: i18n.ts.selectList, - action: setList, -}]; +function editList() { + os.pageWindow('my/lists/' + props.column.listId); +} + +const menu = [ + { + icon: 'ti ti-pencil', + text: i18n.ts.selectList, + action: setList, + }, + { + icon: 'ti ti-settings', + text: i18n.ts.editList, + action: editList, + }, +]; From ac4245dce1f2b957066ddc3cf10a1444fece7691 Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Wed, 5 Jul 2023 06:54:40 +0200 Subject: [PATCH 4/9] feat(frontend): allow cropping images on drive (#11092) * feat(frontend): allow cropping images on drive * nanka iroiro * folder --------- Co-authored-by: tamaina --- .../src/components/MkCropperDialog.vue | 23 ++++++++++++------- .../frontend/src/components/MkDrive.file.vue | 5 ++-- packages/frontend/src/components/MkDrive.vue | 1 + .../frontend/src/components/MkPostForm.vue | 8 +++++-- .../src/components/MkPostFormAttaches.vue | 21 +++++++++++++---- packages/frontend/src/os.ts | 2 ++ .../src/scripts/get-drive-file-menu.ts | 13 +++++++++-- 7 files changed, 55 insertions(+), 18 deletions(-) diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue index 82363499b7..b2d60d36c4 100644 --- a/packages/frontend/src/components/MkCropperDialog.vue +++ b/packages/frontend/src/components/MkCropperDialog.vue @@ -47,6 +47,7 @@ const emit = defineEmits<{ const props = defineProps<{ file: misskey.entities.DriveFile; aspectRatio: number; + uploadFolder?: string | null; }>(); const imgUrl = getProxiedImageUrl(props.file.url, undefined, true); @@ -58,11 +59,17 @@ let loading = $ref(true); const ok = async () => { const promise = new Promise(async (res) => { const croppedCanvas = await cropper?.getCropperSelection()?.$toCanvas(); - croppedCanvas.toBlob(blob => { + croppedCanvas?.toBlob(blob => { + if (!blob) return; const formData = new FormData(); formData.append('file', blob); - formData.append('i', $i.token); - if (defaultStore.state.uploadFolder) { + formData.append('name', `cropped_${props.file.name}`); + formData.append('isSensitive', props.file.isSensitive ? 'true' : 'false'); + formData.append('comment', props.file.comment ?? 'null'); + formData.append('i', $i!.token); + if (props.uploadFolder || props.uploadFolder === null) { + formData.append('folderId', props.uploadFolder ?? 'null'); + } else if (defaultStore.state.uploadFolder) { formData.append('folderId', defaultStore.state.uploadFolder); } @@ -82,12 +89,12 @@ const ok = async () => { const f = await promise; emit('ok', f); - dialogEl.close(); + dialogEl!.close(); }; const cancel = () => { emit('cancel'); - dialogEl.close(); + dialogEl!.close(); }; const onImageLoad = () => { @@ -100,7 +107,7 @@ const onImageLoad = () => { }; onMounted(() => { - cropper = new Cropper(imgEl, { + cropper = new Cropper(imgEl!, { }); const computedStyle = getComputedStyle(document.documentElement); @@ -112,13 +119,13 @@ onMounted(() => { selection.outlined = true; window.setTimeout(() => { - cropper.getCropperImage()!.$center('contain'); + cropper!.getCropperImage()!.$center('contain'); selection.$center(); }, 100); // モーダルオープンアニメーションが終わったあとで再度調整 window.setTimeout(() => { - cropper.getCropperImage()!.$center('contain'); + cropper!.getCropperImage()!.$center('contain'); selection.$center(); }, 500); }); diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue index f0641161be..3a75f8293f 100644 --- a/packages/frontend/src/components/MkDrive.file.vue +++ b/packages/frontend/src/components/MkDrive.file.vue @@ -44,6 +44,7 @@ import { getDriveFileMenu } from '@/scripts/get-drive-file-menu'; const props = withDefaults(defineProps<{ file: Misskey.entities.DriveFile; + folder: Misskey.entities.DriveFolder | null; isSelected?: boolean; selectMode?: boolean; }>(), { @@ -65,12 +66,12 @@ function onClick(ev: MouseEvent) { if (props.selectMode) { emit('chosen', props.file); } else { - os.popupMenu(getDriveFileMenu(props.file), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined); + os.popupMenu(getDriveFileMenu(props.file, props.folder), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined); } } function onContextmenu(ev: MouseEvent) { - os.contextMenu(getDriveFileMenu(props.file), ev); + os.contextMenu(getDriveFileMenu(props.file, props.folder), ev); } function onDragstart(ev: DragEvent) { diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue index 52aef450d9..19508fe4de 100644 --- a/packages/frontend/src/components/MkDrive.vue +++ b/packages/frontend/src/components/MkDrive.vue @@ -65,6 +65,7 @@ v-anim="i" :class="$style.file" :file="file" + :folder="folder" :selectMode="select === 'file'" :isSelected="selectedFiles.some(x => x.id === file.id)" @chosen="chooseFile" diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 5c65569683..5b37a117de 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -66,7 +66,7 @@
{{ maxTextLength - textLength }}
- +
@@ -410,7 +410,11 @@ function updateFileName(file, name) { files[files.findIndex(x => x.id === file.id)].name = name; } -function upload(file: File, name?: string) { +function replaceFile(file: misskey.entities.DriveFile, newFile: misskey.entities.DriveFile): void { + files[files.findIndex(x => x.id === file.id)] = newFile; +} + +function upload(file: File, name?: string): void { uploadFile(file, defaultStore.state.uploadFolder, name).then(res => { files.push(res); }); diff --git a/packages/frontend/src/components/MkPostFormAttaches.vue b/packages/frontend/src/components/MkPostFormAttaches.vue index 18fa142ebc..c50d025ab3 100644 --- a/packages/frontend/src/components/MkPostFormAttaches.vue +++ b/packages/frontend/src/components/MkPostFormAttaches.vue @@ -16,6 +16,7 @@