From b7cabfdbde549b75128c82ae027d4b61c83ec3ea Mon Sep 17 00:00:00 2001 From: Tasheron Date: Sat, 6 Jul 2024 14:56:35 +0300 Subject: [PATCH 1/2] feature: add collections section --- src/locales/en/translation.json | 13 +++ src/locales/ru/translation.json | 13 +++ src/main/data-source.ts | 2 + src/main/entity/collection.entity.ts | 21 ++++ src/main/entity/game.entity.ts | 5 + src/main/entity/index.ts | 1 + .../events/collections/add-collection-game.ts | 18 +++ src/main/events/collections/add-collection.ts | 14 +++ .../events/collections/get-collections.ts | 17 +++ .../collections/remove-collection-game.ts | 18 +++ .../events/collections/remove-collection.ts | 13 +++ src/main/events/index.ts | 5 + src/main/events/library/get-library.ts | 1 + src/main/repository.ts | 3 + src/preload/index.ts | 12 ++ .../src/components/sidebar/sidebar.tsx | 67 ++++++++++- src/renderer/src/declaration.d.ts | 8 ++ .../src/features/collections-slice.ts | 26 +++++ src/renderer/src/features/index.ts | 1 + src/renderer/src/hooks/index.ts | 1 + src/renderer/src/hooks/use-collections.ts | 64 +++++++++++ .../modals/collections-modal.css.ts | 25 ++++ .../game-details/modals/collections-modal.tsx | 108 ++++++++++++++++++ .../modals/game-options-modal.tsx | 27 ++++- src/renderer/src/store.ts | 2 + src/types/index.ts | 7 ++ 26 files changed, 485 insertions(+), 7 deletions(-) create mode 100644 src/main/entity/collection.entity.ts create mode 100644 src/main/events/collections/add-collection-game.ts create mode 100644 src/main/events/collections/add-collection.ts create mode 100644 src/main/events/collections/get-collections.ts create mode 100644 src/main/events/collections/remove-collection-game.ts create mode 100644 src/main/events/collections/remove-collection.ts create mode 100644 src/renderer/src/features/collections-slice.ts create mode 100644 src/renderer/src/hooks/use-collections.ts create mode 100644 src/renderer/src/pages/game-details/modals/collections-modal.css.ts create mode 100644 src/renderer/src/pages/game-details/modals/collections-modal.tsx diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index e44dc7eaf..15abbff1d 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -194,6 +194,19 @@ "found_download_option_other": "Found {{countFormatted}} download options", "import": "Import" }, + "collections": { + "collections": "Collections", + "add_the_game_to_the_collection": "Add the game to the collection", + "select_a_collection": "Select a collection", + "enter_the_name_of_the_collection": "Enter the name of the collection", + "add": "Add", + "remove": "Remove", + "you_cant_give_collections_existing_or_empty_names": "You can`t give collections existing or empty names", + "the_collection_has_been_added_successfully": "The collection has been added successfully", + "the_collection_has_been_removed_successfully": "The collection has been removed successfully", + "the_game_has_been_added_to_the_collection": "The game has been added to the collection", + "the_game_has_been_removed_from_the_collection": "The game has been removed from the collection" + }, "notifications": { "download_complete": "Download complete", "game_ready_to_install": "{{title}} is ready to install", diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 69a9c33d5..6535ffd4f 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -192,6 +192,19 @@ "found_download_option_other": "Найдено {{countFormatted}} вариантов загрузки", "import": "Импортировать" }, + "collections": { + "collections": "Коллекции", + "add_the_game_to_the_collection": "Добавьте игру в коллекцию", + "select_a_collection": "Выберите коллекцию", + "enter_the_name_of_the_collection": "Введите название коллекции", + "add": "Добавить", + "remove": "Удалить", + "you_cant_give_collections_existing_or_empty_names": "Нельзя давать коллекциям существующие или пустые названия", + "the_collection_has_been_added_successfully": "Коллекция успешно добавлена", + "the_collection_has_been_removed_successfully": "Коллекция успешно удалена", + "the_game_has_been_added_to_the_collection": "Игра добавлена в коллекцию", + "the_game_has_been_removed_from_the_collection": "Игра удалена из коллекции" + }, "notifications": { "download_complete": "Загрузка завершена", "game_ready_to_install": "{{title}} готова к установке", diff --git a/src/main/data-source.ts b/src/main/data-source.ts index b47ce2c09..4dba73b6e 100644 --- a/src/main/data-source.ts +++ b/src/main/data-source.ts @@ -1,5 +1,6 @@ import { DataSource } from "typeorm"; import { + Collection, DownloadQueue, DownloadSource, Game, @@ -19,6 +20,7 @@ export const createDataSource = ( new DataSource({ type: "better-sqlite3", entities: [ + Collection, Game, Repack, UserPreferences, diff --git a/src/main/entity/collection.entity.ts b/src/main/entity/collection.entity.ts new file mode 100644 index 000000000..b81d3cf83 --- /dev/null +++ b/src/main/entity/collection.entity.ts @@ -0,0 +1,21 @@ +import { + Entity, + PrimaryGeneratedColumn, + Column, + ManyToMany, + JoinTable, +} from "typeorm"; +import { Game } from "./game.entity"; + +@Entity("collection") +export class Collection { + @PrimaryGeneratedColumn() + id: number; + + @Column("text", { unique: true }) + title: string; + + @ManyToMany("Game", "collections") + @JoinTable() + games: Game[]; +} diff --git a/src/main/entity/game.entity.ts b/src/main/entity/game.entity.ts index 190e74701..fedd850e1 100644 --- a/src/main/entity/game.entity.ts +++ b/src/main/entity/game.entity.ts @@ -6,12 +6,14 @@ import { UpdateDateColumn, OneToOne, JoinColumn, + ManyToMany, } from "typeorm"; import { Repack } from "./repack.entity"; import type { GameShop, GameStatus } from "@types"; import { Downloader } from "@shared"; import type { DownloadQueue } from "./download-queue.entity"; +import { Collection } from "./collection.entity"; @Entity("game") export class Game { @@ -79,6 +81,9 @@ export class Game { @OneToOne("DownloadQueue", "game") downloadQueue: DownloadQueue; + @ManyToMany("Collection", "games") + collections: Collection[]; + @Column("boolean", { default: false }) isDeleted: boolean; diff --git a/src/main/entity/index.ts b/src/main/entity/index.ts index 9cb4f0444..1a79b7219 100644 --- a/src/main/entity/index.ts +++ b/src/main/entity/index.ts @@ -4,4 +4,5 @@ export * from "./user-preferences.entity"; export * from "./game-shop-cache.entity"; export * from "./download-source.entity"; export * from "./download-queue.entity"; +export * from "./collection.entity"; export * from "./user-auth"; diff --git a/src/main/events/collections/add-collection-game.ts b/src/main/events/collections/add-collection-game.ts new file mode 100644 index 000000000..3b693bcbc --- /dev/null +++ b/src/main/events/collections/add-collection-game.ts @@ -0,0 +1,18 @@ +import { collectionRepository } from "@main/repository"; + +import { registerEvent } from "../register-event"; +import { Collection, Game } from "@main/entity"; + +const addCollectionGame = async ( + _event: Electron.IpcMainInvokeEvent, + collectionId: number, + game: Game +) => { + return await collectionRepository + .createQueryBuilder() + .relation(Collection, "games") + .of(collectionId) + .add(game); +}; + +registerEvent("addCollectionGame", addCollectionGame); diff --git a/src/main/events/collections/add-collection.ts b/src/main/events/collections/add-collection.ts new file mode 100644 index 000000000..ae19e15f7 --- /dev/null +++ b/src/main/events/collections/add-collection.ts @@ -0,0 +1,14 @@ +import { collectionRepository } from "@main/repository"; + +import { registerEvent } from "../register-event"; + +const addCollection = async ( + _event: Electron.IpcMainInvokeEvent, + title: string +) => { + return await collectionRepository.insert({ + title: title, + }); +}; + +registerEvent("addCollection", addCollection); diff --git a/src/main/events/collections/get-collections.ts b/src/main/events/collections/get-collections.ts new file mode 100644 index 000000000..cf28840da --- /dev/null +++ b/src/main/events/collections/get-collections.ts @@ -0,0 +1,17 @@ +import { collectionRepository } from "@main/repository"; +import { registerEvent } from "../register-event"; + +const getCollections = async () => + collectionRepository.find({ + relations: { + games: { + downloadQueue: true, + repack: true, + }, + }, + order: { + title: "asc", + }, + }); + +registerEvent("getCollections", getCollections); diff --git a/src/main/events/collections/remove-collection-game.ts b/src/main/events/collections/remove-collection-game.ts new file mode 100644 index 000000000..d5342f19d --- /dev/null +++ b/src/main/events/collections/remove-collection-game.ts @@ -0,0 +1,18 @@ +import { collectionRepository } from "@main/repository"; + +import { registerEvent } from "../register-event"; +import { Collection, Game } from "@main/entity"; + +const removeCollectionGame = async ( + _event: Electron.IpcMainInvokeEvent, + collectionId: number, + game: Game +) => { + return await collectionRepository + .createQueryBuilder() + .relation(Collection, "games") + .of(collectionId) + .remove(game); +}; + +registerEvent("removeCollectionGame", removeCollectionGame); diff --git a/src/main/events/collections/remove-collection.ts b/src/main/events/collections/remove-collection.ts new file mode 100644 index 000000000..789839905 --- /dev/null +++ b/src/main/events/collections/remove-collection.ts @@ -0,0 +1,13 @@ +import { collectionRepository } from "@main/repository"; + +import { registerEvent } from "../register-event"; +import { Collection } from "@main/entity"; + +const removeCollection = async ( + _event: Electron.IpcMainInvokeEvent, + collection: Collection +) => { + return await collectionRepository.remove(collection); +}; + +registerEvent("removeCollection", removeCollection); diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 1b500be97..63b6ff414 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -8,6 +8,11 @@ import "./catalogue/get-how-long-to-beat"; import "./catalogue/get-random-game"; import "./catalogue/search-games"; import "./catalogue/search-game-repacks"; +import "./collections/add-collection"; +import "./collections/add-collection-game"; +import "./collections/get-collections"; +import "./collections/remove-collection"; +import "./collections/remove-collection-game"; import "./hardware/get-disk-free-space"; import "./library/add-game-to-library"; import "./library/create-game-shortcut"; diff --git a/src/main/events/library/get-library.ts b/src/main/events/library/get-library.ts index ad9823089..7f7920bfa 100644 --- a/src/main/events/library/get-library.ts +++ b/src/main/events/library/get-library.ts @@ -8,6 +8,7 @@ const getLibrary = async () => }, relations: { downloadQueue: true, + collections: true, }, order: { createdAt: "desc", diff --git a/src/main/repository.ts b/src/main/repository.ts index 4464e7752..3bf1c5c75 100644 --- a/src/main/repository.ts +++ b/src/main/repository.ts @@ -1,5 +1,6 @@ import { dataSource } from "./data-source"; import { + Collection, DownloadQueue, DownloadSource, Game, @@ -24,3 +25,5 @@ export const downloadSourceRepository = export const downloadQueueRepository = dataSource.getRepository(DownloadQueue); export const userAuthRepository = dataSource.getRepository(UserAuth); + +export const collectionRepository = dataSource.getRepository(Collection); diff --git a/src/preload/index.ts b/src/preload/index.ts index 0cadbc035..5e024eb85 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -9,6 +9,8 @@ import type { AppUpdaterEvent, StartGameDownloadPayload, GameRunning, + Collection, + Game, } from "@types"; contextBridge.exposeInMainWorld("electron", { @@ -102,6 +104,16 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.removeListener("on-library-batch-complete", listener); }, + /* Collections */ + addCollection: (title: string) => ipcRenderer.invoke("addCollection", title), + addCollectionGame: (id: number, game: Game) => + ipcRenderer.invoke("addCollectionGame", id, game), + getCollections: () => ipcRenderer.invoke("getCollections"), + removeCollection: (collection: Collection) => + ipcRenderer.invoke("removeCollection", collection), + removeCollectionGame: (id: number, game: Game) => + ipcRenderer.invoke("removeCollectionGame", id, game), + /* Hardware */ getDiskFreeSpace: (path: string) => ipcRenderer.invoke("getDiskFreeSpace", path), diff --git a/src/renderer/src/components/sidebar/sidebar.tsx b/src/renderer/src/components/sidebar/sidebar.tsx index c5b3f3a91..337e03515 100644 --- a/src/renderer/src/components/sidebar/sidebar.tsx +++ b/src/renderer/src/components/sidebar/sidebar.tsx @@ -15,6 +15,7 @@ import { buildGameDetailsPath } from "@renderer/helpers"; import SteamLogo from "@renderer/assets/steam-logo.svg?react"; import { SidebarProfile } from "./sidebar-profile"; import { sortBy } from "lodash-es"; +import { useCollections } from "@renderer/hooks/use-collections"; const SIDEBAR_MIN_WIDTH = 200; const SIDEBAR_INITIAL_WIDTH = 250; @@ -25,6 +26,7 @@ const initialSidebarWidth = window.localStorage.getItem("sidebarWidth"); export function Sidebar() { const { t } = useTranslation("sidebar"); const { library, updateLibrary } = useLibrary(); + const { collections, updateCollections } = useCollections(); const navigate = useNavigate(); const [filteredLibrary, setFilteredLibrary] = useState([]); @@ -33,6 +35,7 @@ export function Sidebar() { const [sidebarWidth, setSidebarWidth] = useState( initialSidebarWidth ? Number(initialSidebarWidth) : SIDEBAR_INITIAL_WIDTH ); + const [showCollections, setShowCollections] = useState(true); const location = useLocation(); @@ -48,6 +51,10 @@ export function Sidebar() { updateLibrary(); }, [lastPacket?.game.id, updateLibrary]); + useEffect(() => { + updateCollections(); + }); + const isDownloading = sortedLibrary.some( (game) => game.status === "active" && game.progress !== 1 ); @@ -67,17 +74,19 @@ export function Sidebar() { }; const handleFilter: React.ChangeEventHandler = (event) => { + const val = event.target.value.toLocaleLowerCase(); + setFilteredLibrary( - sortedLibrary.filter((game) => - game.title - .toLowerCase() - .includes(event.target.value.toLocaleLowerCase()) - ) + sortedLibrary.filter((game) => game.title.toLowerCase().includes(val)) ); + + setShowCollections(val == ""); }; useEffect(() => { - setFilteredLibrary(sortedLibrary); + setFilteredLibrary( + sortedLibrary.filter((game) => !game.collections.length) + ); }, [sortedLibrary]); useEffect(() => { @@ -199,6 +208,52 @@ export function Sidebar() { theme="dark" /> + {collections.map((collection) => + collection.games?.length && showCollections ? ( +
+ + {collection.title} + + +
    + {collection.games.map((game) => ( +
  • + +
  • + ))} +
+
+ ) : null + )} +
    {filteredLibrary.map((game) => (
  • () => Electron.IpcRenderer; onLibraryBatchComplete: (cb: () => void) => () => Electron.IpcRenderer; + /* Collections */ + addCollection: (title: string) => Promise; + addCollectionGame: (id: number, game: Game) => Promise; + getCollections: () => Promise; + removeCollection: (collection: Collection) => Promise; + removeCollectionGame: (id: number, game: Game) => Promise; + /* User preferences */ getUserPreferences: () => Promise; updateUserPreferences: ( diff --git a/src/renderer/src/features/collections-slice.ts b/src/renderer/src/features/collections-slice.ts new file mode 100644 index 000000000..a8a505a26 --- /dev/null +++ b/src/renderer/src/features/collections-slice.ts @@ -0,0 +1,26 @@ +import { createSlice } from "@reduxjs/toolkit"; +import type { PayloadAction } from "@reduxjs/toolkit"; +import { Collection } from "../../../types/index"; + +export interface CollectionsState { + value: Collection[]; +} + +const initialState: CollectionsState = { + value: [], +}; + +export const collectionsSlice = createSlice({ + name: "collections", + initialState, + reducers: { + setCollections: ( + state, + action: PayloadAction + ) => { + state.value = action.payload; + }, + }, +}); + +export const { setCollections } = collectionsSlice.actions; diff --git a/src/renderer/src/features/index.ts b/src/renderer/src/features/index.ts index fdc23e682..7c63b24b0 100644 --- a/src/renderer/src/features/index.ts +++ b/src/renderer/src/features/index.ts @@ -6,3 +6,4 @@ export * from "./window-slice"; export * from "./toast-slice"; export * from "./user-details-slice"; export * from "./running-game-slice"; +export * from "./collections-slice"; diff --git a/src/renderer/src/hooks/index.ts b/src/renderer/src/hooks/index.ts index 5bc287b85..5186c40c9 100644 --- a/src/renderer/src/hooks/index.ts +++ b/src/renderer/src/hooks/index.ts @@ -4,3 +4,4 @@ export * from "./use-date"; export * from "./use-toast"; export * from "./redux"; export * from "./use-user-details"; +export * from "./use-collections"; diff --git a/src/renderer/src/hooks/use-collections.ts b/src/renderer/src/hooks/use-collections.ts new file mode 100644 index 000000000..4ac051cf8 --- /dev/null +++ b/src/renderer/src/hooks/use-collections.ts @@ -0,0 +1,64 @@ +import { useCallback } from "react"; +import { useAppDispatch, useAppSelector } from "./redux"; +import { setCollections } from "@renderer/features"; +import { Collection, Game } from "@types"; +import { useToast } from "./use-toast"; +import { useTranslation } from "react-i18next"; + +export function useCollections() { + const { t } = useTranslation("collections"); + + const dispatch = useAppDispatch(); + const collections = useAppSelector((state) => state.collections.value); + const { showSuccessToast, showErrorToast } = useToast(); + + const updateCollections = useCallback(async () => { + return window.electron + .getCollections() + .then((updatedCollection) => dispatch(setCollections(updatedCollection))); + }, [dispatch]); + + const addCollection = async (title: string) => { + if ( + !collections.some((collection) => collection.title === title) && + title !== "" + ) { + await window.electron.addCollection(title); + + updateCollections(); + showSuccessToast(t("the_collection_has_been_added_successfully")); + } else { + showErrorToast(t("you_cant_give_collections_existing_or_empty_names")); + } + }; + + const removeCollection = async (collection: Collection) => { + await window.electron.removeCollection(collection); + + updateCollections(); + showSuccessToast(t("the_collection_has_been_removed_successfully")); + }; + + const addCollectionGame = async (collectionId: number, game: Game) => { + await window.electron.addCollectionGame(collectionId, game); + + updateCollections(); + showSuccessToast(t("the_game_has_been_added_to_the_collection")); + }; + + const removeCollectionGame = async (collectionId: number, game: Game) => { + await window.electron.removeCollectionGame(collectionId, game); + + updateCollections(); + showSuccessToast(t("the_game_has_been_removed_from_the_collection")); + }; + + return { + collections, + updateCollections, + addCollection, + removeCollection, + addCollectionGame, + removeCollectionGame, + }; +} diff --git a/src/renderer/src/pages/game-details/modals/collections-modal.css.ts b/src/renderer/src/pages/game-details/modals/collections-modal.css.ts new file mode 100644 index 000000000..29f75d3b6 --- /dev/null +++ b/src/renderer/src/pages/game-details/modals/collections-modal.css.ts @@ -0,0 +1,25 @@ +import { style } from "@vanilla-extract/css"; + +import { SPACING_UNIT } from "../../../theme.css"; + +export const collectionsContainer = style({ + display: "flex", + gap: `${SPACING_UNIT * 2}px`, + flexDirection: "column", + width: "50%", + margin: "auto", +}); + +export const buttonsContainer = style({ + display: "flex", + gap: `${SPACING_UNIT * 2}px`, + flexDirection: "row", +}); + +export const buttonSelect = style({ + flex: 3, +}); + +export const buttonRemove = style({ + flex: 1, +}); diff --git a/src/renderer/src/pages/game-details/modals/collections-modal.tsx b/src/renderer/src/pages/game-details/modals/collections-modal.tsx new file mode 100644 index 000000000..de413436b --- /dev/null +++ b/src/renderer/src/pages/game-details/modals/collections-modal.tsx @@ -0,0 +1,108 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Button, Modal, TextField } from "@renderer/components"; +import type { Collection, Game } from "@types"; +import * as styles from "./collections-modal.css"; +import { useCollections } from "@renderer/hooks/use-collections"; +import { useLibrary } from "@renderer/hooks"; + +export interface CollectionsModalProps { + visible: boolean; + game: Game; + onClose: () => void; +} + +export function CollectionsModal({ + visible, + game, + onClose, +}: CollectionsModalProps) { + const { t } = useTranslation("collections"); + const { + collections, + addCollection, + removeCollection, + addCollectionGame, + removeCollectionGame, + } = useCollections(); + const { updateLibrary } = useLibrary(); + + const [collectionTitle, setcollectionTitle] = useState(""); + + const handleAddCollection = () => { + addCollection(collectionTitle); + setcollectionTitle(""); + }; + + const handleRemoveCollection = (collection: Collection) => { + removeCollection(collection); + updateLibrary(); + }; + + const handleSetCollection = (id: number, addOrRemove: boolean) => { + addOrRemove ? addCollectionGame(id, game) : removeCollectionGame(id, game); + updateLibrary(); + }; + + return ( + <> + +
    + setcollectionTitle(e.target.value)} + rightContent={ + + } + /> + {collections.map((collection) => ( +
    + + +
    + ))} +
    +
    + + ); +} diff --git a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx index 313582fc1..c05702071 100644 --- a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx @@ -7,6 +7,7 @@ import { gameDetailsContext } from "@renderer/context"; import { DeleteGameModal } from "@renderer/pages/downloads/delete-game-modal"; import { useDownload, useToast } from "@renderer/hooks"; import { RemoveGameFromLibraryModal } from "./remove-from-library-modal"; +import { CollectionsModal } from "./collections-modal"; export interface GameOptionsModalProps { visible: boolean; @@ -19,7 +20,7 @@ export function GameOptionsModal({ game, onClose, }: GameOptionsModalProps) { - const { t } = useTranslation("game_details"); + const { t } = useTranslation(["game_details", "collections"]); const { showSuccessToast, showErrorToast } = useToast(); @@ -28,6 +29,7 @@ export function GameOptionsModal({ const [showDeleteModal, setShowDeleteModal] = useState(false); const [showRemoveGameModal, setShowRemoveGameModal] = useState(false); + const [showCollectionsModal, setShowCollectionsModal] = useState(false); const { removeGameInstaller, @@ -107,6 +109,29 @@ export function GameOptionsModal({ large={true} >
    +
    +

    {t("collections:collections")}

    +

    + {t("collections:add_the_game_to_the_collection")} +

    +
    + + + + { + setShowCollectionsModal(false); + }} + /> +

    {t("executable_section_title")}

    diff --git a/src/renderer/src/store.ts b/src/renderer/src/store.ts index 0f2bee9f8..716a49bd7 100644 --- a/src/renderer/src/store.ts +++ b/src/renderer/src/store.ts @@ -3,6 +3,7 @@ import { downloadSlice, windowSlice, librarySlice, + collectionsSlice, searchSlice, userPreferencesSlice, toastSlice, @@ -15,6 +16,7 @@ export const store = configureStore({ search: searchSlice.reducer, window: windowSlice.reducer, library: librarySlice.reducer, + collections: collectionsSlice.reducer, userPreferences: userPreferencesSlice.reducer, download: downloadSlice.reducer, toast: toastSlice.reducer, diff --git a/src/types/index.ts b/src/types/index.ts index 71071620a..c50591302 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -128,12 +128,19 @@ export interface Game { objectID: string; shop: GameShop; downloadQueue: DownloadQueue | null; + collections: Collection[]; createdAt: Date; updatedAt: Date; } export type LibraryGame = Omit; +export interface Collection { + id: number; + title: string; + games: Game[]; +} + export interface GameRunning { id: number; title: string; From 2a6e0f31dfe1a65f476c0ab7c4e86b8e37ab3cbf Mon Sep 17 00:00:00 2001 From: Tasheron Date: Sun, 7 Jul 2024 11:38:36 +0300 Subject: [PATCH 2/2] feature: add collections section - query optimization --- src/main/entity/game.entity.ts | 5 -- .../events/collections/get-collections.ts | 6 +- src/main/events/library/get-library.ts | 1 - .../src/components/sidebar/sidebar.tsx | 86 +++++++++++-------- src/types/index.ts | 1 - 5 files changed, 52 insertions(+), 47 deletions(-) diff --git a/src/main/entity/game.entity.ts b/src/main/entity/game.entity.ts index fedd850e1..190e74701 100644 --- a/src/main/entity/game.entity.ts +++ b/src/main/entity/game.entity.ts @@ -6,14 +6,12 @@ import { UpdateDateColumn, OneToOne, JoinColumn, - ManyToMany, } from "typeorm"; import { Repack } from "./repack.entity"; import type { GameShop, GameStatus } from "@types"; import { Downloader } from "@shared"; import type { DownloadQueue } from "./download-queue.entity"; -import { Collection } from "./collection.entity"; @Entity("game") export class Game { @@ -81,9 +79,6 @@ export class Game { @OneToOne("DownloadQueue", "game") downloadQueue: DownloadQueue; - @ManyToMany("Collection", "games") - collections: Collection[]; - @Column("boolean", { default: false }) isDeleted: boolean; diff --git a/src/main/events/collections/get-collections.ts b/src/main/events/collections/get-collections.ts index cf28840da..c8fbd9dd1 100644 --- a/src/main/events/collections/get-collections.ts +++ b/src/main/events/collections/get-collections.ts @@ -4,9 +4,11 @@ import { registerEvent } from "../register-event"; const getCollections = async () => collectionRepository.find({ relations: { + games: true, + }, + select: { games: { - downloadQueue: true, - repack: true, + id: true, }, }, order: { diff --git a/src/main/events/library/get-library.ts b/src/main/events/library/get-library.ts index 7f7920bfa..ad9823089 100644 --- a/src/main/events/library/get-library.ts +++ b/src/main/events/library/get-library.ts @@ -8,7 +8,6 @@ const getLibrary = async () => }, relations: { downloadQueue: true, - collections: true, }, order: { createdAt: "desc", diff --git a/src/renderer/src/components/sidebar/sidebar.tsx b/src/renderer/src/components/sidebar/sidebar.tsx index 337e03515..6a75464c5 100644 --- a/src/renderer/src/components/sidebar/sidebar.tsx +++ b/src/renderer/src/components/sidebar/sidebar.tsx @@ -49,11 +49,8 @@ export function Sidebar() { useEffect(() => { updateLibrary(); - }, [lastPacket?.game.id, updateLibrary]); - - useEffect(() => { updateCollections(); - }); + }, [lastPacket?.game.id, updateLibrary, updateCollections]); const isDownloading = sortedLibrary.some( (game) => game.status === "active" && game.progress !== 1 @@ -85,9 +82,16 @@ export function Sidebar() { useEffect(() => { setFilteredLibrary( - sortedLibrary.filter((game) => !game.collections.length) + sortedLibrary.filter( + (game) => + !collections.some((collection) => + collection.games.some( + (collectionGame) => collectionGame.id == game.id + ) + ) + ) ); - }, [sortedLibrary]); + }, [sortedLibrary, collections]); useEffect(() => { window.onmousemove = (event: MouseEvent) => { @@ -216,39 +220,45 @@ export function Sidebar() {
      - {collection.games.map((game) => ( -
    • - -
    • - ))} + + + ))}
    ) : null diff --git a/src/types/index.ts b/src/types/index.ts index c50591302..a24e8d267 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -128,7 +128,6 @@ export interface Game { objectID: string; shop: GameShop; downloadQueue: DownloadQueue | null; - collections: Collection[]; createdAt: Date; updatedAt: Date; }