From ed3ce58d82a38665478dee327aa05faa66869822 Mon Sep 17 00:00:00 2001 From: TheThomaas Date: Tue, 17 Dec 2024 21:30:41 +0100 Subject: [PATCH] Add basic games layout --- src/main/index.ts | 37 ++++++++++++++- src/preload/index.ts | 8 +++- src/renderer/src/App.tsx | 45 +++++++++--------- src/renderer/src/assets/main.css | 13 +++-- .../src/components/GameArea/index.tsx | 38 +++++++++++++++ .../src/components/GameCard/index.css | 26 ++++++++++ .../src/components/GameCard/index.tsx | 34 ++++++++++++++ src/renderer/src/styles/index.css | 47 +++++++++++++++++++ src/renderer/src/types/index.ts | 10 ++++ 9 files changed, 228 insertions(+), 30 deletions(-) create mode 100644 src/renderer/src/components/GameArea/index.tsx create mode 100644 src/renderer/src/components/GameCard/index.css create mode 100644 src/renderer/src/components/GameCard/index.tsx create mode 100644 src/renderer/src/styles/index.css create mode 100644 src/renderer/src/types/index.ts diff --git a/src/main/index.ts b/src/main/index.ts index a69e5d3..756b09d 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -3,6 +3,8 @@ import { join } from 'path' import { electronApp, optimizer, is } from '@electron-toolkit/utils' import icon from '../../resources/icon.png?asset' +import { spawn } from 'child_process'; + import { Game, addGame, @@ -19,8 +21,8 @@ let tray: Tray; function createWindow(): void { // Create the browser window. mainWindow = new BrowserWindow({ - width: 900, - height: 670, + width: 1500, + height: 870, show: false, autoHideMenuBar: true, ...(process.platform === 'linux' ? { icon } : {}), @@ -141,6 +143,37 @@ app.whenReady().then(() => { ipcMain.handle('game:getAll', async () => { return getAllGames(); }); + + ipcMain.on('launch-app', (event, { id, appPath }) => { + try { + const appProcess = spawn(appPath, [], { detached: true, stdio: 'ignore' }); + + appProcess.on('data', (data) => { + console.log(`stdout: ${data}`); + }); + + // Surveiller la fermeture de l'application + appProcess.on('close', (code) => { + console.log(`child process close all stdio with code ${code}`); + event.reply('app-status', { id, status: 'closed' }); + }); + + /*appProcess.on('exit', (code) => { + console.log(`child process exited with code ${code}`); + // event.reply('app-status', { id, status: 'Application fermée.' }); + });*/ + + appProcess.on('error', (err) => { + console.error(`Erreur avec l'application ${id} : ${err.message}`); + event.reply('app-status', { id, status: `${err.message}` }); + }); + + event.reply('app-status', { id, status: 'started' }); + } catch (err: any) { + console.error(`Erreur lors du lancement de l'application ${id} : ${err.message}`); + event.reply('app-status', { id, status: `${err.message}` }); + } + }); }) // Quit when all windows are closed, except on macOS. There, it's common diff --git a/src/preload/index.ts b/src/preload/index.ts index 2d18524..579c451 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -1,4 +1,4 @@ -import { contextBridge } from 'electron' +import { contextBridge, ipcRenderer } from 'electron' import { electronAPI } from '@electron-toolkit/preload' // Custom APIs for renderer @@ -9,7 +9,11 @@ const api = {} // just add to the DOM global. if (process.contextIsolated) { try { - contextBridge.exposeInMainWorld('electron', electronAPI) + contextBridge.exposeInMainWorld('electron', { + ...electronAPI, + launchApp: (id, appPath) => ipcRenderer.send('launch-app', { id, appPath}), + onAppStatus: (callback) => ipcRenderer.on('app-status', (event, status) => callback(status)) + }) contextBridge.exposeInMainWorld('api', api) } catch (error) { console.error(error) diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index af20fb6..b31a3e2 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -1,31 +1,34 @@ +import './styles/index.css' + import Versions from './components/Versions' -import electronLogo from './assets/electron.svg' +import { useEffect, useState } from 'react'; +import GameArea, { Game } from './components/GameArea'; function App(): JSX.Element { - const ipcHandle = (): void => window.electron.ipcRenderer.send('ping') + const [games, setGames] = useState([]); + + async function getAllGames() { + const data = await window.electron.ipcRenderer.invoke('game:getAll'); + + console.log(data) + if (data) { + setGames(data); + } + } + + useEffect(() => { + getAllGames(); + }, []); return ( <> - logo -
Powered by electron-vite
-
- Build an Electron app with React -  and TypeScript -
-

- Please try pressing F12 to open the devTool -

-
-
- - Documentation - -
-
- - Send IPC - +
+
+
+
diff --git a/src/renderer/src/assets/main.css b/src/renderer/src/assets/main.css index 0179fc4..5a87eb5 100644 --- a/src/renderer/src/assets/main.css +++ b/src/renderer/src/assets/main.css @@ -1,9 +1,9 @@ @import './base.css'; body { - display: flex; + /*display: flex; align-items: center; - justify-content: center; + justify-content: center;*/ overflow: hidden; background-image: url('./wavy-lines.svg'); background-size: cover; @@ -27,11 +27,12 @@ code { } #root { - display: flex; + /*display: flex; align-items: center; justify-content: center; flex-direction: column; - margin-bottom: 80px; + margin-bottom: 80px;*/ + min-height: 100vh; } .logo { @@ -126,7 +127,7 @@ code { .versions { position: absolute; - bottom: 30px; + bottom: 15px; margin: 0 auto; padding: 15px 0; font-family: 'Menlo', 'Lucida Console', monospace; @@ -136,6 +137,8 @@ code { border-radius: 22px; background-color: #202127; backdrop-filter: blur(24px); + left: 50%; + transform: translateX(-50%); } .versions li { diff --git a/src/renderer/src/components/GameArea/index.tsx b/src/renderer/src/components/GameArea/index.tsx new file mode 100644 index 0000000..0e61eb2 --- /dev/null +++ b/src/renderer/src/components/GameArea/index.tsx @@ -0,0 +1,38 @@ +import GameCard from "../GameCard"; + +export type Game = { + id?: number; + game_id: number; + title: string; + formatted_title: string; + path: string; + img_cover: string; + img_background: string; + is_running: number; +}; + +export default function GameArea({ + games, +}: { + games: Game[]; +}) { + return ( +
+ {games.map((game) => ( + + ))} +
+ ); +} \ No newline at end of file diff --git a/src/renderer/src/components/GameCard/index.css b/src/renderer/src/components/GameCard/index.css new file mode 100644 index 0000000..4404965 --- /dev/null +++ b/src/renderer/src/components/GameCard/index.css @@ -0,0 +1,26 @@ +.gameCard { + display: flex; + flex-direction: column; + position: relative; + transition: transform .3s ease-out, outline .3s; + outline: 3px solid transparent; + outline-offset: 5px; + border-radius: 4px; +} +.gameCard:hover, .gameCard:has(.hoverLink:focus:focus-visible) { + transform: scale(1.02); +} +.gameCard:has(.hoverLink:focus:focus-visible) { + outline-color: #2121ec; +} +.gameCard .hoverLink { + outline: none; +} + +.gameCard img { + max-width: 100%; + aspect-ratio: 2/3; + object-fit: cover; + overflow: hidden; + border-radius: 4px; +} \ No newline at end of file diff --git a/src/renderer/src/components/GameCard/index.tsx b/src/renderer/src/components/GameCard/index.tsx new file mode 100644 index 0000000..b3627e1 --- /dev/null +++ b/src/renderer/src/components/GameCard/index.tsx @@ -0,0 +1,34 @@ +import { useEffect, useState } from 'react'; +import './index.css'; + +import { Game } from "@renderer/types"; + +const GameCard = ({ title, path, is_running, img_cover, id, }: Game) => { + const [status, setStatus] = useState(""); + + function startGame(id) { + window.electron.launchApp(id, path); + } + + useEffect(() => { + window.electron.onAppStatus((status) => { + if (status.id === id) { + let newStatus = status.status === "started" ? "Lancé" : status.status === "closed" ? "" : status.status; + setStatus(newStatus); + } + }); + }, []); + + + return ( + + ); +}; + +export default GameCard \ No newline at end of file diff --git a/src/renderer/src/styles/index.css b/src/renderer/src/styles/index.css new file mode 100644 index 0000000..e12be9a --- /dev/null +++ b/src/renderer/src/styles/index.css @@ -0,0 +1,47 @@ +.hoverLink { + position: absolute; + inset: 0; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +.appWrapper { + display: grid; + grid-template-columns: repeat(5, 1fr); + grid-template-areas: "sidebar main main main main"; +} + +.gameArea { + grid-area: main; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(139px, 1fr)); + gap: 1em; + justify-content: center; + justify-items: center; + max-height: 92vh; + overflow: hidden auto; + padding: 3rem 1rem; + + scrollbar-color: #323232 transparent; + scrollbar-width: thin; + scrollbar-gutter: 10px; +} + +.gameArea::-webkit-scrollbar { + width: 10px; +} + +.gameArea::-webkit-scrollbar-track { + background-color: transparent; + border-radius: 100px; +} diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts new file mode 100644 index 0000000..b0da006 --- /dev/null +++ b/src/renderer/src/types/index.ts @@ -0,0 +1,10 @@ +export type Game = { + id?: number; + game_id: number; + title: string; + formatted_title: string; + path: string; + img_cover: string; + img_background: string; + is_running: boolean; +}; \ No newline at end of file