From 622bd911f3b6dfdef3345275d7efebfcc8c3c7c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=B7=E3=81=93=E3=82=8B=E3=81=B5?= <86549420+puk06@users.noreply.github.com> Date: Tue, 1 Sep 2026 21:50:22 +0900 Subject: [PATCH 1/2] feat(avatars): add booth id support to temp avatar --- .../Data/Localization/en-US.json | 4 ++ .../Data/Localization/es-ES.json | 4 ++ .../Data/Localization/fr-FR.json | 4 ++ .../Data/Localization/ja-JP.json | 4 ++ .../Data/Localization/ko-KR.json | 4 ++ .../Data/Localization/zh-CN.json | 4 ++ .../Data/Localization/zh-TW.json | 4 ++ .../Localization/LocalizationKeys.g.cs | 4 ++ .../Models/Items/TempAvatar.cs | 12 +++++- .../Models/System/TempAvatarSearchIndex.cs | 14 ++++++- .../Services/System/BoothService.cs | 3 +- .../Repositories/TempAvatarRepository.cs | 18 ++++++++- AvatarExplorer.Core/Utils/BoothUtils.cs | 9 +++++ .../Factories/NavigationItemFactory.cs | 28 +++++++++----- .../Models/ContextMenu/ActionKey.cs | 1 + .../ContextMenu/ContextMenuHandlerService.cs | 38 +++++++++++++++++++ .../ViewControl/ContextMenuCreator.cs | 1 + .../Overlays/SelectAvatarsViewModel.cs | 32 +++++++++++++++- .../Views/Overlays/TextDialog.axaml | 13 +++---- docs/04-avatar-management.md | 21 ++++++++++ docs/05-booth-integration.md | 28 ++++++++++++++ docs/09-search.md | 1 + 22 files changed, 227 insertions(+), 24 deletions(-) create mode 100644 AvatarExplorer.Core/Utils/BoothUtils.cs diff --git a/AvatarExplorer.Core/Data/Localization/en-US.json b/AvatarExplorer.Core/Data/Localization/en-US.json index 23236781..ed51d784 100644 --- a/AvatarExplorer.Core/Data/Localization/en-US.json +++ b/AvatarExplorer.Core/Data/Localization/en-US.json @@ -72,6 +72,7 @@ "Dialog.Confirmation.RenameTag": "Rename tag \"{0}\" to \"{1}\"?", "Dialog.Confirmation.RemoveTag": "Remove tag \"{0}\"?\n※Removing will delete this tag from all items.", "Dialog.Confirmation.RenameTagAlreadyExist": "Tag \"{0}\" already exists. If you continue, it will be recognized as the same tag. Do you want to continue?", + "Dialog.Confirmation.SetBoothIdForTempAvatar": "Set Booth ID for this temporary avatar?\n\n※Setting a Booth ID enables linking when adding items via ID search or from Booth.", "Dialog.Confirmation.EditCommonAvatars.ReplaceAvatarsToGroup": "Replace the supported avatars of items containing avatars in the common avatar group with the common avatar group?", "Dialog.Confirmation.EditCommonAvatars.ReplaceGroupToAvatars": "Replace the supported avatars of items that have the common avatar group set as supported avatars with individual avatars?", @@ -87,6 +88,7 @@ "Dialog.Title.NewTempAvatarName": "New Temporary Avatar Name", "Dialog.Title.NewCustomCategoryName": "New Custom Category Name", "Dialog.Title.AddUrl": "Enter Download URL", + "Dialog.Title.SetBoothIdForTempAvatar": "Set Booth ID for Temporary Avatar (link accepted)", "Dialog.SelectSaveFolderPath": "Please select the folder to save to", "Dialog.SelectFolderPath": "Please select a folder", @@ -160,6 +162,7 @@ "ContextMenu.ItemFile.OpenPdfViewer": "Open PDF Viewer", "ContextMenu.BulkImportPreset.RemovePreset": "Remove Preset", "ContextMenu.TempAvatar.EditTempAvatarName": "Edit Temporary Avatar Name", + "ContextMenu.TempAvatar.EditBoothId": "Edit Booth ID", "ContextMenu.TempAvatar.ResolveTempAvatar": "Link to Permanent Item", "ContextMenu.TempAvatar.RemoveTempAvatar": "Remove Temporary Avatar", "ContextMenu.ItemCategory.EditCustomCategoryName": "Edit Custom Category Name", @@ -215,6 +218,7 @@ "Warning.RunningInAdministratorMode": "In administrator mode, some operations like drag & drop may not be available.", "Warning.FetchAllItemThumbnailsCancelled": "Bulk thumbnail fetch was cancelled. Success: {0} / Failed: {1} / Total: {2}", "Warning.FetchAllVariationHashesCancelled": "Bulk hash fetch was cancelled. Success: {0} / Failed: {1} / Total: {2}", + "Warning.InvalidBoothId": "Invalid Booth ID. The Booth ID has been cleared.", "Error.Default": "Error", "Error.Nothing": "No items found!", diff --git a/AvatarExplorer.Core/Data/Localization/es-ES.json b/AvatarExplorer.Core/Data/Localization/es-ES.json index 36fa6f4f..12e18cad 100644 --- a/AvatarExplorer.Core/Data/Localization/es-ES.json +++ b/AvatarExplorer.Core/Data/Localization/es-ES.json @@ -72,6 +72,7 @@ "Dialog.Confirmation.RenameTag": "¿Renombrar la etiqueta «{0}» a «{1}»?", "Dialog.Confirmation.RemoveTag": "¿Eliminar la etiqueta «{0}»?\n※Al eliminar, se eliminará esta etiqueta de todos los elementos.", "Dialog.Confirmation.RenameTagAlreadyExist": "La etiqueta \"{0}\" ya existe. Si continúas, se reconocerá como la misma etiqueta. ¿Deseas continuar?", + "Dialog.Confirmation.SetBoothIdForTempAvatar": "¿Establecer Booth ID para este avatar temporal?\n\n※Establecer un Booth ID permite la vinculación al agregar elementos mediante búsqueda de ID o desde Booth.", "Dialog.Confirmation.EditCommonAvatars.ReplaceAvatarsToGroup": "¿Reemplazar los avatares compatibles de los elementos que contienen avatares en el grupo de avatares comunes por el grupo de avatares comunes?", "Dialog.Confirmation.EditCommonAvatars.ReplaceGroupToAvatars": "¿Reemplazar los avatares compatibles de los elementos que tienen el grupo de avatares comunes configurado como avatares compatibles por avatares individuales?", @@ -87,6 +88,7 @@ "Dialog.Title.NewTempAvatarName": "Nuevo nombre de avatar temporal", "Dialog.Title.NewCustomCategoryName": "Nuevo nombre de categoría personalizada", "Dialog.Title.AddUrl": "Introducir URL de descarga", + "Dialog.Title.SetBoothIdForTempAvatar": "Establecer Booth ID para Avatar Temporal (también acepta enlace)", "Dialog.SelectSaveFolderPath": "Selecciona la carpeta donde guardar", "Dialog.SelectFolderPath": "Selecciona una carpeta", @@ -160,6 +162,7 @@ "ContextMenu.ItemFile.OpenPdfViewer": "Abrir con PDF Viewer", "ContextMenu.BulkImportPreset.RemovePreset": "Eliminar ajuste preestablecido", "ContextMenu.TempAvatar.EditTempAvatarName": "Editar nombre de avatar temporal", + "ContextMenu.TempAvatar.EditBoothId": "Editar Booth ID", "ContextMenu.TempAvatar.ResolveTempAvatar": "Vincular al elemento permanente", "ContextMenu.TempAvatar.RemoveTempAvatar": "Eliminar avatar temporal", "ContextMenu.ItemCategory.EditCustomCategoryName": "Editar nombre de categoría personalizada", @@ -215,6 +218,7 @@ "Warning.RunningInAdministratorMode": "En modo administrador, algunas operaciones como arrastrar y soltar pueden no estar disponibles.", "Warning.FetchAllItemThumbnailsCancelled": "La obtención masiva de miniaturas se canceló. Correctos: {0} / Fallidos: {1} / Total: {2}", "Warning.FetchAllVariationHashesCancelled": "La obtención masiva de hashes se canceló. Correctos: {0} / Fallidos: {1} / Total: {2}", + "Warning.InvalidBoothId": "Booth ID no válido. Se ha borrado el Booth ID.", "Error.Default": "Error", "Error.Nothing": "¡No se encontraron elementos!", diff --git a/AvatarExplorer.Core/Data/Localization/fr-FR.json b/AvatarExplorer.Core/Data/Localization/fr-FR.json index 2a45c3ff..2c3fb141 100644 --- a/AvatarExplorer.Core/Data/Localization/fr-FR.json +++ b/AvatarExplorer.Core/Data/Localization/fr-FR.json @@ -72,6 +72,7 @@ "Dialog.Confirmation.RenameTag": "Renommer le tag « {0} » en « {1} » ?", "Dialog.Confirmation.Remove": "Supprimer le tag « {0} » ?\n※La suppression entraînera la suppression de ce tag de tous les éléments.", "Dialog.Confirmation.RemoveTag": "Supprimer le tag « {0} » ?\n※La suppression entraînera la suppression de ce tag de tous les éléments.", + "Dialog.Confirmation.SetBoothIdForTempAvatar": "Définir l'ID Booth pour cet avatar temporaire ?\n\n※Définir un ID Booth permet la liaison lors de l'ajout d'éléments par recherche ID ou depuis Booth.", "Dialog.Confirmation.EditCommonAvatars.ReplaceAvatarsToGroup": "Remplacer les avatars pris en charge des éléments contenant des avatars du groupe d'avatars communs par le groupe d'avatars communs ?", "Dialog.Confirmation.EditCommonAvatars.ReplaceGroupToAvatars": "Remplacer les avatars pris en charge des éléments dont les avatars pris en charge sont définis sur le groupe d'avatars communs par des avatars individuels ?", @@ -87,6 +88,7 @@ "Dialog.Title.NewTempAvatarName": "Nouveau nom d'avatar temporaire", "Dialog.Title.NewCustomCategoryName": "Nouveau nom de catégorie personnalisée", "Dialog.Title.AddUrl": "Entrez l'URL de téléchargement", + "Dialog.Title.SetBoothIdForTempAvatar": "Définir l'ID Booth pour l'avatar temporaire (lien accepté)", "Dialog.SelectSaveFolderPath": "Veuillez sélectionner le dossier d'enregistrement", "Dialog.SelectFolderPath": "Veuillez sélectionner un dossier", @@ -160,6 +162,7 @@ "ContextMenu.ItemFile.OpenPdfViewer": "Ouvrir avec PDF Viewer", "ContextMenu.BulkImportPreset.RemovePreset": "Supprimer le préréglage", "ContextMenu.TempAvatar.EditTempAvatarName": "Modifier le nom de l'avatar temporaire", + "ContextMenu.TempAvatar.EditBoothId": "Modifier l'ID Booth", "ContextMenu.TempAvatar.ResolveTempAvatar": "Lier à l'élément permanent", "ContextMenu.TempAvatar.RemoveTempAvatar": "Supprimer l'avatar temporaire", "ContextMenu.ItemCategory.EditCustomCategoryName": "Modifier le nom de la catégorie personnalisée", @@ -215,6 +218,7 @@ "Warning.RunningInAdministratorMode": "En mode administrateur, certaines opérations comme le glisser-déposer peuvent ne pas être disponibles.", "Warning.FetchAllItemThumbnailsCancelled": "La récupération groupée des vignettes a été annulée. Réussies : {0} / Échouées : {1} / Total : {2}", "Warning.FetchAllVariationHashesCancelled": "La récupération groupée des hachages a été annulée. Réussies : {0} / Échouées : {1} / Total : {2}", + "Warning.InvalidBoothId": "ID Booth invalide. L'ID Booth a été effacé.", "Error.Default": "Erreur", "Error.Nothing": "Aucun élément trouvé !", diff --git a/AvatarExplorer.Core/Data/Localization/ja-JP.json b/AvatarExplorer.Core/Data/Localization/ja-JP.json index 1a9636a5..8475618c 100644 --- a/AvatarExplorer.Core/Data/Localization/ja-JP.json +++ b/AvatarExplorer.Core/Data/Localization/ja-JP.json @@ -72,6 +72,7 @@ "Dialog.Confirmation.RenameTag": "タグ「{0}」を「{1}」に変更しますか?", "Dialog.Confirmation.RemoveTag": "タグ「{0}」を削除しますか?\n※削除すると、全てのアイテムからこのタグが削除されます。", "Dialog.Confirmation.RenameTagAlreadyExist": "タグ「{0}」は既に存在します。続行すると、同じタグとして認識されるようになります。続行しますか?", + "Dialog.Confirmation.SetBoothIdForTempAvatar": "仮アバターにBooth IDを設定しますか?\n\n※Booth IDを設定すると、ID検索や該当アイテム追加時に自動で紐付けされるようになります。", "Dialog.Confirmation.EditCommonAvatars.ReplaceAvatarsToGroup": "共通素体グループ内のアバターが含まれているアイテムの対応アバターを共通素体グループに置き換えますか?", "Dialog.Confirmation.EditCommonAvatars.ReplaceGroupToAvatars": "共通素体グループが対応アバターとして設定されているアイテムの対応アバターを、個別のアバターに置き換えますか?", @@ -87,6 +88,7 @@ "Dialog.Title.NewTempAvatarName": "新しい仮アバター名", "Dialog.Title.NewCustomCategoryName": "新しいカスタムカテゴリ名", "Dialog.Title.AddUrl": "ダウンロードURLを入力", + "Dialog.Title.SetBoothIdForTempAvatar": "仮アバターのBooth IDを設定(リンクでも可)", "Dialog.SelectSaveFolderPath": "保存先フォルダを選択してください", "Dialog.SelectFolderPath": "フォルダを選択してください", @@ -160,6 +162,7 @@ "ContextMenu.ItemFile.OpenPdfViewer": "PDF Viewerで開く", "ContextMenu.BulkImportPreset.RemovePreset": "プリセットを削除する", "ContextMenu.TempAvatar.EditTempAvatarName": "仮アバター名を編集する", + "ContextMenu.TempAvatar.EditBoothId": "Booth IDを編集する", "ContextMenu.TempAvatar.ResolveTempAvatar": "正式なアイテムと紐付ける", "ContextMenu.TempAvatar.RemoveTempAvatar": "仮アバターを削除する", "ContextMenu.ItemCategory.EditCustomCategoryName": "カスタムカテゴリ名を編集する", @@ -215,6 +218,7 @@ "Warning.RunningInAdministratorMode": "管理者モードでは、ドラッグ&ドロップなど一部の操作が利用できなくなります。", "Warning.FetchAllItemThumbnailsCancelled": "サムネイル一括取得をキャンセルしました。成功: {0}件 / 失敗: {1}件 / 合計: {2}件", "Warning.FetchAllVariationHashesCancelled": "ハッシュ一括取得をキャンセルしました。成功: {0}件 / 失敗: {1}件 / 合計: {2}件", + "Warning.InvalidBoothId": "無効なBooth IDだったため、空のBooth IDが設定されました。", "Error.Default": "エラー", "Error.Nothing": "アイテムが見つからないよっ!", diff --git a/AvatarExplorer.Core/Data/Localization/ko-KR.json b/AvatarExplorer.Core/Data/Localization/ko-KR.json index 3d655013..304efb3d 100644 --- a/AvatarExplorer.Core/Data/Localization/ko-KR.json +++ b/AvatarExplorer.Core/Data/Localization/ko-KR.json @@ -72,6 +72,7 @@ "Dialog.Confirmation.RenameTag": "태그 \"{0}\"을(를) \"{1}\"(으)로 변경하시겠습니까?", "Dialog.Confirmation.RemoveTag": "태그 \"{0}\"을(를) 삭제하시겠습니까?\n※삭제하면 모든 아이템에서 이 태그가 제거됩니다.", "Dialog.Confirmation.RenameTagAlreadyExist": "태그 \"{0}\"은(는) 이미 존재합니다. 계속하면 같은 태그로 인식됩니다. 계속하시겠습니까?", + "Dialog.Confirmation.SetBoothIdForTempAvatar": "이 임시 아바타에 Booth ID를 설정하시겠습니까?\n\n※Booth ID를 설정하면 ID 검색이나 Booth에서 항목 추가 시 연결에 이용됩니다.", "Dialog.Confirmation.EditCommonAvatars.ReplaceAvatarsToGroup": "공통 바디 그룹에 포함된 아바타가 들어 있는 아이템의 대응 아바타를 공통 바디 그룹으로 바꾸시겠습니까?", "Dialog.Confirmation.EditCommonAvatars.ReplaceGroupToAvatars": "공통 바디 그룹이 대응 아바타로 설정된 아이템의 대응 아바타를 개별 아바타로 바꾸시겠습니까?", @@ -87,6 +88,7 @@ "Dialog.Title.NewTempAvatarName": "새 임시 아바타 이름", "Dialog.Title.NewCustomCategoryName": "새 사용자 지정 카테고리 이름", "Dialog.Title.AddUrl": "다운로드 URL 입력", + "Dialog.Title.SetBoothIdForTempAvatar": "임시 아바타의 Booth ID 설정 (링크 입력 가능)", "Dialog.SelectSaveFolderPath": "저장할 폴더를 선택해 주세요", "Dialog.SelectFolderPath": "폴더를 선택해 주세요", @@ -160,6 +162,7 @@ "ContextMenu.ItemFile.OpenPdfViewer": "PDF Viewer로 열기", "ContextMenu.BulkImportPreset.RemovePreset": "프리셋 삭제", "ContextMenu.TempAvatar.EditTempAvatarName": "임시 아바타 이름 편집", + "ContextMenu.TempAvatar.EditBoothId": "Booth ID 편집", "ContextMenu.TempAvatar.ResolveTempAvatar": "정식 아이템과 연결", "ContextMenu.TempAvatar.RemoveTempAvatar": "임시 아바타 삭제", "ContextMenu.ItemCategory.EditCustomCategoryName": "사용자 지정 카테고리 이름 편집", @@ -215,6 +218,7 @@ "Warning.RunningInAdministratorMode": "관리자 모드에서는 드래그 앤 드롭 등 일부 작업을 사용할 수 없습니다.", "Warning.FetchAllItemThumbnailsCancelled": "썸네일 일괄 가져오기를 취소했습니다. 성공: {0}개 / 실패: {1}개 / 전체: {2}개", "Warning.FetchAllVariationHashesCancelled": "해시 일괄 가져오기를 취소했습니다. 성공: {0}개 / 실패: {1}개 / 전체: {2}개", + "Warning.InvalidBoothId": "Booth ID가 유효하지 않습니다. Booth ID가 초기화되었습니다.", "Error.Default": "오류", "Error.Nothing": "아이템을 찾을 수 없어요!", diff --git a/AvatarExplorer.Core/Data/Localization/zh-CN.json b/AvatarExplorer.Core/Data/Localization/zh-CN.json index f74a41b9..b7bc840a 100644 --- a/AvatarExplorer.Core/Data/Localization/zh-CN.json +++ b/AvatarExplorer.Core/Data/Localization/zh-CN.json @@ -72,6 +72,7 @@ "Dialog.Confirmation.RenameTag": "要将标签「{0}」更改为「{1}」吗?", "Dialog.Confirmation.RemoveTag": "要删除标签「{0}」吗?\n※删除后,该标签将从所有项目中移除。", "Dialog.Confirmation.RenameTagAlreadyExist": "标签「{0}」已存在。继续的话,将被识别为同一标签。要继续吗?", + "Dialog.Confirmation.SetBoothIdForTempAvatar": "要为此临时头像设置 Booth ID 吗?\n\n※设置 Booth ID 后,可通过 ID 搜索或从 Booth 添加项目时进行关联。", "Dialog.Confirmation.EditCommonAvatars.ReplaceAvatarsToGroup": "要将包含共用基体组头像的项目的支持头像替换为共用基体组吗?", "Dialog.Confirmation.EditCommonAvatars.ReplaceGroupToAvatars": "要将将共用基体组设置为支持头像的项目的支持头像替换为单独头像吗?", @@ -87,6 +88,7 @@ "Dialog.Title.NewTempAvatarName": "新建临时头像名称", "Dialog.Title.NewCustomCategoryName": "新建自定义类别名称", "Dialog.Title.AddUrl": "输入下载 URL", + "Dialog.Title.SetBoothIdForTempAvatar": "设置临时头像的 Booth ID(也可输入链接)", "Dialog.SelectSaveFolderPath": "请选择要保存的文件夹", "Dialog.SelectFolderPath": "请选择文件夹", @@ -160,6 +162,7 @@ "ContextMenu.ItemFile.OpenPdfViewer": "使用 PDF Viewer 打开", "ContextMenu.BulkImportPreset.RemovePreset": "删除预设", "ContextMenu.TempAvatar.EditTempAvatarName": "编辑临时头像名称", + "ContextMenu.TempAvatar.EditBoothId": "编辑 Booth ID", "ContextMenu.TempAvatar.ResolveTempAvatar": "关联到正式项目", "ContextMenu.TempAvatar.RemoveTempAvatar": "删除临时头像", "ContextMenu.ItemCategory.EditCustomCategoryName": "编辑自定义类别名称", @@ -215,6 +218,7 @@ "Warning.RunningInAdministratorMode": "在管理员模式下,拖放等部分操作可能不可用。", "Warning.FetchAllItemThumbnailsCancelled": "批量获取缩略图已取消。成功: {0} 个 / 失败: {1} 个 / 总计: {2} 个", "Warning.FetchAllVariationHashesCancelled": "批量获取哈希已取消。成功: {0} 个 / 失败: {1} 个 / 总计: {2} 个", + "Warning.InvalidBoothId": "Booth ID 无效,已清除 Booth ID。", "Error.Default": "错误", "Error.Nothing": "没有找到项目!", diff --git a/AvatarExplorer.Core/Data/Localization/zh-TW.json b/AvatarExplorer.Core/Data/Localization/zh-TW.json index f5e8a03e..29d4fd91 100644 --- a/AvatarExplorer.Core/Data/Localization/zh-TW.json +++ b/AvatarExplorer.Core/Data/Localization/zh-TW.json @@ -72,6 +72,7 @@ "Dialog.Confirmation.RenameTag": "要將標籤「{0}」變更為「{1}」嗎?", "Dialog.Confirmation.RemoveTag": "要刪除標籤「{0}」嗎?\n※刪除後,該標籤將從所有項目中移除。", "Dialog.Confirmation.RenameTagAlreadyExist": "標籤「{0}」已存在。繼續的話,將被識別為同一標籤。要繼續嗎?", + "Dialog.Confirmation.SetBoothIdForTempAvatar": "要為此暫時頭像設定 Booth ID 嗎?\n\n※設定 Booth ID 後,可透過 ID 搜尋或從 Booth 新增項目時進行連結。", "Dialog.Confirmation.EditCommonAvatars.ReplaceAvatarsToGroup": "要將包含共用基體群組頭像的項目的支援頭像替換為共用基體群組嗎?", "Dialog.Confirmation.EditCommonAvatars.ReplaceGroupToAvatars": "要將將共用基體群組設定為支援頭像的項目的支援頭像替換為個別頭像嗎?", @@ -87,6 +88,7 @@ "Dialog.Title.NewTempAvatarName": "新暫時頭像名稱", "Dialog.Title.NewCustomCategoryName": "新自訂類別名稱", "Dialog.Title.AddUrl": "輸入下載 URL", + "Dialog.Title.SetBoothIdForTempAvatar": "設定暫時頭像的 Booth ID(也可輸入連結)", "Dialog.SelectSaveFolderPath": "請選擇要儲存的資料夾", "Dialog.SelectFolderPath": "請選擇資料夾", @@ -160,6 +162,7 @@ "ContextMenu.ItemFile.OpenPdfViewer": "使用 PDF Viewer 開啟", "ContextMenu.BulkImportPreset.RemovePreset": "刪除預設", "ContextMenu.TempAvatar.EditTempAvatarName": "編輯暫時頭像名稱", + "ContextMenu.TempAvatar.EditBoothId": "編輯 Booth ID", "ContextMenu.TempAvatar.ResolveTempAvatar": "連結到正式項目", "ContextMenu.TempAvatar.RemoveTempAvatar": "刪除暫時頭像", "ContextMenu.ItemCategory.EditCustomCategoryName": "編輯自訂類別名稱", @@ -215,6 +218,7 @@ "Warning.RunningInAdministratorMode": "在管理員模式下,拖放等部分操作可能無法使用。", "Warning.FetchAllItemThumbnailsCancelled": "批次取得縮圖已取消。成功: {0} 個 / 失敗: {1} 個 / 總計: {2} 個", "Warning.FetchAllVariationHashesCancelled": "批次取得雜湊已取消。成功: {0} 個 / 失敗: {1} 個 / 總計: {2} 個", + "Warning.InvalidBoothId": "Booth ID 無效,已清除 Booth ID。", "Error.Default": "錯誤", "Error.Nothing": "找不到項目!", diff --git a/AvatarExplorer.Core/Localization/LocalizationKeys.g.cs b/AvatarExplorer.Core/Localization/LocalizationKeys.g.cs index f3d88395..e63bb947 100644 --- a/AvatarExplorer.Core/Localization/LocalizationKeys.g.cs +++ b/AvatarExplorer.Core/Localization/LocalizationKeys.g.cs @@ -128,6 +128,7 @@ public static class Confirmation public const string RenameTag = "Dialog.Confirmation.RenameTag"; public const string RemoveTag = "Dialog.Confirmation.RemoveTag"; public const string RenameTagAlreadyExist = "Dialog.Confirmation.RenameTagAlreadyExist"; + public const string SetBoothIdForTempAvatar = "Dialog.Confirmation.SetBoothIdForTempAvatar"; public static class EditCommonAvatars { public const string ReplaceAvatarsToGroup = "Dialog.Confirmation.EditCommonAvatars.ReplaceAvatarsToGroup"; @@ -146,6 +147,7 @@ public static class Title public const string NewTempAvatarName = "Dialog.Title.NewTempAvatarName"; public const string NewCustomCategoryName = "Dialog.Title.NewCustomCategoryName"; public const string AddUrl = "Dialog.Title.AddUrl"; + public const string SetBoothIdForTempAvatar = "Dialog.Title.SetBoothIdForTempAvatar"; } public const string SelectSaveFolderPath = "Dialog.SelectSaveFolderPath"; public const string SelectFolderPath = "Dialog.SelectFolderPath"; @@ -280,6 +282,7 @@ public static class BulkImportPreset public static class TempAvatar { public const string EditTempAvatarName = "ContextMenu.TempAvatar.EditTempAvatarName"; + public const string EditBoothId = "ContextMenu.TempAvatar.EditBoothId"; public const string ResolveTempAvatar = "ContextMenu.TempAvatar.ResolveTempAvatar"; public const string RemoveTempAvatar = "ContextMenu.TempAvatar.RemoveTempAvatar"; } @@ -347,6 +350,7 @@ public static class Warning public const string RunningInAdministratorMode = "Warning.RunningInAdministratorMode"; public const string FetchAllItemThumbnailsCancelled = "Warning.FetchAllItemThumbnailsCancelled"; public const string FetchAllVariationHashesCancelled = "Warning.FetchAllVariationHashesCancelled"; + public const string InvalidBoothId = "Warning.InvalidBoothId"; } public static class Error { diff --git a/AvatarExplorer.Core/Models/Items/TempAvatar.cs b/AvatarExplorer.Core/Models/Items/TempAvatar.cs index 6b5ebd32..fa6d08a4 100644 --- a/AvatarExplorer.Core/Models/Items/TempAvatar.cs +++ b/AvatarExplorer.Core/Models/Items/TempAvatar.cs @@ -6,14 +6,22 @@ namespace AvatarExplorer.Core.Models.Items; /// /// まだ正式に登録されていないアバターを一時的に識別するための「仮アバター」を表すモデルクラスです。後で正式なアバターに「解決(Resolve)」できます。 /// -public class TempAvatar(string avatarName) : AbstractDatabaseItem, IIdentifiable +public class TempAvatar(string avatarName, int boothId = -1) : AbstractDatabaseItem, IIdentifiable { /// 仮アバターの名前です。 - [JsonInclude] public string AvatarName { get; private set; } = avatarName; + [JsonInclude] public string AvatarName { get; private set; } = avatarName; + + /// 仮アバターのBoothIdです。未設定の場合は -1 です。 + [JsonInclude] public int BoothId { get; set; } = boothId; + /// 仮アバター名を更新します。 /// 新しいアバター名。 public void UpdateAvatarName(string newName) => AvatarName = newName; + /// 仮アバターのBoothIdを更新します。 + /// 新しいBoothId。 + public void UpdateBoothId(int newBoothId) => BoothId = newBoothId; + /// この仮アバターを一意に識別する識別子("tempavatar:" + Id)です。 [JsonIgnore] public string Identifier => "tempavatar:" + Id; } diff --git a/AvatarExplorer.Core/Models/System/TempAvatarSearchIndex.cs b/AvatarExplorer.Core/Models/System/TempAvatarSearchIndex.cs index 33b06064..91ef76dd 100644 --- a/AvatarExplorer.Core/Models/System/TempAvatarSearchIndex.cs +++ b/AvatarExplorer.Core/Models/System/TempAvatarSearchIndex.cs @@ -14,6 +14,11 @@ public record TempAvatarSearchIndex : ISearchIndex /// public required string AvatarName { get; init; } + /// + /// 仮アバターのBooth 商品 ID(文字列)。 + /// + public required string BoothId { get; init; } + /// /// フリーワード検索用文字列(アバター名の小文字表現)。 /// @@ -46,6 +51,7 @@ private string[] GetTargets(string? field) return field?.ToLowerInvariant() switch { "title" => [AvatarName], + "boothid" or "booth" => [BoothId], null => [FreeWord], _ => [] }; @@ -58,10 +64,16 @@ private string[] GetTargets(string? field) /// 構築された TempAvatarSearchIndex。 public static TempAvatarSearchIndex Build(TempAvatar tempAvatar) { + var freeWord = string.Join("\n", + tempAvatar.AvatarName, + tempAvatar.BoothId.ToString() + ).ToLowerInvariant(); + return new TempAvatarSearchIndex { AvatarName = tempAvatar.AvatarName, - FreeWord = tempAvatar.AvatarName.ToLowerInvariant() + BoothId = tempAvatar.BoothId.ToString(), + FreeWord = freeWord }; } } diff --git a/AvatarExplorer.Core/Services/System/BoothService.cs b/AvatarExplorer.Core/Services/System/BoothService.cs index b82ed1f6..036e4732 100644 --- a/AvatarExplorer.Core/Services/System/BoothService.cs +++ b/AvatarExplorer.Core/Services/System/BoothService.cs @@ -5,6 +5,7 @@ using AvatarExplorer.Core.Models.Items; using AvatarExplorer.Core.Services.IO; using AvatarExplorer.Core.Services.Network; +using AvatarExplorer.Core.Utils; using ErrorOr; namespace AvatarExplorer.Core.Services.System; @@ -75,7 +76,7 @@ public static async Task> Fetch(string boothUrl, bool waitCoo if (!waitCooldown && IsApiCooldownNow) return Error.Failure(description: "Booth API Cooldown Error."); else await WaitForApiCooldownAsync(); - var boothId = boothUrl.Split('/')[^1]; + var boothId = BoothUtils.ExtractBoothIdFromUrl(boothUrl); _lastBoothApiGetTime = DateTime.Now; diff --git a/AvatarExplorer.Core/Services/System/Repositories/TempAvatarRepository.cs b/AvatarExplorer.Core/Services/System/Repositories/TempAvatarRepository.cs index 0ac4938c..dbfd210b 100644 --- a/AvatarExplorer.Core/Services/System/Repositories/TempAvatarRepository.cs +++ b/AvatarExplorer.Core/Services/System/Repositories/TempAvatarRepository.cs @@ -23,9 +23,10 @@ public override void Load() /// 指定した名前で新しい仮アバターを作成し、データベースに保存します。 /// 作成する仮アバターの名前。 - public void Create(string avatarName) + /// 作成する仮アバターのBoothId。未設定の場合は -1。 + public void Create(string avatarName, int boothId = -1) { - Add(new(avatarName)); + Add(new(avatarName, boothId)); } /// 指定した仮アバターの名前を変更します。 @@ -40,4 +41,17 @@ public void RenameAvatar(string identifier, string newName) Save(); InvokeUpdated(); } + + /// 指定した仮アバターのBoothIdを変更します。 + /// 対象の仮アバターのIdentifier。 + /// 変更後のBoothId。 + public void UpdateBoothId(string identifier, int newBoothId) + { + var avatar = Get(identifier); + if (avatar == null) return; + + avatar.UpdateBoothId(newBoothId); + Save(); + InvokeUpdated(); + } } diff --git a/AvatarExplorer.Core/Utils/BoothUtils.cs b/AvatarExplorer.Core/Utils/BoothUtils.cs new file mode 100644 index 00000000..a30df5b9 --- /dev/null +++ b/AvatarExplorer.Core/Utils/BoothUtils.cs @@ -0,0 +1,9 @@ +namespace AvatarExplorer.Core.Utils; + +public static class BoothUtils +{ + public static string ExtractBoothIdFromUrl(string url) + { + return url.Split('/')[^1]; + } +} diff --git a/AvatarExplorer.UI/Factories/NavigationItemFactory.cs b/AvatarExplorer.UI/Factories/NavigationItemFactory.cs index 05ccd1f9..743c9ab4 100644 --- a/AvatarExplorer.UI/Factories/NavigationItemFactory.cs +++ b/AvatarExplorer.UI/Factories/NavigationItemFactory.cs @@ -113,16 +113,26 @@ private static ItemViewModel FromItemFile(ItemFile file) ViewModelType = ViewModelType.CommonAvatar }; - private static ItemViewModel FromTempAvatar(Avatar avatar, TempAvatar tempAvatar) => new() + private static ItemViewModel FromTempAvatar(Avatar avatar, TempAvatar tempAvatar) { - ThumbnailSource = new() { Primary = SystemIconKey.AvatarIcon }, - TitleRaw = tempAvatar.AvatarName, - TitleLocalizable = false, - DescriptionRaw = new(Loc.Button.Description.TempAvatar), - Identifier = avatar.Identifier, - ActualValue = tempAvatar.Identifier, - ViewModelType = ViewModelType.TempAvatar - }; + var vm = new ItemViewModel() + { + ThumbnailSource = new() { Primary = SystemIconKey.AvatarIcon }, + TitleRaw = tempAvatar.AvatarName, + TitleLocalizable = false, + DescriptionRaw = new(Loc.Button.Description.TempAvatar), + Identifier = avatar.Identifier, + ActualValue = tempAvatar.Identifier, + ViewModelType = ViewModelType.TempAvatar + }; + + if (tempAvatar.BoothId != -1) + { + vm.Tags = [new() { ValueRaw = tempAvatar.BoothId.ToString() }]; + } + + return vm; + } private static ItemViewModel CreateEmpty() => new() { diff --git a/AvatarExplorer.UI/Models/ContextMenu/ActionKey.cs b/AvatarExplorer.UI/Models/ContextMenu/ActionKey.cs index 89c9eed4..4ea68b73 100644 --- a/AvatarExplorer.UI/Models/ContextMenu/ActionKey.cs +++ b/AvatarExplorer.UI/Models/ContextMenu/ActionKey.cs @@ -34,6 +34,7 @@ public enum ActionKey RemovePreset, EditTempAvatarName, + EditBoothId, ResolveTempAvatar, RemoveTempAvatar, diff --git a/AvatarExplorer.UI/Services/ContextMenu/ContextMenuHandlerService.cs b/AvatarExplorer.UI/Services/ContextMenu/ContextMenuHandlerService.cs index 1a561ade..9e0ea552 100644 --- a/AvatarExplorer.UI/Services/ContextMenu/ContextMenuHandlerService.cs +++ b/AvatarExplorer.UI/Services/ContextMenu/ContextMenuHandlerService.cs @@ -64,6 +64,7 @@ public static void Initialize() Register(ActionKey.OpenPdfViewer, OpenPdfViewer); Register(ActionKey.RemovePreset, RemovePreset); Register(ActionKey.EditTempAvatarName, EditTempAvatarName); + Register(ActionKey.EditBoothId, EditBoothId); Register(ActionKey.ResolveTempAvatar, ResolveTempAvatar); Register(ActionKey.RemoveTempAvatar, RemoveTempAvatar); Register(ActionKey.EditCustomCategoryName, EditCustomCategoryName); @@ -531,6 +532,43 @@ private static async void EditTempAvatarName(string identifier) NotificationType.Success ); } + private static async void EditBoothId(string identifier) + { + var tempAvatar = InstanceRepository.TempAvatars.Get(identifier); + if (tempAvatar == null) + { + NotificationManager.Show( + Localizer.Instance[Loc.Error.Default], + Localizer.Instance[Loc.Error.TempAvatarNotFound], + NotificationType.Error + ); + return; + } + + var newBoothId = await InstanceRepository.MainWindow.ShowTextDialog( + Localizer.Instance[Loc.Dialog.Title.SetBoothIdForTempAvatar], + tempAvatar.BoothId == -1 ? string.Empty : tempAvatar.BoothId.ToString() + ); + if (string.IsNullOrEmpty(newBoothId)) return; + + var boothId = ValueParser.Int(BoothUtils.ExtractBoothIdFromUrl(newBoothId), -1); + if (boothId == -1) + { + NotificationManager.Show( + Localizer.Instance[Loc.Warning.Default], + Localizer.Instance[Loc.Warning.InvalidBoothId], + NotificationType.Warning + ); + } + + InstanceRepository.TempAvatars.UpdateBoothId(tempAvatar.Identifier, boothId); + + NotificationManager.Show( + Localizer.Instance[Loc.Success.Default], + Localizer.Instance[Loc.Success.ItemEdit], + NotificationType.Success + ); + } private static void ResolveTempAvatar(string identifier) { InstanceRepository.MainWindow.ResolveTempAvatarVM.Open(identifier); diff --git a/AvatarExplorer.UI/Services/ViewControl/ContextMenuCreator.cs b/AvatarExplorer.UI/Services/ViewControl/ContextMenuCreator.cs index b9c1e092..e2715d73 100644 --- a/AvatarExplorer.UI/Services/ViewControl/ContextMenuCreator.cs +++ b/AvatarExplorer.UI/Services/ViewControl/ContextMenuCreator.cs @@ -158,6 +158,7 @@ private static ContextMenuAction[] CreateFromTempAvatar() List contextMenuActions = [ new(Loc.ContextMenu.TempAvatar.EditTempAvatarName, ActionKey.EditTempAvatarName, ContextMenuIconType.Edit), + new(Loc.ContextMenu.TempAvatar.EditBoothId, ActionKey.EditBoothId, ContextMenuIconType.Edit), new(Loc.ContextMenu.TempAvatar.ResolveTempAvatar, ActionKey.ResolveTempAvatar, ContextMenuIconType.Link, addSeparator: true), new(Loc.ContextMenu.TempAvatar.RemoveTempAvatar, ActionKey.RemoveTempAvatar, ContextMenuIconType.Delete), ]; diff --git a/AvatarExplorer.UI/ViewModels/Overlays/SelectAvatarsViewModel.cs b/AvatarExplorer.UI/ViewModels/Overlays/SelectAvatarsViewModel.cs index d7dccc45..5cb2b6de 100644 --- a/AvatarExplorer.UI/ViewModels/Overlays/SelectAvatarsViewModel.cs +++ b/AvatarExplorer.UI/ViewModels/Overlays/SelectAvatarsViewModel.cs @@ -2,14 +2,17 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Avalonia.Controls.Notifications; using AvatarExplorer.Core.Extensions; using AvatarExplorer.Core.Localization; using AvatarExplorer.Core.Models.Search; +using AvatarExplorer.Core.Utils; using AvatarExplorer.UI.Factories; using AvatarExplorer.UI.Interfaces; using AvatarExplorer.UI.Localization; using AvatarExplorer.UI.Services; using AvatarExplorer.UI.Services.Sort; +using AvatarExplorer.UI.Services.System; using AvatarExplorer.UI.ViewModels.Component; using ReactiveUI; using ReactiveUI.Fody.Helpers; @@ -119,7 +122,34 @@ private async Task AddTempAvatar() var newTempAvatarName = await InstanceRepository.MainWindow.ShowTextDialog(Localizer.Instance[Loc.Dialog.Title.NewTempAvatarName]); if (string.IsNullOrEmpty(newTempAvatarName)) return; - InstanceRepository.TempAvatars.Create(newTempAvatarName); + // BoothIdを指定するかどうか + var setBoothId = await InstanceRepository.MainWindow.ShowYesNoDialog( + Localizer.Instance[Loc.Dialog.Confirmation.Default], + Localizer.Instance[Loc.Dialog.Confirmation.SetBoothIdForTempAvatar] + ); + + var boothId = -1; + var parseBoothIdFailed = false; + if (setBoothId) + { + var boothIdInput = await InstanceRepository.MainWindow.ShowTextDialog(Localizer.Instance[Loc.Dialog.Title.SetBoothIdForTempAvatar]); + if (boothIdInput != null) + { + boothId = ValueParser.Int(BoothUtils.ExtractBoothIdFromUrl(boothIdInput), -1); + if (boothId < 0) parseBoothIdFailed = true; + } + } + + if (parseBoothIdFailed) + { + NotificationManager.Show( + Localizer.Instance[Loc.Warning.Default], + Localizer.Instance[Loc.Warning.InvalidBoothId], + NotificationType.Warning + ); + } + + InstanceRepository.TempAvatars.Create(newTempAvatarName, boothId); RefleshAvatars(IncludeCommonAvatar, IncludeTempAvatar); ApplySearchResult(SearchText); } diff --git a/AvatarExplorer.UI/Views/Overlays/TextDialog.axaml b/AvatarExplorer.UI/Views/Overlays/TextDialog.axaml index e9720e5a..c9b5ec4b 100644 --- a/AvatarExplorer.UI/Views/Overlays/TextDialog.axaml +++ b/AvatarExplorer.UI/Views/Overlays/TextDialog.axaml @@ -6,19 +6,16 @@ x:DataType="vm:TextDialogViewModel"> - + - - + - - + - -