From 0efc56f8d65facc80743bfc54f60bae8e467a4da Mon Sep 17 00:00:00 2001 From: MathieuG-P <40181755+Zagrios@users.noreply.github.com> Date: Sun, 20 Oct 2024 18:10:01 +0200 Subject: [PATCH] [chore-521] Move create playlist action --- assets/jsons/translations/de.json | 46 +++++++- assets/jsons/translations/en.json | 47 ++++++-- assets/jsons/translations/es.json | 46 +++++++- assets/jsons/translations/fr.json | 47 +++++++- assets/jsons/translations/ja.json | 46 +++++++- assets/jsons/translations/ru.json | 46 +++++++- assets/jsons/translations/zh-tw.json | 46 +++++++- assets/jsons/translations/zh.json | 46 +++++++- src/main/helpers/fs.helpers.ts | 2 + src/main/ipcs/bs-playlist-ipcs.ts | 5 + src/main/ipcs/os-controls-ipcs.ts | 5 + .../local-playlists-manager.service.ts | 63 ++++++++++- .../maps/local-maps-manager.service.ts | 7 +- .../maps-playlists-panel.component.tsx | 102 ++++++++++++------ .../local-playlists-list-panel.component.tsx | 24 +++-- .../components/shared/dropzone.component.tsx | 98 +++++++++++++++-- .../components/svgs/bsm-icon.component.tsx | 12 ++- .../svgs/icons/add-file-icon.component.tsx | 9 ++ .../svgs/icons/browse-icon.component.tsx | 11 ++ src/renderer/services/maps-manager.service.ts | 2 +- .../services/playlists-manager.service.ts | 83 +++++++++++++- src/renderer/windows/App.tsx | 4 + .../models/exceptions/custom-error.class.ts | 13 +-- src/shared/models/ipc/ipc-routes.ts | 4 +- 24 files changed, 704 insertions(+), 110 deletions(-) create mode 100644 src/renderer/components/svgs/icons/add-file-icon.component.tsx create mode 100644 src/renderer/components/svgs/icons/browse-icon.component.tsx diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index 775d3f24e..386fcda49 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -52,8 +52,9 @@ "tabs": { "maps": { "actions": { - "add-maps": { - "text": "Hinzufügen" + "drop-down": { + "browse-maps": "Karten durchsuchen", + "import-maps": "Karten importieren" }, "link-maps": { "tooltips": { @@ -70,6 +71,17 @@ "text": "Deine Karten importieren", "subtext": "Lasse deine ZIP-Dateien hier fallen, um deine Karten zu importieren" } + }, + "playlists": { + "drop-down": { + "browse-playlists": "Wiedergabelisten durchsuchen", + "create-a-playlist": "Eine Wiedergabeliste erstellen", + "import-playlists": "Wiedergabelisten importieren" + }, + "drop-zone": { + "text": "Importiere deine Wiedergabelisten", + "subtext": "Ziehe deine \".bplist\" oder \".json\" Dateien hierhin, um sie zu importieren" + } } } }, @@ -254,11 +266,13 @@ "errors": { "titles": { "operation-running": "Vorgang läuft", - "no-internet": "Kein Internet" + "no-internet": "Kein Internet", + "file-not-supported": "Datei wird nicht unterstützt" }, "msg": { "operation-running": "Warte auf das Ende des aktuellen Vorgangs und versuche es erneut.", - "no-internet": "Überprüfe deine Verbindung und versuche es erneut." + "no-internet": "Überprüfe deine Verbindung und versuche es erneut.", + "file-not-supported": "Nur {types}-Dateien werden unterstützt." } } }, @@ -519,7 +533,8 @@ "success": "Die Karten wurden erfolgreich importiert.", "some-success": "Einige Karten wurden erfolgreich importiert.", "only-accept-zip": "Nur Zip-Dateien werden unterstützt.", - "invalid-zip": "Die Zip-Datei(en) enthalten keine Karten." + "invalid-zip": "Die Zip-Datei(en) enthalten keine Karten.", + "unknown": "Ein unbekannter Fehler ist aufgetreten." } } }, @@ -1083,6 +1098,9 @@ } } }, + "drop-zone": { + "or-browse-files": "Oder Dateien durchsuchen" + }, "playlist": { "error-playlist-creation-title": "Fehler beim Erstellen der Playlist", "error-playlist-creation-desc": "Beim Erstellen der Playlist ist ein Fehler aufgetreten.", @@ -1197,6 +1215,24 @@ "last-week": "Letzte Woche", "last-month": "Letzter Monat", "3-last-month": "Letzte 3 Monate" + }, + "playlists-imported": "Wiedergabelisten importiert", + "all-playlists-have-been-successfully-imported": "Alle Wiedergabelisten wurden erfolgreich importiert", + "no-playlist-found": "Keine Wiedergabeliste gefunden", + "no-playlist-found-in-selected-files": "Keine Wiedergabeliste in den ausgewählten Dateien gefunden", + "some-playlists-not-imported": "Einige Wiedergabelisten wurden nicht importiert", + "some-playlists-have-been-imported": { + "INVALID_SOURCE": "Einige Wiedergabelisten konnten nicht gefunden werden", + "INVALID_PLAYLIST_FILE": "Einige Wiedergabelisten sind ungültig", + "CANNOT_PARSE_PLAYLIST": "Einige Wiedergabelisten sind nicht lesbar", + "unknown": "Einige Wiedergabelisten konnten nicht importiert werden" + }, + "no-playlists-imported": "Keine Wiedergabelisten importiert", + "no-playlists-imported-errors": { + "INVALID_SOURCE": "Wiedergabelisten konnten nicht gefunden werden", + "INVALID_PLAYLIST_FILE": "Wiedergabelisten sind ungültig", + "CANNOT_PARSE_PLAYLIST": "Wiedergabelisten sind nicht lesbar", + "unknown": "Es konnte keine Wiedergabeliste importiert werden" } }, "dateformat": { diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index ff6765e8c..cc4892f5e 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -52,8 +52,9 @@ "tabs": { "maps": { "actions": { - "add-maps": { - "text": "Add" + "drop-down": { + "browse-maps": "Browse maps", + "import-maps": "Import maps" }, "link-maps": { "tooltips": { @@ -70,7 +71,17 @@ "text": "Import your maps", "subtext": "Drop your zip files here to import your maps" } - + }, + "playlists": { + "drop-down": { + "browse-playlists": "Browse playlists", + "create-a-playlist": "Create a playlist", + "import-playlists": "Import playlists" + }, + "drop-zone": { + "text": "Import your playlists", + "subtext": "Drop your \".bplist\" or \".json\" files here to import them" + } } } }, @@ -260,11 +271,13 @@ "errors": { "titles": { "operation-running": "Operation running", - "no-internet": "No internet" + "no-internet": "No internet", + "file-not-supported": "File not supported" }, "msg": { "operation-running": "Wait for the current operation to finish, then try again.", - "no-internet": "Check your connection and try again." + "no-internet": "Check your connection and try again.", + "file-not-supported": "Only {types} files are supported." } } }, @@ -527,7 +540,8 @@ "success": "Maps successfully imported.", "some-success": "Some maps were successfully imported.", "only-accept-zip": "Only zip files are supported.", - "invalid-zip": "The zip file(s) do not contain any maps." + "invalid-zip": "The zip file(s) do not contain any maps.", + "unknown": "An unknown error occurred." } } }, @@ -1091,6 +1105,9 @@ } } }, + "drop-zone": { + "or-browse-files": "Or browse files" + }, "playlist": { "error-playlist-creation-title": "Error creating playlist", "error-playlist-creation-desc": "An error occurred while creating the playlist.", @@ -1205,6 +1222,24 @@ "last-week": "Last week", "last-month": "Last month", "3-last-month": "Last 3 months" + }, + "playlists-imported": "Playlists imported", + "all-playlists-have-been-successfully-imported": "All playlists have been successfully imported", + "no-playlist-found": "No playlist found", + "no-playlist-found-in-selected-files": "No playlist found in the selected files", + "some-playlists-not-imported": "Some playlists not imported", + "some-playlists-have-been-imported": { + "INVALID_SOURCE": "Some playlists could not be found", + "INVALID_PLAYLIST_FILE": "Some playlists are invalid", + "CANNOT_PARSE_PLAYLIST": "Some playlists are unreadable", + "unknown": "Some playlists could not be imported" + }, + "no-playlists-imported": "No playlists imported", + "no-playlists-imported-errors": { + "INVALID_SOURCE": "Playlists could not be found", + "INVALID_PLAYLIST_FILE": "Playlists are invalid", + "CANNOT_PARSE_PLAYLIST": "Playlists are unreadable", + "unknown": "No playlist could be imported" } }, "dateformat": { diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index 385b971fc..a4825aa30 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -52,8 +52,9 @@ "tabs": { "maps": { "actions": { - "add-maps": { - "text": "Añadir" + "drop-down": { + "browse-maps": "Explorar mapas", + "import-maps": "Importar mapas" }, "link-maps": { "tooltips": { @@ -70,6 +71,17 @@ "text": "Importar tus mapas", "subtext": "Suelta tus archivos zip aquí para importar tus mapas" } + }, + "playlists": { + "drop-down": { + "browse-playlists": "Explorar listas de reproducción", + "create-a-playlist": "Crear una lista de reproducción", + "import-playlists": "Importar listas de reproducción" + }, + "drop-zone": { + "text": "Importa tus listas de reproducción", + "subtext": "Suelta tus archivos \".bplist\" o \".json\" aquí para importarlos" + } } } }, @@ -254,11 +266,13 @@ "errors": { "titles": { "operation-running": "Operación en curso", - "no-internet": "Sin internet" + "no-internet": "Sin internet", + "file-not-supported": "Archivo no compatible" }, "msg": { "operation-running": "Espera a que termine la operación actual y vuelve a empezar.", - "no-internet": "Comprueba tu conexión a Internet e inténtalo de nuevo." + "no-internet": "Comprueba tu conexión a Internet e inténtalo de nuevo.", + "file-not-supported": "Solo se admiten archivos {types}." } } }, @@ -519,7 +533,8 @@ "success": "Los mapas se han importado con éxito.", "some-success": "Algunos mapas se han importado con éxito.", "only-accept-zip": "Solo se admiten archivos zip.", - "invalid-zip": "El o los archivos zip no contienen mapas." + "invalid-zip": "El o los archivos zip no contienen mapas.", + "unknown": "Se produjo un error desconocido." } } }, @@ -1083,6 +1098,9 @@ } } }, + "drop-zone": { + "or-browse-files": "O navegar por los archivos" + }, "playlist": { "error-playlist-creation-title": "Error al crear la lista de reproducción", "error-playlist-creation-desc": "Ocurrió un error al crear la lista de reproducción.", @@ -1197,6 +1215,24 @@ "last-week": "Última semana", "last-month": "Último mes", "3-last-month": "Últimos 3 meses" + }, + "playlists-imported": "Listas de reproducción importadas", + "all-playlists-have-been-successfully-imported": "Todas las listas de reproducción se han importado con éxito", + "no-playlist-found": "No se encontró ninguna lista de reproducción", + "no-playlist-found-in-selected-files": "No se encontró ninguna lista de reproducción en los archivos seleccionados", + "some-playlists-not-imported": "Algunas listas de reproducción no se importaron", + "some-playlists-have-been-imported": { + "INVALID_SOURCE": "No se pudieron encontrar algunas listas de reproducción", + "INVALID_PLAYLIST_FILE": "Algunas listas de reproducción no son válidas", + "CANNOT_PARSE_PLAYLIST": "Algunas listas de reproducción no se pueden leer", + "unknown": "No se pudieron importar algunas listas de reproducción" + }, + "no-playlists-imported": "No se importaron listas de reproducción", + "no-playlists-imported-errors": { + "INVALID_SOURCE": "No se pudieron encontrar las listas de reproducción", + "INVALID_PLAYLIST_FILE": "Las listas de reproducción no son válidas", + "CANNOT_PARSE_PLAYLIST": "Las listas de reproducción no se pueden leer", + "unknown": "No se pudo importar ninguna lista de reproducción" } }, "dateformat": { diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index 2e9ac8def..0695a01a1 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -52,8 +52,9 @@ "tabs": { "maps": { "actions": { - "add-maps": { - "text": "Ajouter" + "drop-down": { + "browse-maps": "Explorer les maps", + "import-maps": "Importer des maps" }, "link-maps": { "tooltips": { @@ -70,6 +71,17 @@ "text": "Importer vos maps", "subtext": "Déposez vos fichiers zip ici pour importer vos maps" } + }, + "playlists": { + "drop-down": { + "browse-playlists": "Explorer les playlists", + "create-a-playlist": "Créer une playlist", + "import-playlists": "Importer des playlists" + }, + "drop-zone": { + "text": "Importer vos playlists", + "subtext": "Déposez vos fichiers \".bplist\" ou \".json\" ici pour les importer" + } } } }, @@ -254,11 +266,13 @@ "errors": { "titles": { "operation-running": "Opération en cours", - "no-internet": "Pas d'accès Internet" + "no-internet": "Pas d'accès Internet", + "file-not-supported": "Fichier non supporté" }, "msg": { "operation-running": "Attends la fin de l'opération en cours puis recommence.", - "no-internet": "Vérifie ta connexion internet et ressaye." + "no-internet": "Vérifie ta connexion internet et ressaye.", + "file-not-supported": "Seuls les fichiers {types} sont pris en charge." } } }, @@ -519,7 +533,8 @@ "success": "Les maps ont été importées avec succès.", "some-success": "Certaines maps ont été importées avec succès.", "only-accept-zip": "Seules les fichiers zip sont pris en charge.", - "invalid-zip": "Le ou les fichiers zip ne contiennent aucune maps." + "invalid-zip": "Le ou les fichiers zip ne contiennent aucune maps.", + "unknown": "Une erreur inconnue s'est produite." } } }, @@ -1085,6 +1100,9 @@ } } }, + "drop-zone": { + "or-browse-files": "Ou parcourir les fichiers" + }, "playlist": { "error-playlist-creation-title": "Erreur lors de la création de la playlist", "error-playlist-creation-desc": "Une erreur est survenue lors de la création de la playlist.", @@ -1199,7 +1217,26 @@ "last-week": "Dernière semaine", "last-month": "Dernier mois", "3-last-month": "3 derniers mois" + }, + "playlists-imported": "Playlists importées", + "all-playlists-have-been-successfully-imported": "Toutes les playlists ont été importées avec succès", + "no-playlist-found": "Aucune playlist trouvée", + "no-playlist-found-in-selected-files": "Aucune playlist trouvée dans les fichiers sélectionnés", + "some-playlists-not-imported": "Certaines playlists non importées", + "some-playlists-have-been-imported": { + "INVALID_SOURCE": "Certaines playlists n'ont pas pu être trouvées", + "INVALID_PLAYLIST_FILE": "Certaines playlists ne sont pas valides", + "CANNOT_PARSE_PLAYLIST": "Certaines playlists ne sont pas lisibles", + "unknown": "Certaines playlists n'ont pas pu être importées" + }, + "no-playlists-imported": "Aucune playlist importée", + "no-playlists-imported-errors": { + "INVALID_SOURCE": "Les playlists n'ont pas été trouvées", + "INVALID_PLAYLIST_FILE": "Les playlists ne sont pas valides", + "CANNOT_PARSE_PLAYLIST": "Les playlists ne sont pas lisibles", + "unknown": "Aucune playlist n'a pu être importée" } + }, "dateformat": { "dayNames": ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"], diff --git a/assets/jsons/translations/ja.json b/assets/jsons/translations/ja.json index bbcf1b154..3d20cd42b 100644 --- a/assets/jsons/translations/ja.json +++ b/assets/jsons/translations/ja.json @@ -52,8 +52,9 @@ "tabs": { "maps": { "actions": { - "add-maps": { - "text": "追加" + "drop-down": { + "browse-maps": "マップを閲覧", + "import-maps": "マップをインポート" }, "link-maps": { "tooltips": { @@ -70,6 +71,17 @@ "text": "マップをインポート", "subtext": "ZIPファイルをここにドロップしてマップをインポート" } + }, + "playlists": { + "drop-down": { + "browse-playlists": "プレイリストを閲覧", + "create-a-playlist": "プレイリストを作成", + "import-playlists": "プレイリストをインポート" + }, + "drop-zone": { + "text": "プレイリストをインポート", + "subtext": "\".bplist\" または \".json\" ファイルをここにドロップしてインポート" + } } } }, @@ -254,11 +266,13 @@ "errors": { "titles": { "operation-running": "稼働中", - "no-internet": "インターネット接続がありません" + "no-internet": "インターネット接続がありません", + "file-not-supported": "ファイルはサポートされていません" }, "msg": { "operation-running": "現在の処理が終了するのを待ってから、もう一度試してください。", - "no-internet": "インターネット接続を確認し、もう一度お試しください。" + "no-internet": "インターネット接続を確認し、もう一度お試しください。", + "file-not-supported": "{types}ファイルのみがサポートされています。" } } }, @@ -519,7 +533,8 @@ "success": "マップが正常にインポートされました。", "some-success": "一部のマップが正常にインポートされました。", "only-accept-zip": "zipファイルのみがサポートされています。", - "invalid-zip": "zipファイルにマップが含まれていません。" + "invalid-zip": "zipファイルにマップが含まれていません。", + "unknown": "不明なエラーが発生しました。" } } }, @@ -1083,6 +1098,9 @@ } } }, + "drop-zone": { + "or-browse-files": "またはファイルを参照" + }, "playlist": { "error-playlist-creation-title": "プレイリストの作成エラー", "error-playlist-creation-desc": "プレイリストの作成中にエラーが発生しました。", @@ -1197,6 +1215,24 @@ "last-week": "先週", "last-month": "先月", "3-last-month": "過去3ヶ月" + }, + "playlists-imported": "プレイリストがインポートされました", + "all-playlists-have-been-successfully-imported": "すべてのプレイリストが正常にインポートされました", + "no-playlist-found": "プレイリストが見つかりません", + "no-playlist-found-in-selected-files": "選択されたファイルにプレイリストが見つかりません", + "some-playlists-not-imported": "一部のプレイリストはインポートされませんでした", + "some-playlists-have-been-imported": { + "INVALID_SOURCE": "一部のプレイリストが見つかりませんでした", + "INVALID_PLAYLIST_FILE": "一部のプレイリストは無効です", + "CANNOT_PARSE_PLAYLIST": "一部のプレイリストは読み取れません", + "unknown": "一部のプレイリストをインポートできませんでした" + }, + "no-playlists-imported": "プレイリストはインポートされませんでした", + "no-playlists-imported-errors": { + "INVALID_SOURCE": "プレイリストが見つかりませんでした", + "INVALID_PLAYLIST_FILE": "プレイリストは無効です", + "CANNOT_PARSE_PLAYLIST": "プレイリストは読み取れません", + "unknown": "プレイリストをインポートできませんでした" } }, "dateformat": { diff --git a/assets/jsons/translations/ru.json b/assets/jsons/translations/ru.json index dc65eaa5a..b71d8ffd9 100644 --- a/assets/jsons/translations/ru.json +++ b/assets/jsons/translations/ru.json @@ -52,8 +52,9 @@ "tabs": { "maps": { "actions": { - "add-maps": { - "text": "Добавить" + "drop-down": { + "browse-maps": "Просмотр карт", + "import-maps": "Импорт карт" }, "link-maps": { "tooltips": { @@ -70,6 +71,17 @@ "text": "Импортируйте свои карты", "subtext": "Перетащите сюда файлы ZIP, чтобы импортировать свои карты" } + }, + "playlists": { + "drop-down": { + "browse-playlists": "Просмотр плейлистов", + "create-a-playlist": "Создать плейлист", + "import-playlists": "Импорт плейлистов" + }, + "drop-zone": { + "text": "Импортируйте ваши плейлисты", + "subtext": "Перетащите сюда файлы \".bplist\" или \".json\" для их импорта" + } } } }, @@ -254,11 +266,13 @@ "errors": { "titles": { "operation-running": "Операция в процессе", - "no-internet": "Нет интернета" + "no-internet": "Нет интернета", + "file-not-supported": "Файл не поддерживается" }, "msg": { "operation-running": "Дождитесь завершения операции, потом попробуйте снова.", - "no-internet": "Проверьте своё соединение и попробуйте снова." + "no-internet": "Проверьте своё соединение и попробуйте снова.", + "file-not-supported": "Поддерживаются только файлы {types}." } } }, @@ -519,7 +533,8 @@ "success": "Карты были успешно импортированы.", "some-success": "Некоторые карты успешно импортированы.", "only-accept-zip": "Поддерживаются только zip-файлы.", - "invalid-zip": "В zip-файле(ах) нет карт." + "invalid-zip": "В zip-файле(ах) нет карт.", + "unknown": "Произошла неизвестная ошибка." } } }, @@ -1082,6 +1097,9 @@ } } }, + "drop-zone": { + "or-browse-files": "Или просмотреть файлы" + }, "playlist": { "error-playlist-creation-title": "Ошибка при создании плейлиста", "error-playlist-creation-desc": "Произошла ошибка при создании плейлиста.", @@ -1196,6 +1214,24 @@ "last-week": "Последняя неделя", "last-month": "Последний месяц", "3-last-month": "Последние 3 месяца" + }, + "playlists-imported": "Плейлисты импортированы", + "all-playlists-have-been-successfully-imported": "Все плейлисты успешно импортированы", + "no-playlist-found": "Плейлист не найден", + "no-playlist-found-in-selected-files": "Плейлист не найден в выбранных файлах", + "some-playlists-not-imported": "Некоторые плейлисты не импортированы", + "some-playlists-have-been-imported": { + "INVALID_SOURCE": "Некоторые плейлисты не найдены", + "INVALID_PLAYLIST_FILE": "Некоторые плейлисты недействительны", + "CANNOT_PARSE_PLAYLIST": "Некоторые плейлисты нечитаемы", + "unknown": "Некоторые плейлисты не удалось импортировать" + }, + "no-playlists-imported": "Плейлисты не импортированы", + "no-playlists-imported-errors": { + "INVALID_SOURCE": "Плейлисты не найдены", + "INVALID_PLAYLIST_FILE": "Плейлисты недействительны", + "CANNOT_PARSE_PLAYLIST": "Плейлисты нечитаемы", + "unknown": "Не удалось импортировать плейлисты" } }, "dateformat": { diff --git a/assets/jsons/translations/zh-tw.json b/assets/jsons/translations/zh-tw.json index 71b5ee935..04bb6c398 100644 --- a/assets/jsons/translations/zh-tw.json +++ b/assets/jsons/translations/zh-tw.json @@ -52,8 +52,9 @@ "tabs": { "maps": { "actions": { - "add-maps": { - "text": "新增" + "drop-down": { + "browse-maps": "瀏覽地圖", + "import-maps": "匯入地圖" }, "link-maps": { "tooltips": { @@ -70,6 +71,17 @@ "text": "導入您的地圖", "subtext": "將ZIP文件拖放到此處以導入您的地圖" } + }, + "playlists": { + "drop-down": { + "browse-playlists": "瀏覽播放列表", + "create-a-playlist": "建立播放列表", + "import-playlists": "匯入播放列表" + }, + "drop-zone": { + "text": "匯入您的播放列表", + "subtext": "將您的 \".bplist\" 或 \".json\" 檔案拖曳到這裡進行匯入" + } } } }, @@ -254,11 +266,13 @@ "errors": { "titles": { "operation-running": "操作進行中", - "no-internet": "無網路" + "no-internet": "無網路", + "file-not-supported": "文件不受支持" }, "msg": { "operation-running": "等待當前操作完成,然後重試。", - "no-internet": "檢查你的連接並重試。" + "no-internet": "檢查你的連接並重試。", + "file-not-supported": "僅支援{types}檔案。" } } }, @@ -519,7 +533,8 @@ "success": "地圖已成功匯入。", "some-success": "部分地圖已成功匯入。", "only-accept-zip": "僅支援zip檔案。", - "invalid-zip": "zip檔案中沒有地圖。" + "invalid-zip": "zip檔案中沒有地圖。", + "unknown": "發生了未知錯誤。" } } }, @@ -1083,6 +1098,9 @@ } } }, + "drop-zone": { + "or-browse-files": "或瀏覽檔案" + }, "playlist": { "error-playlist-creation-title": "建立播放清單時發生錯誤", "error-playlist-creation-desc": "建立播放清單時發生錯誤。", @@ -1197,6 +1215,24 @@ "last-week": "上週", "last-month": "上個月", "3-last-month": "最近3個月" + }, + "playlists-imported": "播放清單已匯入", + "all-playlists-have-been-successfully-imported": "所有播放清單已成功匯入", + "no-playlist-found": "未找到播放清單", + "no-playlist-found-in-selected-files": "在選定檔案中未找到播放清單", + "some-playlists-not-imported": "部分播放清單未匯入", + "some-playlists-have-been-imported": { + "INVALID_SOURCE": "某些播放清單未找到", + "INVALID_PLAYLIST_FILE": "某些播放清單無效", + "CANNOT_PARSE_PLAYLIST": "某些播放清單無法解析", + "unknown": "某些播放清單無法匯入" + }, + "no-playlists-imported": "未匯入任何播放清單", + "no-playlists-imported-errors": { + "INVALID_SOURCE": "播放清單未找到", + "INVALID_PLAYLIST_FILE": "播放清單無效", + "CANNOT_PARSE_PLAYLIST": "播放清單無法解析", + "unknown": "無法匯入任何播放清單" } }, "dateformat": { diff --git a/assets/jsons/translations/zh.json b/assets/jsons/translations/zh.json index d762040ac..c50558b32 100644 --- a/assets/jsons/translations/zh.json +++ b/assets/jsons/translations/zh.json @@ -52,8 +52,9 @@ "tabs": { "maps": { "actions": { - "add-maps": { - "text": "添加" + "drop-down": { + "browse-maps": "浏览地图", + "import-maps": "导入地图" }, "link-maps": { "tooltips": { @@ -70,6 +71,17 @@ "text": "导入您的地图", "subtext": "将ZIP文件拖放到此处以导入您的地图" } + }, + "playlists": { + "drop-down": { + "browse-playlists": "浏览播放列表", + "create-a-playlist": "创建播放列表", + "import-playlists": "导入播放列表" + }, + "drop-zone": { + "text": "导入您的播放列表", + "subtext": "将您的 \".bplist\" 或 \".json\" 文件拖放到这里进行导入" + } } } }, @@ -254,11 +266,13 @@ "errors": { "titles": { "operation-running": "操作进行中", - "no-internet": "无网络" + "no-internet": "无网络", + "file-not-supported": "文件不受支持" }, "msg": { "operation-running": "等待当前操作完成,然后重试。", - "no-internet": "检查你的连接并重试。" + "no-internet": "检查你的连接并重试。", + "file-not-supported": "仅支持{types}文件。" } } }, @@ -519,7 +533,8 @@ "success": "地图已成功导入。", "some-success": "部分地图已成功导入。", "only-accept-zip": "仅支持zip文件。", - "invalid-zip": "zip文件中没有地图。" + "invalid-zip": "zip文件中没有地图。", + "unknown": "发生了未知错误。" } } }, @@ -1083,6 +1098,9 @@ } } }, + "drop-zone": { + "or-browse-files": "或浏览文件" + }, "playlist": { "error-playlist-creation-title": "创建播放列表时出错", "error-playlist-creation-desc": "创建播放列表时发生错误。", @@ -1197,6 +1215,24 @@ "last-week": "上周", "last-month": "上个月", "3-last-month": "最近3个月" + }, + "playlists-imported": "播放列表已导入", + "all-playlists-have-been-successfully-imported": "所有播放列表已成功导入", + "no-playlist-found": "未找到播放列表", + "no-playlist-found-in-selected-files": "在选定文件中未找到播放列表", + "some-playlists-not-imported": "部分播放列表未导入", + "some-playlists-have-been-imported": { + "INVALID_SOURCE": "某些播放列表未找到", + "INVALID_PLAYLIST_FILE": "某些播放列表无效", + "CANNOT_PARSE_PLAYLIST": "某些播放列表无法解析", + "unknown": "某些播放列表无法导入" + }, + "no-playlists-imported": "未导入任何播放列表", + "no-playlists-imported-errors": { + "INVALID_SOURCE": "播放列表未找到", + "INVALID_PLAYLIST_FILE": "播放列表无效", + "CANNOT_PARSE_PLAYLIST": "播放列表无法解析", + "unknown": "无法导入任何播放列表" } }, "dateformat": { diff --git a/src/main/helpers/fs.helpers.ts b/src/main/helpers/fs.helpers.ts index a3206561a..d8111de9e 100644 --- a/src/main/helpers/fs.helpers.ts +++ b/src/main/helpers/fs.helpers.ts @@ -8,6 +8,7 @@ import crypto from "crypto"; import { execSync } from "child_process"; import { tryit } from "../../shared/helpers/error.helpers"; import { CustomError } from "shared/models/exceptions/custom-error.class"; +import { ErrorObject } from "serialize-error"; export async function pathExist(path: string): Promise { try { @@ -279,4 +280,5 @@ export interface Progression { diff?: number; data?: T; extra?: D; + lastError?: ErrorObject; } diff --git a/src/main/ipcs/bs-playlist-ipcs.ts b/src/main/ipcs/bs-playlist-ipcs.ts index 1b63f9a33..17b59bb31 100644 --- a/src/main/ipcs/bs-playlist-ipcs.ts +++ b/src/main/ipcs/bs-playlist-ipcs.ts @@ -59,6 +59,11 @@ ipc.on("export-playlists", (args, reply) => { reply(playlists.exportPlaylists(args)); }); +ipc.on("import-playlists", (args, reply) => { + const playlists = LocalPlaylistsManagerService.getInstance(); + reply(playlists.importPlaylists(args)); +}); + ipc.on("install-playlist-file", (args, reply) => { const playlists = LocalPlaylistsManagerService.getInstance(); diff --git a/src/main/ipcs/os-controls-ipcs.ts b/src/main/ipcs/os-controls-ipcs.ts index 4f39e4a0c..2ca355a32 100644 --- a/src/main/ipcs/os-controls-ipcs.ts +++ b/src/main/ipcs/os-controls-ipcs.ts @@ -13,6 +13,11 @@ ipc.on("new-window", (args, reply) => { reply(from(shell.openExternal(args))); }); +ipc.on("open-dialog", (args, reply) => { + // Use this ipc instead of "choose-folder" or "choose-file" to have more control over the dialog + reply(from(dialog.showOpenDialog(args))); +}) + ipc.on("choose-folder", (args, reply) => { reply(from(dialog.showOpenDialog({ properties: ["openDirectory"], defaultPath: args ?? "" }))); }); diff --git a/src/main/services/additional-content/local-playlists-manager.service.ts b/src/main/services/additional-content/local-playlists-manager.service.ts index efb4c3f86..ca1bad720 100644 --- a/src/main/services/additional-content/local-playlists-manager.service.ts +++ b/src/main/services/additional-content/local-playlists-manager.service.ts @@ -26,6 +26,7 @@ import { tryit } from "shared/helpers/error.helpers"; import recursiveReadDir from "recursive-readdir"; import { BsvMapDetail, SongDetails } from "shared/models/maps"; import { findHashInString } from "shared/helpers/string.helpers"; +import { serializeError } from "serialize-error"; export class LocalPlaylistsManagerService { private static instance: LocalPlaylistsManagerService; @@ -97,7 +98,7 @@ export class LocalPlaylistsManagerService { const dest = await (async () => { if(opt.dest && path.isAbsolute(opt.dest) && this.acceptPlaylistFiletype(opt.dest)) { return opt.dest; } const playlistFolder = await this.getPlaylistsFolder(opt.version); - const playlistPath = path.join(playlistFolder, `${sanitize(opt.bpList.playlistTitle)}${path.extname(opt.dest)}`); + const playlistPath = path.join(playlistFolder, `${sanitize(opt.bpList.playlistTitle)}.bplist`); return getUniqueFileNamePath(playlistPath); })(); @@ -122,7 +123,7 @@ export class LocalPlaylistsManagerService { const dest = await (async () => { if(opt.dest && path.isAbsolute(opt.dest) && this.acceptPlaylistFiletype(opt.dest)) { return opt.dest; } const playlistFolder = await this.getPlaylistsFolder(opt.version); - return path.join(playlistFolder, `${sanitize(bplist.playlistTitle)}${path.extname(opt.dest)}`); + return path.join(playlistFolder, `${sanitize(bplist.playlistTitle)}.bplist`); })(); writeFileSync(dest, JSON.stringify(bplist, null, 2)); @@ -137,13 +138,22 @@ export class LocalPlaylistsManagerService { const isLocalFile = await pathExists(source).catch(e => { log.error(e); return false; }); if(!isLocalFile && !isValidUrl(source)) { - throw new Error(`Invalid source ${source}`); + throw new CustomError(`Invalid source (${source})`, "INVALID_SOURCE"); } - const bpList: BPList = isLocalFile ? JSON.parse(readFileSync(source).toString()) : await this.request.getJSON(source); + const bpList: BPList = await (async () => { + if(isLocalFile){ + const res = await tryit(async () => JSON.parse(readFileSync(source).toString())); + if(res.error) { + throw CustomError.fromError(res.error, "CANNOT_PARSE_PLAYLIST"); + } + return res.result; + } + return this.request.getJSON(source); + })(); if(!bpList?.playlistTitle) { - throw new Error(`Invalid playlist file ${source}`); + throw new CustomError(`Invalid playlist file (${source})`, "INVALID_PLAYLIST_FILE"); } bpList.songs = (bpList.songs ?? []).map(s => s.hash ? ( @@ -430,6 +440,49 @@ export class LocalPlaylistsManagerService { return archive.finalize(); } + public importPlaylists({ version, paths }: { version?: BSVersion, paths: string[] }): Observable> { + return new Observable>(obs => { + + let canceled = false; + + (async () => { + const bplistPaths = paths.filter(p => this.acceptPlaylistFiletype(p)); + const progress: Progression = { current: 0, total: bplistPaths.length, data: null }; + + obs.next(progress); + + for(const playlistPath of bplistPaths) { + if(canceled) { + log.info("Playlist import canceled"); + return; + } + + const { result: localBPList, error } = await tryit(() => this.installBPListFile({ bplistSource: playlistPath, version })); + + if(error) { + progress.lastError = serializeError(CustomError.fromError(error)); + obs.next(progress); + + log.error(error); + continue; + } + + const bpListDetails = this.getLocalBPListDetails(localBPList.localBPList); + + progress.current += 1; + progress.data = bpListDetails; + obs.next(progress); + } + })() + .catch(err => obs.error(err)) + .finally(() => obs.complete()); + + return () => { + canceled = true; + } + }); + } + public oneClickInstallPlaylist(bplistUrl: string): Observable> { return new Observable>(obs => { diff --git a/src/main/services/additional-content/maps/local-maps-manager.service.ts b/src/main/services/additional-content/maps/local-maps-manager.service.ts index 8036141fa..31de5b95f 100644 --- a/src/main/services/additional-content/maps/local-maps-manager.service.ts +++ b/src/main/services/additional-content/maps/local-maps-manager.service.ts @@ -348,14 +348,15 @@ export class LocalMapsManagerService { const zip = new StreamZip.async({ file: zipPath }); const { result: zipEntries, error } = await tryit(() => zip.entries()); - const zipEntriesValues = Object.values(zipEntries); if(error) { - log.error("Could not read zip entries", zipPath, error); - await zip.close(); + const res = await tryit(() => zip.close()); + log.error("Could not read zip entries", zipPath, error, res?.error); continue; } + const zipEntriesValues = Object.values(zipEntries); + const mapsFolders = zipEntriesValues.reduce((acc, entry) => { if(!/(^|\/)[Ii]nfo\.dat$/.test(entry.name)){ return acc; } acc.push(path.dirname(entry.name)); diff --git a/src/renderer/components/maps-playlists-panel/maps-playlists-panel.component.tsx b/src/renderer/components/maps-playlists-panel/maps-playlists-panel.component.tsx index 9aa29c5a5..313081196 100644 --- a/src/renderer/components/maps-playlists-panel/maps-playlists-panel.component.tsx +++ b/src/renderer/components/maps-playlists-panel/maps-playlists-panel.component.tsx @@ -10,7 +10,6 @@ import { useTranslation } from "renderer/hooks/use-translation.hook"; import { FolderLinkState } from "renderer/services/version-folder-linker.service"; import { useService } from "renderer/hooks/use-service.hook"; import { BsContentTabPanel } from "../shared/bs-content-tab-panel/bs-content-tab-panel.component"; -import { BsmButton } from "../shared/bsm-button.component"; import { MapIcon } from "../svgs/icons/map-icon.component"; import { PlaylistIcon } from "../svgs/icons/playlist-icon.component"; import { useObservable } from "renderer/hooks/use-observable.hook"; @@ -24,7 +23,6 @@ import { PlaylistDownloaderService } from "renderer/services/playlist-downloader import { LocalPlaylistFilter, LocalPlaylistFilterPanel } from "./playlists/local-playlist-filter-panel.component"; import { noop } from "shared/helpers/function.helpers"; import { Dropzone } from "../shared/dropzone.component"; -import { NotificationService } from "renderer/services/notification.service"; import { logRenderError } from "renderer"; type Props = { @@ -45,11 +43,13 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) { const mapsDownloader = useService(MapsDownloaderService); const playlistsManager = useService(PlaylistsManagerService); const playlistsDownloader = useService(PlaylistDownloaderService); - const notifications = useService(NotificationService); const t = useTranslation(); const [tabIndex, setTabIndex] = useState(0); + const [mapsDropZoneOpen, setMapsDropZoneOpen] = useState(false); + const [playlistsDropZoneOpen, setPlaylistsDropZoneOpen] = useState(false); + const maps$ = useConstant(() => new BehaviorSubject(undefined)); const playlists$ = useConstant(() => new BehaviorSubject(undefined)); @@ -77,11 +77,16 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) { return playlistsManager.$playlistsFolderLinkState(version); }, FolderLinkState.Unlinked, [version]); - const handleAddClick = () => { + const handleBrowse = () => { switch (tabIndex) { - case 0: return mapsDownloader.openDownloadMapModal(version, maps$.value); - case 1: return playlistsDownloader.openDownloadPlaylistModal(version, playlists$, maps$); - default: return noop(); + case 0: + mapsDownloader.openDownloadMapModal(version, maps$.value); + return; + case 1: + playlistsDownloader.openDownloadPlaylistModal(version, playlists$, maps$); + return; + default: + return noop(); } } @@ -105,27 +110,31 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) { return playlistsManager.unlinkVersion(version); } - const handleFileDrop = async (files: FileList) => { - const zipMimeTypes = ["application/zip", "application/zip-compressed", "application/x-zip-compressed"]; - const paths: string[] = Array.from(files).reduce((acc, file) => { - if (zipMimeTypes.includes(file.type)) { - acc.push(window.electron.webUtils.getPathForFile(file)); - } - return acc; - }, []); - - if (paths.length === 0) { - notifications.notifyError({ - title: "notifications.maps.import-map.titles.error", - desc: "notifications.maps.import-map.msgs.only-accept-zip" - }); - return; - } + const importMaps = async (paths: string[]) => { + setMapsDropZoneOpen(() => false); + return lastValueFrom(mapsManager.importMaps(paths, version)).catch(logRenderError); + } - const import$ = mapsManager.importMaps(paths, version); - return lastValueFrom(import$).catch(logRenderError); + const importPlaylists = async (paths: string[]) => { + setPlaylistsDropZoneOpen(() => false); + return lastValueFrom(playlistsManager.importPlaylists({ version, paths })).catch(logRenderError); } + const addDropDownItems = ((): DropDownItem[] => { + if(tabIndex === 0){ + return [ + { icon: "browse", text: "pages.version-viewer.maps.tabs.maps.actions.drop-down.browse-maps", onClick: handleBrowse }, + { icon: "download", text: "pages.version-viewer.maps.tabs.maps.actions.drop-down.import-maps", onClick: () => setMapsDropZoneOpen(true) } + ]; + } + + return [ + { icon: "browse", text: "pages.version-viewer.maps.tabs.playlists.drop-down.browse-playlists", onClick: handleBrowse }, + { icon: "add-file", text: "pages.version-viewer.maps.tabs.playlists.drop-down.create-a-playlist", onClick: () => playlistsRef?.current?.createPlaylist?.() }, + { icon: "download", text: "pages.version-viewer.maps.tabs.playlists.drop-down.import-playlists", onClick: () => setPlaylistsDropZoneOpen(true) } + ]; + })(); + const dropDownItems = ((): DropDownItem[] => { if (tabIndex === 0) { return [ @@ -136,7 +145,6 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) { } return [ - { icon: "add", text: t("playlist.create-a-playlist"), onClick: () => playlistsRef?.current?.createPlaylist?.() }, { icon: "sync", text: t("playlist.synchronize-playlists"), onClick: () => playlistsRef?.current?.syncPlaylists?.() }, { icon: "export", text: t("playlist.export-playlists"), onClick: () => playlistsRef?.current?.exportPlaylists?.() }, { icon: "trash", text: t("playlist.delete-playlists"), onClick: () => playlistsRef?.current?.deletePlaylists?.() }, @@ -146,13 +154,20 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) { return ( <>