diff --git a/.gitignore b/.gitignore index ef6f2ff47..fd445f0f8 100644 --- a/.gitignore +++ b/.gitignore @@ -34,7 +34,7 @@ npm-debug.log.* /flashpoint.sqlite /extensions/test /src/shared/version.ts -Data/flashpoint.sqlite +Data # Jest related files /.coveralls.yml diff --git a/lang/cs-CZ.json b/lang/cs-CZ.json index 6e295ba52..eef5fa2f0 100644 --- a/lang/cs-CZ.json +++ b/lang/cs-CZ.json @@ -380,7 +380,8 @@ "openGameDataBrowser": "Otevřít prohlížeč herních dat", "notArchived": "Nearchivováno", "archived": "Archivováno", - "playOnline": "Hrát online" + "playOnline": "Hrát online", + "tagFilterIcon": "" }, "tags": { "name": "Název", @@ -403,7 +404,8 @@ "makeAliasWhenMerged": "Udělat z tohoto alias Štítku?", "deleteTag": "Odstranit Štítek", "deleteTagCategory": "Odstranit Kategorii Štítků", - "locked": "Uzamčeno během zpracovávání..." + "locked": "Uzamčeno během zpracovávání...", + "filterIcon": "" }, "curate": { "noCurationSelected": "Není vybrána žádná kurace", @@ -518,6 +520,7 @@ "misc": { "noBlankFound": "Žádn(ý/á) {0} nenalezen(a)", "addBlank": "Přidat {0}", + "removeBlank": "", "deleteAllBlankImages": "Odstranit VŠECHNY {0} k této hře", "yes": "Ano", "no": "Ne", diff --git a/lang/de-DE.json b/lang/de-DE.json index f23998f44..85958d6b0 100644 --- a/lang/de-DE.json +++ b/lang/de-DE.json @@ -380,7 +380,8 @@ "openGameDataBrowser": "Spieldaten-Browser öffnen", "notArchived": "Nicht archiviert", "archived": "Archiviert", - "playOnline": "Online spielen" + "playOnline": "Online spielen", + "tagFilterIcon": "" }, "tags": { "name": "Name", @@ -403,7 +404,8 @@ "makeAliasWhenMerged": "Dies zum Alias des Tags machen?", "deleteTag": "Tag löschen", "deleteTagCategory": "Tag-Kategorie löschen", - "locked": "Während der Verarbeitung gesperrt..." + "locked": "Während der Verarbeitung gesperrt...", + "filterIcon": "" }, "curate": { "noCurationSelected": "Keine Kuration ausgewählt", @@ -518,6 +520,7 @@ "misc": { "noBlankFound": "Kein {0} gefunden", "addBlank": "{0} hinzufügen", + "removeBlank": "", "deleteAllBlankImages": "Lösche ALLE {0} Bilder für dieses Spiel", "yes": "Ja", "no": "Nein", diff --git a/lang/en.json b/lang/en.json index f0b49b493..8efc890a6 100644 --- a/lang/en.json +++ b/lang/en.json @@ -381,7 +381,8 @@ "openGameDataBrowser": "Open Game Data Browser", "notArchived": "Not Archived", "archived": "Archived", - "playOnline": "Play Online" + "playOnline": "Play Online", + "tagFilterIcon": "Tag Filter Icon" }, "tags": { "name": "Name", @@ -404,7 +405,8 @@ "makeAliasWhenMerged": "Make this an alias of the tag?", "deleteTag": "Delete Tag", "deleteTagCategory": "Delete Tag Category", - "locked": "Locked while processing..." + "locked": "Locked while processing...", + "filterIcon": "Icon Thumbnail" }, "curate": { "noCurationSelected": "No Curation Selected", @@ -519,6 +521,7 @@ "misc": { "noBlankFound": "No {0} Found", "addBlank": "Add {0}", + "removeBlank": "Remove {0}", "deleteAllBlankImages": "Delete ALL {0} images for this game", "yes": "Yes", "no": "No", diff --git a/lang/es-ES.json b/lang/es-ES.json index 89dcd83da..78abab0a5 100644 --- a/lang/es-ES.json +++ b/lang/es-ES.json @@ -380,7 +380,8 @@ "openGameDataBrowser": "Open Game Data Browser", "notArchived": "Not Archived", "archived": "Archived", - "playOnline": "Play Online" + "playOnline": "Play Online", + "tagFilterIcon": "" }, "tags": { "name": "Nombre", @@ -403,7 +404,8 @@ "makeAliasWhenMerged": "¿Hacer esto una alias de la etiqueta?", "deleteTag": "Eliminar etiqueta", "deleteTagCategory": "Eliminar categoría de etiqueta", - "locked": "Bloqueado mientras se procesa..." + "locked": "Bloqueado mientras se procesa...", + "filterIcon": "" }, "curate": { "noCurationSelected": "Ninguna curación seleccionada", @@ -518,6 +520,7 @@ "misc": { "noBlankFound": "Ninguna {0} Encontrada", "addBlank": "Añadir {0}", + "removeBlank": "Remove {0}", "deleteAllBlankImages": "Borrar todas las {0} imágenes para este juego", "yes": "Sí", "no": "No \t", diff --git a/lang/et-EE.json b/lang/et-EE.json index e3599df4e..2560a65e7 100644 --- a/lang/et-EE.json +++ b/lang/et-EE.json @@ -380,7 +380,8 @@ "openGameDataBrowser": "Ava Mängu Andmete Brauser", "notArchived": "Pole Arhiveeritud", "archived": "Arhiveeritud", - "playOnline": "Mängi võrgus" + "playOnline": "Mängi võrgus", + "tagFilterIcon": "" }, "tags": { "name": "Nimi", @@ -403,7 +404,8 @@ "makeAliasWhenMerged": "Kas muuta see uueks märksõna alternatiiviks?", "deleteTag": "Kustuta Märksõna", "deleteTagCategory": "Kustuta Märksõna Kategooria", - "locked": "Lukus protsessi ajal..." + "locked": "Lukus protsessi ajal...", + "filterIcon": "" }, "curate": { "noCurationSelected": "Ühtegi Kureeringut Pole Valitud", @@ -518,6 +520,7 @@ "misc": { "noBlankFound": "{0} ei Leitud", "addBlank": "Lisa {0}", + "removeBlank": "", "deleteAllBlankImages": "Kustuta KÕIK {0} pildid sellel mängul", "yes": "Jah", "no": "Ei", diff --git a/lang/fi-FI.json b/lang/fi-FI.json index efe0a7af3..c8dcbb7a4 100644 --- a/lang/fi-FI.json +++ b/lang/fi-FI.json @@ -380,7 +380,8 @@ "openGameDataBrowser": "Avaa Pelitietojen Selain", "notArchived": "Ei Arkistoitu", "archived": "Arkistoitu", - "playOnline": "Pelaa verkossa" + "playOnline": "Pelaa verkossa", + "tagFilterIcon": "" }, "tags": { "name": "Nimi", @@ -403,7 +404,8 @@ "makeAliasWhenMerged": "Tee tästä tunnisteen alias?", "deleteTag": "Poista tunniste", "deleteTagCategory": "Poista tunnistekategoria", - "locked": "Lukittu käsittelyn aikana..." + "locked": "Lukittu käsittelyn aikana...", + "filterIcon": "" }, "curate": { "noCurationSelected": "Ei Kuraatiota Valittu", @@ -518,6 +520,7 @@ "misc": { "noBlankFound": "{0} ei löydy", "addBlank": "Lisää {0}", + "removeBlank": "", "deleteAllBlankImages": "Tuhoa kaikki {0} kuvaa tästä pelistä", "yes": "Kyllä", "no": "Ei", diff --git a/lang/fr-FR.json b/lang/fr-FR.json index a516428f0..f3a356ef8 100644 --- a/lang/fr-FR.json +++ b/lang/fr-FR.json @@ -380,7 +380,8 @@ "openGameDataBrowser": "Ouvrir le navigateur de données du jeu", "notArchived": "Not Archived", "archived": "Archived", - "playOnline": "Play Online" + "playOnline": "Play Online", + "tagFilterIcon": "" }, "tags": { "name": "Nom", @@ -403,7 +404,8 @@ "makeAliasWhenMerged": "Faire un alias de ce tag ?", "deleteTag": "Supprimer le tag", "deleteTagCategory": "Supprimer la catégorie tag", - "locked": "Protégé pendant le traitement..." + "locked": "Protégé pendant le traitement...", + "filterIcon": "" }, "curate": { "noCurationSelected": "Aucune conservation sélectionnée", @@ -518,6 +520,7 @@ "misc": { "noBlankFound": "Aucune {0} trouvée", "addBlank": "Ajouter une {0}", + "removeBlank": "", "deleteAllBlankImages": "Supprimer TOUTES les {0} pour ce jeu", "yes": "Oui", "no": "Non", diff --git a/lang/hu-HU.json b/lang/hu-HU.json index 5a9bb674a..fe1d44e37 100644 --- a/lang/hu-HU.json +++ b/lang/hu-HU.json @@ -380,7 +380,8 @@ "openGameDataBrowser": "Játékadat Böngésző Megnyitása", "notArchived": "Not Archived", "archived": "Archived", - "playOnline": "Play Online" + "playOnline": "Play Online", + "tagFilterIcon": "" }, "tags": { "name": "Név", @@ -403,7 +404,8 @@ "makeAliasWhenMerged": "Kíván nevet készíteni a címkéből?", "deleteTag": "Címke törlése", "deleteTagCategory": "Címke kategória törlése", - "locked": "Zárolva a feldolgozás alatt.." + "locked": "Zárolva a feldolgozás alatt..", + "filterIcon": "" }, "curate": { "noCurationSelected": "Nincsen kiválasztott Kurátor", @@ -518,6 +520,7 @@ "misc": { "noBlankFound": "Nem Található", "addBlank": "Hozzáad", + "removeBlank": "", "deleteAllBlankImages": "Összes kép törlése ehhez a játékhoz", "yes": "Igen", "no": "Nem", diff --git a/lang/it-IT.json b/lang/it-IT.json index cd5c0f63b..094845890 100644 --- a/lang/it-IT.json +++ b/lang/it-IT.json @@ -380,7 +380,8 @@ "openGameDataBrowser": "Apri il browser dei dati di gioco", "notArchived": "Non Archiviato", "archived": "Archiviato", - "playOnline": "Gioca in Rete" + "playOnline": "Gioca in Rete", + "tagFilterIcon": "" }, "tags": { "name": "Nome", @@ -403,7 +404,8 @@ "makeAliasWhenMerged": "Imposta come alias dell'etichetta?", "deleteTag": "Elimina l'Etichetta", "deleteTagCategory": "Elimina la Categoria Etichette", - "locked": "Bloccato durante l'elaborazione..." + "locked": "Bloccato durante l'elaborazione...", + "filterIcon": "" }, "curate": { "noCurationSelected": "Nessun Contenuto Curato Selezionato", @@ -518,6 +520,7 @@ "misc": { "noBlankFound": "Nessuna {0} Trovata", "addBlank": "Aggiungi {0}", + "removeBlank": "", "deleteAllBlankImages": "Elimina TUTTE le immagini {0} per questo gioco", "yes": "Sì", "no": "No", diff --git a/lang/ja-JP.json b/lang/ja-JP.json index 75a04b1e2..9a741cbba 100644 --- a/lang/ja-JP.json +++ b/lang/ja-JP.json @@ -380,7 +380,8 @@ "openGameDataBrowser": "ゲームデータブラウザを開く", "notArchived": "アーカイブに保存していない", "archived": "アーカイブに保存している", - "playOnline": "オンラインでプレイ" + "playOnline": "オンラインでプレイ", + "tagFilterIcon": "" }, "tags": { "name": "名前", @@ -403,7 +404,8 @@ "makeAliasWhenMerged": "タグのエイリアスにしますか?", "deleteTag": "タグを削除", "deleteTagCategory": "タグカテゴリを削除", - "locked": "処理中にロックしています..." + "locked": "処理中にロックしています...", + "filterIcon": "" }, "curate": { "noCurationSelected": "キュレーションが選択されていません", @@ -518,6 +520,7 @@ "misc": { "noBlankFound": "{0} が見つかりませんでした", "addBlank": "{0} を追加", + "removeBlank": "", "deleteAllBlankImages": "このゲームの {0} 枚の画像をすべて削除する", "yes": "はい", "no": "いいえ", diff --git a/lang/mk-MK.json b/lang/mk-MK.json index 7d838731a..50606b34a 100644 --- a/lang/mk-MK.json +++ b/lang/mk-MK.json @@ -380,7 +380,8 @@ "openGameDataBrowser": "Отворете го прелистувачот за податоци за игри", "notArchived": "Не е Архивирано", "archived": "Архивиран", - "playOnline": "Играј online" + "playOnline": "Играј online", + "tagFilterIcon": "" }, "tags": { "name": "Име", @@ -403,7 +404,8 @@ "makeAliasWhenMerged": "Направи алијас од ознаката?", "deleteTag": "Избриши ознака", "deleteTagCategory": "Избриши ја Категоријата на Ознаката", - "locked": "Заклучено додека се обработува..." + "locked": "Заклучено додека се обработува...", + "filterIcon": "" }, "curate": { "noCurationSelected": "Никаква Курација Селектирана", @@ -518,6 +520,7 @@ "misc": { "noBlankFound": "Нема {0} пронајдено", "addBlank": "Додади {0}", + "removeBlank": "", "deleteAllBlankImages": "Избриши сите {0} слики од таа игра", "yes": "Да", "no": "Не", diff --git a/lang/pl-PL.json b/lang/pl-PL.json index 5a542a37b..2e2e59527 100644 --- a/lang/pl-PL.json +++ b/lang/pl-PL.json @@ -380,7 +380,8 @@ "openGameDataBrowser": "Otwórz przeglądarkę danych gry", "notArchived": "Nie zarchiwizowano", "archived": "Zarchiwizowano", - "playOnline": "Zagraj online" + "playOnline": "Zagraj online", + "tagFilterIcon": "" }, "tags": { "name": "Nazwa", @@ -403,7 +404,8 @@ "makeAliasWhenMerged": "Ustawić to jako alias tagu?", "deleteTag": "Usuń tag", "deleteTagCategory": "Usuń kategorię tagu", - "locked": "Zablokowano podczas przetwarzania..." + "locked": "Zablokowano podczas przetwarzania...", + "filterIcon": "" }, "curate": { "noCurationSelected": "Nie wybrano kuracji", @@ -518,6 +520,7 @@ "misc": { "noBlankFound": "Nie znaleziono {0}", "addBlank": "Dodaj {0}", + "removeBlank": "", "deleteAllBlankImages": "Usuń WSZYSTKIE {0} zdjęcia tej gry", "yes": "Tak", "no": "Nie", diff --git a/lang/pt-BR.json b/lang/pt-BR.json index 6462c41a0..790e3f2b6 100644 --- a/lang/pt-BR.json +++ b/lang/pt-BR.json @@ -380,7 +380,8 @@ "openGameDataBrowser": "Abrir Navegador de Dados de Jogo", "notArchived": "Não Arquivado", "archived": "Arquivado", - "playOnline": "Jogar Online" + "playOnline": "Jogar Online", + "tagFilterIcon": "" }, "tags": { "name": "Nome", @@ -403,7 +404,8 @@ "makeAliasWhenMerged": "Tornar isso um apelido da tag?", "deleteTag": "Deletar Tag", "deleteTagCategory": "Deletar Categoria de Tag", - "locked": "Bloqueado durante o processamento..." + "locked": "Bloqueado durante o processamento...", + "filterIcon": "" }, "curate": { "noCurationSelected": "Nenhuma Curação Selecionada", @@ -518,6 +520,7 @@ "misc": { "noBlankFound": "Nenhuma {0} Encontrada", "addBlank": "Adicionar {0}", + "removeBlank": "", "deleteAllBlankImages": "Deletar TODAS as {0} imagens para este jogo", "yes": "Sim", "no": "Não", diff --git a/lang/ro-RO.json b/lang/ro-RO.json index 9d6911764..8b9c4c528 100644 --- a/lang/ro-RO.json +++ b/lang/ro-RO.json @@ -380,7 +380,8 @@ "openGameDataBrowser": "Explorați datele jocului", "notArchived": "Not Archived", "archived": "Arhivat", - "playOnline": "Jucați online" + "playOnline": "Jucați online", + "tagFilterIcon": "" }, "tags": { "name": "Nume", @@ -403,7 +404,8 @@ "makeAliasWhenMerged": "Sigur doriți să stabiliți acest pseudonim ca numele etichetei?", "deleteTag": "Ştergeți eticheta", "deleteTagCategory": "Ștergeți categoria de etichete", - "locked": "Blocate în timpul procesării..." + "locked": "Blocate în timpul procesării...", + "filterIcon": "" }, "curate": { "noCurationSelected": "Nicio reparație selectată", @@ -518,6 +520,7 @@ "misc": { "noBlankFound": "Nu s-a găsit {0}", "addBlank": "Adaugă o imagine...", + "removeBlank": "", "deleteAllBlankImages": "Șterge toate imaginile pentru acest joc", "yes": "Da", "no": "Nu", diff --git a/lang/ru-RU.json b/lang/ru-RU.json index 425cd8be6..75f887a5b 100644 --- a/lang/ru-RU.json +++ b/lang/ru-RU.json @@ -380,7 +380,8 @@ "openGameDataBrowser": "Открыть браузер игровых данных", "notArchived": "Не в архиве", "archived": "В архиве", - "playOnline": "Играть в браузере" + "playOnline": "Играть в браузере", + "tagFilterIcon": "" }, "tags": { "name": "Наименование", @@ -403,7 +404,8 @@ "makeAliasWhenMerged": "Сделать это псевдонимомом тега?", "deleteTag": "Удалить тег", "deleteTagCategory": "Удалить категорию тега", - "locked": "Заблокировано во время обработки..." + "locked": "Заблокировано во время обработки...", + "filterIcon": "" }, "curate": { "noCurationSelected": "Кураций не выбрано", @@ -518,6 +520,7 @@ "misc": { "noBlankFound": "{0} не найден", "addBlank": "Добавить {0}", + "removeBlank": "", "deleteAllBlankImages": "Удалить все изображения {0} для этой игры", "yes": "Да", "no": "Нет", diff --git a/lang/tr-TR.json b/lang/tr-TR.json index 19a7bc3dd..e492db2a8 100644 --- a/lang/tr-TR.json +++ b/lang/tr-TR.json @@ -380,7 +380,8 @@ "openGameDataBrowser": "Oyun Verileri Tarayıcısını Aç", "notArchived": "Arşivlenmedi", "archived": "Arşivlendi", - "playOnline": "Çevrim içi Oyna" + "playOnline": "Çevrim içi Oyna", + "tagFilterIcon": "" }, "tags": { "name": "Adı", @@ -403,7 +404,8 @@ "makeAliasWhenMerged": "Bunu etiketin takma bir ismi yap?", "deleteTag": "Etiketi Sil", "deleteTagCategory": "Etiket Kategorisini Sil", - "locked": "İşlenirken kilitlendi..." + "locked": "İşlenirken kilitlendi...", + "filterIcon": "" }, "curate": { "noCurationSelected": "Küratör Seçilmedi", @@ -518,6 +520,7 @@ "misc": { "noBlankFound": "{0} Bulunamadı", "addBlank": "{0} Ekle", + "removeBlank": "", "deleteAllBlankImages": "Bu oyun için olan TÜM {0} resimlerini sil", "yes": "Evet", "no": "Hayır", diff --git a/lang/vi-VN.json b/lang/vi-VN.json index d8ae693b2..5616768d8 100644 --- a/lang/vi-VN.json +++ b/lang/vi-VN.json @@ -380,7 +380,8 @@ "openGameDataBrowser": "Mở trình duyệt dữ liệu trò chơi", "notArchived": "Not Archived", "archived": "Archived", - "playOnline": "Chơi trực tuyến" + "playOnline": "Chơi trực tuyến", + "tagFilterIcon": "" }, "tags": { "name": "Tên", @@ -403,7 +404,8 @@ "makeAliasWhenMerged": "Đặt làm tên khác của nhãn?", "deleteTag": "Xoá nhãn", "deleteTagCategory": "Xoá danh mục nhãn", - "locked": "Bị khoá khi đang xử lí..." + "locked": "Bị khoá khi đang xử lí...", + "filterIcon": "" }, "curate": { "noCurationSelected": "Chưa có bản đóng góp nào", @@ -518,6 +520,7 @@ "misc": { "noBlankFound": "Không tìm thấy {0}", "addBlank": "Thêm {0}", + "removeBlank": "", "deleteAllBlankImages": "Xoá TOÀN BỘ {0} hình của trò chơi này", "yes": "Có", "no": "Không", diff --git a/lang/zh-CN.json b/lang/zh-CN.json index 96eba3d19..122e46495 100644 --- a/lang/zh-CN.json +++ b/lang/zh-CN.json @@ -380,7 +380,8 @@ "openGameDataBrowser": "打开游戏数据浏览器", "notArchived": "未存档", "archived": "已存档", - "playOnline": "在线播放" + "playOnline": "在线播放", + "tagFilterIcon": "" }, "tags": { "name": "名称", @@ -403,7 +404,8 @@ "makeAliasWhenMerged": "设置为标签的别名吗?", "deleteTag": "删除标签", "deleteTagCategory": "删除标签类别", - "locked": "处理过程中暂时锁定..." + "locked": "处理过程中暂时锁定...", + "filterIcon": "" }, "curate": { "noCurationSelected": "未选择展品", @@ -518,6 +520,7 @@ "misc": { "noBlankFound": "无{0}", "addBlank": "添加{0}", + "removeBlank": "", "deleteAllBlankImages": "删除该游戏所有{0}", "yes": "是", "no": "否", diff --git a/lang/zh-TW.json b/lang/zh-TW.json index a30c9b640..eda9ca0ce 100644 --- a/lang/zh-TW.json +++ b/lang/zh-TW.json @@ -380,7 +380,8 @@ "openGameDataBrowser": "Open Game Data Browser", "notArchived": "Not Archived", "archived": "Archived", - "playOnline": "Play Online" + "playOnline": "Play Online", + "tagFilterIcon": "" }, "tags": { "name": "名稱", @@ -403,7 +404,8 @@ "makeAliasWhenMerged": "將此標籤設為該標籤的別名嗎?", "deleteTag": "刪除標籤", "deleteTagCategory": "刪除標籤類別", - "locked": "系統處理時將鎖定操作..." + "locked": "系統處理時將鎖定操作...", + "filterIcon": "" }, "curate": { "noCurationSelected": "未選取任何典藏作品。", @@ -518,6 +520,7 @@ "misc": { "noBlankFound": "無法找到{0} ", "addBlank": "新增 {0}", + "removeBlank": "", "deleteAllBlankImages": "刪除此遊戲共 {0} 張圖片", "yes": "是", "no": "否", diff --git a/package-lock.json b/package-lock.json index 5d48e6ee6..44ba772c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "flashpoint-launcher", - "version": "13.0.1", + "version": "13.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "flashpoint-launcher", - "version": "13.0.1", + "version": "13.0.2", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/src/renderer/components/GameGrid.tsx b/src/renderer/components/GameGrid.tsx index 50f088f02..2429b41de 100644 --- a/src/renderer/components/GameGrid.tsx +++ b/src/renderer/components/GameGrid.tsx @@ -9,6 +9,7 @@ import { GameGridItem } from './GameGridItem'; import { GameItemContainer } from './GameItemContainer'; import { GameDragEventData } from './pages/BrowsePage'; import { ScreenshotPreviewMode } from '@shared/BrowsePageLayout'; +import { TagFilter } from 'flashpoint-launcher'; const RENDERER_OVERSCAN = 5; @@ -36,6 +37,8 @@ export type GameGridProps = { cellHeight: number; /** List of Extreme tags */ extremeTags: string[]; + /** Tag Filter icons */ + tagGroupIcons: { tagFilter: TagFilter; iconBase64: string; }[]; /** Function that renders the elements to show instead of the grid if there are no games (render prop). */ noRowsRenderer?: () => JSX.Element; /** Called when the user attempts to select a game. */ @@ -191,6 +194,7 @@ export class GameGrid extends React.Component { const index = props.rowIndex * this.columns + props.columnIndex; if (index < (gamesTotal || 0)) { const game = games[index]; + const tagGroupIcon = this.props.tagGroupIcons.find(tg => tg.tagFilter.find(t => game?.tags.includes(t)))?.iconBase64; return ( { platforms={game ? [game.primaryPlatform] : []} extreme={game ? game.tags.findIndex(t => this.props.extremeTags.includes(t.trim())) !== -1 : false} extremeIconPath={extremeIconPath} + tagGroupIconBase64={tagGroupIcon || ''} thumbnail={game ? getGameImageURL(LOGOS, game.id) : ''} screenshot={game ? getGameImageURL(SCREENSHOTS, game.id) : ''} screenshotPreviewMode={this.props.screenshotPreviewMode} diff --git a/src/renderer/components/GameGridItem.tsx b/src/renderer/components/GameGridItem.tsx index e0080ab3a..95265c164 100644 --- a/src/renderer/components/GameGridItem.tsx +++ b/src/renderer/components/GameGridItem.tsx @@ -24,6 +24,8 @@ export type GameGridItemProps = Partial & { isDragged: boolean; /** Path to the extreme icon */ extremeIconPath: string; + /** Icon for games in tag categories */ + tagGroupIconBase64: string; /** On Drop event */ onDrop?: (event: React.DragEvent) => void; /** Screenshot Preview Mode */ @@ -55,7 +57,7 @@ export function GameGridItem(props: GameGridItemProps) { } }, [isHovered]); - const { rowIndex, id, title, platforms, thumbnail, screenshot, extreme, isDraggable, isSelected, isDragged, extremeIconPath, style, onDrop } = props; + const { rowIndex, id, title, platforms, thumbnail, screenshot, extreme, tagGroupIconBase64, isDraggable, isSelected, isDragged, extremeIconPath, style, onDrop } = props; // Get the platform icon path const platformIcons = React.useMemo(() => platforms.slice(0, 5).map(p => getPlatformIconURL(p, props.logoVersion)) @@ -93,7 +95,13 @@ export function GameGridItem(props: GameGridItemProps) { className='game-grid-item__thumb__icons__icon' style={{ backgroundImage: `url('${extremeIconPath}')` }} /> - ) : undefined } + ) : (tagGroupIconBase64 ? ( +
+
+
+ ) : undefined )}
{platformIcons.map(p => (
= (instance: T | null) => void; @@ -32,6 +34,8 @@ export type OwnProps = { showExtremeIcon: boolean; /** Extreme Tag Filters */ extremeTags: string[]; + /** Tag Filter icons */ + tagGroupIcons: { tagFilter: TagFilter; iconBase64: string; }[]; /** Function that renders the elements to show instead of the grid if there are no games (render prop). */ noRowsRenderer?: () => JSX.Element; /** Called when the user attempts to select a game. */ @@ -193,6 +197,7 @@ class _GameList extends React.Component { if (!games) { throw new Error('Trying to render a row in game list, but no games are found?'); } const game = games[cellProps.index]; const platform = game?.primaryPlatform; + const tagGroupIcon = this.props.tagGroupIcons.find(tg => tg.tagFilter.find(t => game?.tags.includes(t)))?.iconBase64; return game ? ( { extreme={game.tags.findIndex(t => this.props.extremeTags.includes(t.trim())) !== -1} extremeIconPath={extremeIconPath} showExtremeIcon={showExtremeIcon} + tagGroupIconBase64={tagGroupIcon || ''} logoVersion={this.props.logoVersion} isDraggable={true} isSelected={game.id === selectedGameId} diff --git a/src/renderer/components/GameListItem.tsx b/src/renderer/components/GameListItem.tsx index 2369a41af..442ec098b 100644 --- a/src/renderer/components/GameListItem.tsx +++ b/src/renderer/components/GameListItem.tsx @@ -24,13 +24,15 @@ export type GameListItemProps = ListRowProps & { isDragged: boolean; /** Path to the extreme icon */ extremeIconPath: string; + /** Icon for games in tag categories */ + tagGroupIconBase64: string; /** Game drag event */ onDrop?: (event: React.DragEvent) => void; onDragOver?: (event: React.DragEvent) => void; }; export function GameListItem(props: GameListItemProps) { - const { id, title, platform, tags, developer, publisher, extreme, isDraggable, isSelected, isDragged, extremeIconPath, showExtremeIcon, index, style, onDrop, + const { id, title, platform, tags, developer, publisher, extreme, tagGroupIconBase64, isDraggable, isSelected, isDragged, extremeIconPath, showExtremeIcon, index, style, onDrop, onDragOver } = props; // Get the platform icon path const platformIcon = React.useMemo(() => ( @@ -64,9 +66,13 @@ export function GameListItem(props: GameListItemProps) {
+ ) : (tagGroupIconBase64 ? ( +
) : (
- )) + ))) }
void; rollRandomGames: () => void; extremeTags: string[]; + /** Tag Filter icons */ + tagGroupIcons: { tagFilter: TagFilter; iconBase64: string; }[]; /** Update to clear platform icon cache */ logoVersion: number; minimized: boolean; @@ -58,6 +60,7 @@ export function RandomGames(props: RandomGamesProps) { platforms={game.platforms.map(p => p.trim())} extreme={game ? game.tags.findIndex(t => props.extremeTags.includes(t.trim())) !== -1 : false} extremeIconPath={getExtremeIconURL(props.logoVersion)} + tagGroupIconBase64={props.tagGroupIcons.find(tg => tg.tagFilter.find(t => game?.tags.includes(t)))?.iconBase64 || ''} thumbnail={getGameImageURL(LOGOS, game.id)} screenshot={getGameImageURL(SCREENSHOTS, game.id)} screenshotPreviewMode={props.screenshotPreviewMode} diff --git a/src/renderer/components/TagFilterGroupEditor.tsx b/src/renderer/components/TagFilterGroupEditor.tsx index d30eb58ab..11187c475 100644 --- a/src/renderer/components/TagFilterGroupEditor.tsx +++ b/src/renderer/components/TagFilterGroupEditor.tsx @@ -7,6 +7,8 @@ import { CheckBox } from './CheckBox'; import { InputField } from './InputField'; import { OpenIcon } from './OpenIcon'; import { TagInputField } from './TagInputField'; +import { SimpleButton } from './SimpleButton'; +import { formatString } from '@shared/utils/StringFormatter'; export type TagFilterGroupEditorProps = { tagFilterGroup: TagFilterGroup; @@ -17,6 +19,7 @@ export type TagFilterGroupEditorProps = { onChangeName: (name: string) => void; onChangeDescription: (description: string) => void; onToggleExtreme: (checked: boolean) => void; + onChangeIconBase64: (iconBase64: string) => void; closeEditor: () => void; showExtreme: boolean; tagCategories: TagCategory[]; @@ -30,6 +33,8 @@ export function TagFilterGroupEditor(props: TagFilterGroupEditorProps) { const [tagSuggestions, setTagSuggestions] = React.useState([]); const [parsedTagsList, setParsedTagsList] = React.useState(buildPlaceholderTags(props.tagFilterGroup.tags)); + var imgTagFilterIconInput: HTMLInputElement; + // const tags = React.useMemo(() => tagsFactory(props.tagFilterGroup.tags, props.onRemoveTag), [props.tagFilterGroup.tags, props.onRemoveTag]); // const categories = React.useMemo(() => categoriesFactory(props.tagFilterGroup.categories, props.onRemoveCategory), [props.tagFilterGroup.categories, props.onRemoveCategory]); @@ -138,6 +143,41 @@ export function TagFilterGroupEditor(props: TagFilterGroupEditorProps) { onTagSubmit={(tag) => onTagSubmit(tag)} />
+
+ {strings.tags.filterIcon} +
+
+ { + props.tagFilterGroup.iconBase64 ? ( + <> +
+ props.onChangeIconBase64('')} /> + + ) : ( + imgTagFilterIconInput && imgTagFilterIconInput.click()} /> + ) + } + imgTagFilterIconInput = (ref as HTMLInputElement)} + accept='image/png' + onChange={(event) => { + let reader = new FileReader(); + reader.readAsDataURL(event.target.files![0]); + reader.onload = () => { + props.onChangeIconBase64(reader.result?.toString()!)}; + event.target.value = ''; + }} + type='file'/> +
+
);}, [parsedTagsList, editTag, tagSuggestions, props.tagCategories, onAddTag, onRemoveTag, onTagSubmit, updateSuggestions]); } diff --git a/src/renderer/components/pages/BrowsePage.tsx b/src/renderer/components/pages/BrowsePage.tsx index b3e8fb4b9..d24d36a9b 100644 --- a/src/renderer/components/pages/BrowsePage.tsx +++ b/src/renderer/components/pages/BrowsePage.tsx @@ -184,6 +184,7 @@ export class BrowsePage extends React.Component !t.enabled && t.extreme).reduce((prev, cur) => prev.concat(cur.tags), []); + const tagGroupIcons = this.props.preferencesData.tagFilters.filter(t => !t.enabled && t.iconBase64 !== '').map(({tags, iconBase64: tagGroupIcon}) => ({tagFilter:tags, iconBase64:tagGroupIcon})); // Render return (
this.onRemoveTagEditorCategoryEvent(this.state.editingTagFilterGroupIdx || -1, category)} onChangeName={this.onChangeTagEditorNameEvent} onChangeDescription={this.onChangeTagEditorDescriptionEvent} + onChangeIconBase64={this.onChangeTagEditorIconEvent} onToggleExtreme={this.onToggleExtremeTagEditorEvent} closeEditor={this.onCloseTagFilterGroupEditor} showExtreme={this.props.preferencesData.browsePageShowExtreme} @@ -602,11 +603,18 @@ export class ConfigPage extends React.Component - ) : ( + ) : (item.iconBase64 ? ( +
+ ) : + (
- )) + ))) }
{ + if (this.state.editingTagFilterGroup) { + const newTFG = {...this.state.editingTagFilterGroup, iconBase64 }; + this.setState({ editingTagFilterGroup: newTFG }); + } + }; + onDuplicateTagFilterGroup = (index: number): void => { const newTagFilters = [...this.props.preferencesData.tagFilters]; newTagFilters.push({...newTagFilters[index], name: `${newTagFilters[index].name} - Copy`}); diff --git a/src/renderer/components/pages/HomePage.tsx b/src/renderer/components/pages/HomePage.tsx index dbd36da59..d6fa8cd58 100644 --- a/src/renderer/components/pages/HomePage.tsx +++ b/src/renderer/components/pages/HomePage.tsx @@ -251,6 +251,8 @@ export function HomePage(props: HomePageProps) { ); }, [strings, props.preferencesData.minimizedHomePageBoxes, toggleMinimizeBox]); + const tagGroupIcons = props.preferencesData.tagFilters.filter(t => !t.enabled && t.iconBase64 !== '').map(({tags, iconBase64: tagGroupIcon}) => ({tagFilter:tags, iconBase64:tagGroupIcon})); + const renderedRandomGames = React.useMemo(() => ( !tfg.enabled && tfg.extreme).reduce((prev, cur) => prev.concat(cur.tags), [])} + tagGroupIcons={tagGroupIcons} logoVersion={props.logoVersion} selectedGameId={props.selectedGameId} screenshotPreviewMode={props.preferencesData.screenshotPreviewMode} @@ -313,6 +316,7 @@ export function HomePage(props: HomePageProps) { platforms={loadedGotd.platforms.map(p => p.trim())} extreme={loadedGotd.tags.findIndex(t => extremeTags.includes(t.trim())) !== -1} extremeIconPath={extremeIconPath} + tagGroupIconBase64={tagGroupIcons.find(tg => tg.tagFilter.find(t => loadedGotd?.tags.includes(t)))?.iconBase64 || ''} thumbnail={getGameImageURL(LOGOS, loadedGotd.id)} screenshot={getGameImageURL(SCREENSHOTS, loadedGotd.id)} screenshotPreviewMode={props.preferencesData.screenshotPreviewMode} diff --git a/src/shared/Util.ts b/src/shared/Util.ts index 56c0d5744..cb0d8060f 100644 --- a/src/shared/Util.ts +++ b/src/shared/Util.ts @@ -460,7 +460,8 @@ export function generateTagFilterGroup(tags?: string[]): TagFilterGroup { extreme: false, tags: tags || [], categories: [], - childFilters: [] + childFilters: [], + iconBase64: '' }; } diff --git a/src/shared/lang.ts b/src/shared/lang.ts index 134072804..55c407a7b 100644 --- a/src/shared/lang.ts +++ b/src/shared/lang.ts @@ -394,6 +394,7 @@ const langTemplate = { 'showExtremeScreenshot', 'busy', 'openGameDataBrowser', + 'tagFilterIcon' ] as const, tags: [ 'name', @@ -417,6 +418,7 @@ const langTemplate = { 'deleteTag', 'deleteTagCategory', 'locked', + 'filterIcon', ] as const, curate: [ 'noCurationSelected', @@ -532,6 +534,7 @@ const langTemplate = { misc: [ 'noBlankFound', 'addBlank', + 'removeBlank', 'deleteAllBlankImages', 'yes', 'no', @@ -747,6 +750,7 @@ export function getDefaultLocalization(): LangContainer { lang.browse.hours += ' {0}'; lang.misc.noBlankFound = ' {0} ' + lang.misc.noBlankFound; lang.misc.addBlank += ' {0}'; + lang.misc.removeBlank += ' {0}'; lang.misc.deleteAllBlankImages += ' {0}'; lang.dialog.errorImportingCuration += ' {0}'; lang.dialog.mustBe7zArchiveSkipping += ' {0}'; diff --git a/src/shared/preferences/util.ts b/src/shared/preferences/util.ts index 2589c50ea..743092cea 100644 --- a/src/shared/preferences/util.ts +++ b/src/shared/preferences/util.ts @@ -372,7 +372,8 @@ function parseTagFilterGroup(parser: IObjectParserProp): TagFilt tags: [], categories: [], childFilters: [], - extreme: false + extreme: false, + iconBase64: '' }; parser.prop('name', v => tfg.name = str(v)); parser.prop('description', v => tfg.description = str(v)); @@ -381,6 +382,7 @@ function parseTagFilterGroup(parser: IObjectParserProp): TagFilt parser.prop('categories').arrayRaw((item) => tfg.categories.push(str(item))); parser.prop('childFilters').arrayRaw((item) => tfg.childFilters.push(str(item))); parser.prop('extreme', v => tfg.extreme = !!v); + parser.prop('iconBase64', v => tfg.iconBase64 = str(v)); return tfg; } diff --git a/static/window/styles/core.css b/static/window/styles/core.css index b211eda3a..744b7944d 100644 --- a/static/window/styles/core.css +++ b/static/window/styles/core.css @@ -2943,7 +2943,6 @@ body { .tag-filter-editor__content { width: 100%; display: flex; - } .tag-filter-editor__content-section { @@ -2982,6 +2981,12 @@ body { text-align: center; font-weight: bold; } + +.tag-filter-editor__icon { + display: flex; + justify-content: center; +} + /** Checkboxes */ .checkbox { width: 3rem; diff --git a/tests/unit/back/configuration.test.ts b/tests/unit/back/configuration.test.ts index 114958015..65bc030a6 100644 --- a/tests/unit/back/configuration.test.ts +++ b/tests/unit/back/configuration.test.ts @@ -115,9 +115,10 @@ describe('Configuration Files', () => { 'tags': [ 'Action' ], - 'categories': [ 'test' ], + 'categories': ['test'], 'childFilters': [], - 'extreme': false + 'extreme': false, + iconBase64: '' } ], 'tagFiltersInCurate': true, diff --git a/typings/flashpoint-launcher.d.ts b/typings/flashpoint-launcher.d.ts index a67705f7f..d50503c26 100644 --- a/typings/flashpoint-launcher.d.ts +++ b/typings/flashpoint-launcher.d.ts @@ -918,6 +918,8 @@ declare module 'flashpoint-launcher' { childFilters: string[]; /** Are these tags considered Extreme? */ extreme: boolean; + /** Custon icon */ + iconBase64: string; } export type TagFilter = string[];