merge: Better beta emoji panel (!1146)

View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1146

Approved-by: dakkar <dakkar@thenautilus.net>
Approved-by: Hazelnoot <acomputerdog@gmail.com>
This commit is contained in:
Hazelnoot 2025-09-25 14:44:36 -04:00
commit 018b3d3dee
3 changed files with 130 additions and 67 deletions

View file

@ -90,6 +90,8 @@ import MkPagingButtons from '@/components/MkPagingButtons.vue';
import { selectFile } from '@/utility/select-file.js';
import { copyGridDataToClipboard, removeDataFromGrid } from '@/components/grid/grid-utils.js';
import { useLoading } from '@/components/hook/useLoading.js';
import { retryOnThrottled } from '@@/js/retry-on-throttled';
import promiseLimit from "promise-limit";
type GridItem = {
checked: boolean;
@ -326,28 +328,39 @@ async function onUpdateButtonClicked() {
return;
}
const action = () => {
return updatedItems.map(item =>
misskeyApi(
'admin/emoji/update',
{
// eslint-disable-next-line
id: item.id!,
name: item.name,
category: emptyStrToNull(item.category),
aliases: emptyStrToEmptyArray(item.aliases),
license: emptyStrToNull(item.license),
isSensitive: item.isSensitive,
localOnly: item.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: item.roleIdsThatCanBeUsedThisEmojiAsReaction.map(it => it.id),
fileId: item.fileId,
})
.then(() => ({ item, success: true, err: undefined }))
.catch(err => ({ item, success: false, err })),
);
const delay = (ms: number) => new Promise(resolve => window.setTimeout(resolve, ms));
type ApiResponse = {
item: any;
success: boolean;
err?: unknown;
};
const result = await os.promiseDialog(Promise.all(action()));
const execute = async (item: any): Promise<ApiResponse> => {
try {
await retryOnThrottled(() => misskeyApi('admin/emoji/update', {
id: item.id,
name: item.name,
category: emptyStrToNull(item.category),
aliases: emptyStrToEmptyArray(item.aliases),
license: emptyStrToNull(item.license),
isSensitive: item.isSensitive,
localOnly: item.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: item.roleIdsThatCanBeUsedThisEmojiAsReaction.map((it: any) => it.id),
fileId: item.fileId,
}));
return { item, success: true };
} catch (error) {
return { item, success: false, err: error };
}
};
const action = async (): Promise<ApiResponse[]> => {
const limit = promiseLimit<ApiResponse>(2);
return await Promise.all(updatedItems.map(async it => limit(() => execute(it))));
};
const result = await os.promiseDialog(action());
const failedItems = result.filter(it => !it.success);
if (failedItems.length > 0) {

View file

@ -75,17 +75,16 @@ SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import * as Misskey from 'misskey-js';
import { onMounted, ref, useCssModule } from 'vue';
import { retryOnThrottled } from '@@/js/retry-on-throttled';
import promiseLimit from 'promise-limit';
import type { RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js';
import { emptyStrToEmptyArray, emptyStrToNull, roleIdsParser } from '@/pages/admin/custom-emojis-manager.impl.js';
import type { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
import type { DroppedFile } from '@/utility/file-drop.js';
import { extractDroppedItems, flattenDroppedFiles } from '@/utility/file-drop.js';
import type { GridSetting } from '@/components/grid/grid.js';
import type { GridRow } from '@/components/grid/row.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import {
emptyStrToEmptyArray,
emptyStrToNull,
roleIdsParser,
} from '@/pages/admin/custom-emojis-manager.impl.js';
import MkGrid from '@/components/grid/MkGrid.vue';
import { i18n } from '@/i18n.js';
import MkSelect from '@/components/MkSelect.vue';
@ -96,7 +95,6 @@ import * as os from '@/os.js';
import { validators } from '@/components/grid/cell-validators.js';
import { chooseFileFromDrive, chooseFileFromPc } from '@/utility/select-file.js';
import { uploadFile } from '@/utility/upload.js';
import { extractDroppedItems, flattenDroppedFiles } from '@/utility/file-drop.js';
import XRegisterLogs from '@/pages/admin/custom-emojis-manager.logs.vue';
import { copyGridDataToClipboard } from '@/components/grid/grid-utils.js';
@ -247,7 +245,56 @@ const registerButtonDisabled = ref<boolean>(false);
const requestLogs = ref<RequestLogItem[]>([]);
const isDragOver = ref<boolean>(false);
async function onRegistryClicked() {
type ApiResponse = {
item: any;
success: boolean;
err?: unknown;
};
const execute = async (item: any, apiEndpoint: string, payload: any): Promise<ApiResponse> => {
try {
await retryOnThrottled(() => misskeyApi(apiEndpoint, payload));
return { item, success: true };
} catch (err) {
return { item, success: false, err };
}
};
const importEmojis = async (targets: any[]): Promise<void> => {
const confirm = await os.confirm({
type: 'info',
title: i18n.ts._customEmojisManager._remote.confirmImportEmojisTitle,
text: i18n.tsx._customEmojisManager._remote.confirmImportEmojisDescription({ count: targets.length }),
});
if (confirm.canceled) {
return;
}
async function action(): Promise<ApiResponse[]> {
const limit = promiseLimit<ApiResponse>(3);
return await Promise.all(targets.map(item => limit(() => execute(item, 'admin/emoji/copy', { emojiId: item.id }))));
}
const result = await os.promiseDialog(action());
const failedItems = result.filter(it => !it.success);
if (failedItems.length > 0) {
await os.alert({
type: 'error',
title: i18n.ts.somethingHappened,
text: i18n.ts._customEmojisManager._gridCommon.alertEmojisRegisterFailedDescription,
});
}
requestLogs.value = result.map(it => ({
failed: !it.success,
url: it.item.url,
name: it.item.name,
error: it.err ? JSON.stringify(it.err) : undefined,
}));
};
const onRegistryClicked = async (): Promise<void> => {
const dialogSelection = await os.confirm({
type: 'info',
text: i18n.tsx._customEmojisManager._local._register.confirmRegisterEmojisDescription({ count: MAXIMUM_EMOJI_REGISTER_COUNT }),
@ -257,29 +304,24 @@ async function onRegistryClicked() {
return;
}
const items = gridItems.value;
const upload = () => {
return items.slice(0, MAXIMUM_EMOJI_REGISTER_COUNT)
.map(item =>
misskeyApi(
'admin/emoji/add', {
name: item.name,
category: emptyStrToNull(item.category),
aliases: emptyStrToEmptyArray(item.aliases),
license: emptyStrToNull(item.license),
isSensitive: item.isSensitive,
localOnly: item.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: item.roleIdsThatCanBeUsedThisEmojiAsReaction.map(it => it.id),
fileId: item.fileId!,
})
.then(() => ({ item, success: true, err: undefined }))
.catch(err => ({ item, success: false, err })),
);
};
const items = gridItems.value.slice(0, MAXIMUM_EMOJI_REGISTER_COUNT);
const result = await os.promiseDialog(Promise.all(upload()));
async function action(): Promise<ApiResponse[]> {
const limit = promiseLimit<ApiResponse>(2);
return await Promise.all(items.map(item => limit(() => execute(item, 'admin/emoji/add', {
name: item.name,
category: emptyStrToNull(item.category),
aliases: emptyStrToEmptyArray(item.aliases),
license: emptyStrToNull(item.license),
isSensitive: item.isSensitive,
localOnly: item.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: item.roleIdsThatCanBeUsedThisEmojiAsReaction.map((it: any) => it.id),
fileId: item.fileId!,
}))));
}
const result = await os.promiseDialog(action());
const failedItems = result.filter(it => !it.success);
if (failedItems.length > 0) {
await os.alert({
type: 'error',
@ -295,10 +337,10 @@ async function onRegistryClicked() {
error: it.err ? JSON.stringify(it.err) : undefined,
}));
//
// Remove successfully registered items from the list
const successItems = result.filter(it => it.success).map(it => it.item);
gridItems.value = gridItems.value.filter(it => !successItems.includes(it));
}
};
async function onClearClicked() {
const result = await os.confirm({

View file

@ -142,6 +142,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts">
import { computed, onMounted, ref, useCssModule } from 'vue';
import * as Misskey from 'misskey-js';
import { retryOnThrottled } from '@@/js/retry-on-throttled';
import type { GridSortOrderKey, RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js';
import type { GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
import type { GridSetting } from '@/components/grid/grid.js';
@ -160,6 +161,7 @@ import { deviceKind } from '@/utility/device-kind.js';
import MkPagingButtons from '@/components/MkPagingButtons.vue';
import MkSortOrderEditor from '@/components/MkSortOrderEditor.vue';
import { useLoading } from '@/components/hook/useLoading.js';
import promiseLimit from "promise-limit";
type GridItem = {
checked: boolean;
@ -310,7 +312,24 @@ function onGridCellValueChange(event: GridCellValueChangeEvent) {
}
}
async function importEmojis(targets: GridItem[]) {
type ApiResponse = {
item: any;
success: boolean;
err?: unknown;
};
const execute = async (item: any): Promise<ApiResponse> => {
try {
await retryOnThrottled(() => misskeyApi('admin/emoji/copy', {
emojiId: item.id,
}));
return { item, success: true };
} catch (err) {
return { item, success: false, err };
}
};
const importEmojis = async (targets: any[]): Promise<void> => {
const confirm = await os.confirm({
type: 'info',
title: i18n.ts._customEmojisManager._remote.confirmImportEmojisTitle,
@ -321,21 +340,10 @@ async function importEmojis(targets: GridItem[]) {
return;
}
const result = await os.promiseDialog(
Promise.all(
targets.map(item =>
misskeyApi(
'admin/emoji/copy',
{
emojiId: item.id!,
})
.then(() => ({ item, success: true, err: undefined }))
.catch(err => ({ item, success: false, err })),
),
),
);
const failedItems = result.filter(it => !it.success);
const limit = promiseLimit<ApiResponse>(2);
const results = await Promise.all(targets.map(it => limit(() => execute(it))));
const failedItems = results.filter(it => !it.success);
if (failedItems.length > 0) {
await os.alert({
type: 'error',
@ -344,7 +352,7 @@ async function importEmojis(targets: GridItem[]) {
});
}
requestLogs.value = result.map(it => ({
requestLogs.value = results.map(it => ({
failed: !it.success,
url: it.item.url,
name: it.item.name,
@ -352,7 +360,7 @@ async function importEmojis(targets: GridItem[]) {
}));
await refreshCustomEmojis();
}
};
async function refreshCustomEmojis() {
const query: Misskey.entities.V2AdminEmojiListRequest['query'] = {