From 32182530766ab5444d7eda25af0b6abf28b8698a Mon Sep 17 00:00:00 2001 From: Michel Fedde <35878897+Neintonine@users.noreply.github.com> Date: Sat, 6 Jul 2024 20:49:50 +0200 Subject: [PATCH] implermented key display --- src/css/common/index.scss | 4 + src/js/pages/{keys.ts => keys/import.ts} | 19 +--- src/js/pages/keys/index.ts | 22 +++++ src/js/pages/keys/table.ts | 98 +++++++++++++++++++ src/js/pages/types/entities.ts | 25 +++++ src/js/pages/types/keyState.ts | 35 +++++++ src/php/Entities/Games/Game.php | 10 ++ src/php/Entities/Games/Key.php | 43 ++++++++ src/php/Entities/Games/KeyState.php | 12 +++ .../Api/DataTables/DataTablesAPIRoutes.php | 2 + .../Api/DataTables/ProviderKeysEndpoint.php | 98 +++++++++++++++++++ src/templates/layout/keyTable.php | 5 + src/templates/pages/key-manager.php | 19 +++- webpack.config.js | 2 +- 14 files changed, 375 insertions(+), 19 deletions(-) rename src/js/pages/{keys.ts => keys/import.ts} (88%) create mode 100644 src/js/pages/keys/index.ts create mode 100644 src/js/pages/keys/table.ts create mode 100644 src/js/pages/types/entities.ts create mode 100644 src/js/pages/types/keyState.ts create mode 100644 src/php/Entities/Games/KeyState.php create mode 100644 src/php/Routing/Api/DataTables/ProviderKeysEndpoint.php create mode 100644 src/templates/layout/keyTable.php diff --git a/src/css/common/index.scss b/src/css/common/index.scss index acb8a8e..1b272c9 100644 --- a/src/css/common/index.scss +++ b/src/css/common/index.scss @@ -3,6 +3,10 @@ $main-container-max-width: 992px; +.cursor-pointer { + cursor: pointer; +} + .navigation-container, main { width: 100%; diff --git a/src/js/pages/keys.ts b/src/js/pages/keys/import.ts similarity index 88% rename from src/js/pages/keys.ts rename to src/js/pages/keys/import.ts index 94ae972..c56d535 100644 --- a/src/js/pages/keys.ts +++ b/src/js/pages/keys/import.ts @@ -1,10 +1,3 @@ -import '../../css/common/index.scss'; -import '../common/index'; - -import 'datatables.net-bs5/css/dataTables.bootstrap5.css'; - -import { Tab } from 'bootstrap'; - async function checkImportFile() { const fileInput = document.querySelector('#import'); @@ -136,20 +129,12 @@ async function doImport() { fileInput.value = ''; } -document.addEventListener('DOMContentLoaded', () => { - const triggerTabList = document.querySelectorAll('#key-tab button') - triggerTabList.forEach(triggerEl => { - const tabTrigger = new Tab(triggerEl) +export function init() { - triggerEl.addEventListener('click', event => { - event.preventDefault() - tabTrigger.show() - }) - }) const importButton = document.querySelector('.js--send-import'); importButton?.addEventListener('click', checkImportFile); const doImportButton = document.querySelector('.js--do-import'); doImportButton?.addEventListener('click', doImport); -}) \ No newline at end of file +} \ No newline at end of file diff --git a/src/js/pages/keys/index.ts b/src/js/pages/keys/index.ts new file mode 100644 index 0000000..69d8cca --- /dev/null +++ b/src/js/pages/keys/index.ts @@ -0,0 +1,22 @@ +import '../../../css/common/index.scss'; +import '../../common/index'; + +import { Tab } from 'bootstrap'; + +import {init as initImport} from "./import"; +import {init as initTable} from "./table"; + +document.addEventListener('DOMContentLoaded', () => { + const triggerTabList = document.querySelectorAll('#key-tab button') + triggerTabList.forEach(triggerEl => { + const tabTrigger = new Tab(triggerEl) + + triggerEl.addEventListener('click', event => { + event.preventDefault() + tabTrigger.show() + }) + }) + + initImport(); + initTable(); +}) \ No newline at end of file diff --git a/src/js/pages/keys/table.ts b/src/js/pages/keys/table.ts new file mode 100644 index 0000000..84fd843 --- /dev/null +++ b/src/js/pages/keys/table.ts @@ -0,0 +1,98 @@ +import DataTable from "datatables.net-bs5"; +import 'datatables.net-bs5/css/dataTables.bootstrap5.css'; +import {Key} from "../types/entities"; +import {getIconForKeyState, getKeyStateExplanation} from "../types/keyState"; + +import {Dropdown} from "bootstrap"; + +const TABLE_AJAX_URL = '/api/dt/keys/provider'; + +function getKeyDisplay(parent: HTMLElement, keys: Key[]) { + const header = document.createElement("h1"); + header.innerText = 'Keys'; + header.classList.add('h6'); + + const table = document.createElement("table"); + table.classList.add('table', 'table-striped', 'w-100'); + const body = table.createTBody(); + + for (const {fromWhere, keyState, key, store, store_link} of keys) { + const row = body.insertRow(); + row.classList.add('cursor-pointer'); + + const stateCell = row.insertCell(); + stateCell.classList.add('w-auto', 'align-content-center'); + stateCell.innerHTML = `` + + const anchor = stateCell.querySelector('a'); + if (!anchor) { + return; + } + new Dropdown(anchor); + + row.insertCell().textContent = key; + row.insertCell().textContent = store === 'external' ? store_link : store; + row.insertCell().textContent = fromWhere; + + row.addEventListener('click', () => { + console.log('Key options'); + }) + } + + parent.appendChild(header); + parent.appendChild(table); +} + +export function init() { + const keyTable = document.querySelector('.key-table'); + if (!keyTable) { + return; + } + + const table = new DataTable(keyTable, { + ajax: { + url: TABLE_AJAX_URL + }, + processing: true, + columns: [ + { + data: 'gamePicture', + searchable: false + }, + { + data: 'name', + }, + { + data: 'keysAmount', + searchable: false, + }, + { + data: 'igdbState', + searchable: false, + } + ], + ordering: false, + serverSide: true, + order: [ [1, 'asc'] ], + createdRow(row: Node, data: any) { + const tableRow = row; + tableRow.classList.add('cursor-pointer'); + + tableRow.addEventListener('click', () => { + const rowAPI = table.row(row); + if (rowAPI.child.isShown()) { + rowAPI.child.hide(); + return; + } + + const childRow = document.createElement('tr'); + const cell = childRow.insertCell(); + cell.colSpan = row.childNodes.length; + + getKeyDisplay(cell, data.keys); + + rowAPI.child(childRow).show(); + }) + }, + }); +} \ No newline at end of file diff --git a/src/js/pages/types/entities.ts b/src/js/pages/types/entities.ts new file mode 100644 index 0000000..2dcde77 --- /dev/null +++ b/src/js/pages/types/entities.ts @@ -0,0 +1,25 @@ +import {KeyState} from "./keyState"; + +enum Store { + STEAM = 'steam', + GOG = 'gog', + EPICGAMES = 'epicgames', + ORIGIN = 'origin', + UPLAY = 'uplay', + BATTLENET = 'battlenet', + EXTERNAL = 'external' +} + +export type Game = { + name: string +} + +export type Key = { + game?: Game, + key: string, + store: Store, + keyState: KeyState, + store_link: string|null, + fromWhere: string|null, +} + diff --git a/src/js/pages/types/keyState.ts b/src/js/pages/types/keyState.ts new file mode 100644 index 0000000..3de76f2 --- /dev/null +++ b/src/js/pages/types/keyState.ts @@ -0,0 +1,35 @@ +export enum KeyState { + AVAILABLE = 1, + UNKNOWN = 0, + RESERVED_FOR_GIFT = -1, + CLAIMED = -10 +} + +export function getIconForKeyState(keyState: KeyState): string { + switch (keyState) { + case KeyState.AVAILABLE: + return "fa-check text-success"; + default: + case KeyState.UNKNOWN: + return 'fa-question text-info'; + case KeyState.RESERVED_FOR_GIFT: + return 'fa-gift text-warning'; + case KeyState.CLAIMED: + return 'fa-x text-danger'; + + } +} + +export function getKeyStateExplanation(keyState: KeyState): string { + switch (keyState) { + case KeyState.AVAILABLE: + return "This key is available"; + default: + case KeyState.UNKNOWN: + return 'The state of this key is unknown'; + case KeyState.RESERVED_FOR_GIFT: + return 'This key is reserved for a gift'; + case KeyState.CLAIMED: + return 'This key was claimed'; + } +} \ No newline at end of file diff --git a/src/php/Entities/Games/Game.php b/src/php/Entities/Games/Game.php index 31c2239..9b3c834 100644 --- a/src/php/Entities/Games/Game.php +++ b/src/php/Entities/Games/Game.php @@ -16,6 +16,16 @@ final class Game #[ORM\Column] private string $name; + public function getName(): string + { + return $this->name; + } + + public function getId(): ?int + { + return $this->id; + } + /** * @param string $name */ diff --git a/src/php/Entities/Games/Key.php b/src/php/Entities/Games/Key.php index 9f76bce..47b0428 100644 --- a/src/php/Entities/Games/Key.php +++ b/src/php/Entities/Games/Key.php @@ -26,6 +26,8 @@ final class Key private string|null $storeLink; #[ORM\Column] private string|null $fromWhere; + #[ORM\Column(type: 'integer', enumType: KeyState::class)] + private KeyState $state; public function __construct(Game $game, User $contributedUser, string $key, Store $store, ?string $storeLink, ?string $fromWhere) { @@ -35,5 +37,46 @@ final class Key $this->store = $store; $this->storeLink = $storeLink; $this->fromWhere = $fromWhere; + $this->state = KeyState::AVAILABLE; + } + + public function getId(): ?int + { + return $this->id; + } + + public function getGame(): Game + { + return $this->game; + } + + public function getContributedUser(): User + { + return $this->contributedUser; + } + + public function getKey(): string + { + return $this->key; + } + + public function getStore(): Store + { + return $this->store; + } + + public function getStoreLink(): ?string + { + return $this->storeLink; + } + + public function getFromWhere(): ?string + { + return $this->fromWhere; + } + + public function getState(): KeyState + { + return $this->state; } } \ No newline at end of file diff --git a/src/php/Entities/Games/KeyState.php b/src/php/Entities/Games/KeyState.php new file mode 100644 index 0000000..cbeb934 --- /dev/null +++ b/src/php/Entities/Games/KeyState.php @@ -0,0 +1,12 @@ +get('/keys/provider', ProviderKeysEndpoint::class); } } \ No newline at end of file diff --git a/src/php/Routing/Api/DataTables/ProviderKeysEndpoint.php b/src/php/Routing/Api/DataTables/ProviderKeysEndpoint.php new file mode 100644 index 0000000..8f53324 --- /dev/null +++ b/src/php/Routing/Api/DataTables/ProviderKeysEndpoint.php @@ -0,0 +1,98 @@ +loginHandler->isLoggedIn()) { + throw new UnauthorizedException(); + } + + $user = $this->loginHandler->getCurrentUser(); + if (!$user->getPermission()->hasLevel(UserPermission::PROVIDER)) { + throw new ForbiddenException(); + } + + + $params = $request->getQueryParams(); + $draw = $params['draw']; + $start = $params['start']; + $length = $params['length']; + + $searchValue = $params['search']['value']; + + $repo = $this->entityManager->getRepository(Game::class); + $total = $repo->count(); + + $criteria = Criteria::create(); + $criteria->where(Criteria::expr()->contains('name', $searchValue)); + $criteria->setFirstResult((int)$start); + $criteria->setMaxResults((int)$length); + $criteria->orderBy([ 'name' => Order::Ascending ]); + + $values = $repo->matching($criteria); + $filteredCount = $values->count(); + + $entityManager = $this->entityManager; + + return new JsonResponse([ + 'draw' => $draw, + 'recordsTotal' => $total, + 'recordsFiltered' => $filteredCount, + 'data' => + array_filter($values->map(function (Game $game) use ($entityManager, $user) { + $criteria = Criteria::create(); + $criteria->where(Criteria::expr()->eq('game', $game)); + $criteria->andWhere(Criteria::expr()->eq('contributedUser', $user)); + $criteria->orderBy(['state' => Order::Ascending]); + + $keys = $entityManager->getRepository(Key::class)->matching($criteria); + + $keysAmount = $keys->count(); + if ($keysAmount < 1) { + return null; + } + + return [ + 'gamePicture' => '', + 'name' => $game->getName(), + 'keysAmount' => $keysAmount, + 'igdbState' => 'yet to be implermented', + 'keys' => $keys->map(function (Key $key) use ($entityManager) { + return [ + 'keyState' => $key->getState()->value, + 'key' => $key->getKey(), + 'store' => $key->getStore()->value, + 'store_link' => $key->getStoreLink(), + 'from' => $key->getFromWhere(), + ]; + })->toArray() + ]; + })->toArray()) + ]); + } +} \ No newline at end of file diff --git a/src/templates/layout/keyTable.php b/src/templates/layout/keyTable.php new file mode 100644 index 0000000..0d79473 --- /dev/null +++ b/src/templates/layout/keyTable.php @@ -0,0 +1,5 @@ + + + diff --git a/src/templates/pages/key-manager.php b/src/templates/pages/key-manager.php index 6a44231..2108b48 100644 --- a/src/templates/pages/key-manager.php +++ b/src/templates/pages/key-manager.php @@ -2,11 +2,15 @@ declare(strict_types=1); use GamesShop\Entities\Games\KeyAttribute; +use League\Plates\Template\Template; + +assert($this instanceof Template); $this->layout('layout/main', [ 'resourceEntry' => 'keys' ]); ?> +

My Keys

- Key Table + + + + + + + + + + +
Game NameAmount KeysIGDB State
+

Importer

@@ -32,6 +47,8 @@ $this->layout('layout/main', [ 'resourceEntry' => 'keys' ]);
+

Import Details:

+ diff --git a/webpack.config.js b/webpack.config.js index 82d3389..94d798f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -93,7 +93,7 @@ module.exports = { entry: { index: JS_FOLDER + "/pages/index", 'admin/accounts': JS_FOLDER + "/pages/admin/accounts", - keys: JS_FOLDER + "/pages/keys", + keys: JS_FOLDER + "/pages/keys/index", }, output: { path: PUBLIC_FOLDER,