diff --git a/README.md b/README.md index 5f0b51f..3a4165c 100644 --- a/README.md +++ b/README.md @@ -57,10 +57,12 @@ To Be Played is split into three repositories: finished games per year of all users. - **Library**: status filters, source filter (Steam, PlayStation, manual; a linked set counts on each of its sources), "short games" filter (up to 10 h), search, sorting by playtime, recently seen, newest in library, rating or priority, paging. Each row shows the unlocked achievements. -- **Editions**: an auditor links the editions of one game (Steam and PS5 copy, remaster, port) for every user. A user - who owns two or more of a linked set sees "Also in your library" on the details page, an "x2" badge in the library - and the filter "Hide duplicates"; the numbers count the set once. "Same game as..." on the details page asks the - auditors for a link. +- **Editions**: the editions of one game (Steam and PS5 copy, remaster, port) are linked automatically from IGDB's + "same game" key; an auditor can also link or unlink them for every user. A user who owns two or more of a linked + set sees "Also in your library" on the details page, an "x2" badge in the library and the filter "Hide + duplicates"; the numbers count the set once. The editions of a set share one status, rating and notes (the page + reloads after a save to show it). "Not the same game" splits the game off its set for this user only, and it stays + split. "Same game as..." on the details page asks the auditors for a link. - **Add a game by name** for games outside Steam (PlayStation, Switch, Xbox, physical): search IGDB, pick a hit, a platform and optional hours. Works without a Steam ID. Games already in ToBePlayed come first. Check several hits to add them at once through the import review. @@ -257,6 +259,17 @@ flowchart LR which question is open; `src/utils/questions.js` only loads the list and sends the answer. A yes to Finished? gets the Rate it? card back at once. `409` / `404` reload the list. `RatingStars` is shared with `BacklogCard`. +```mermaid +flowchart LR + Detail["GameDetail: EditionsCard"] -->|"Not the same game"| Split["Core DELETE /api/library/games/{appid}/set"] + Detail -->|"Same game as... (request)"| Request["Core POST /api/library/games/{appid}/link-requests"] + Detail -->|"Unlink (auditor)"| Unlink["Core DELETE /api/library/games/{appid}/link"] + Split --> Reload["reload the detail"] + Backlog["BacklogCard save"] -->|"game has editions"| Reload +``` +- `EditionsCard` shows "Not the same game" on every game that has other editions in the own library. The call goes + through `src/utils/links.js` (`splitGame`); `404 NOT_LINKED` shows the generic error toast. + ```mermaid flowchart LR Header["AppHeader badge"] --> Count["Core /api/library/questions/count"] diff --git a/public/i18n/de.json b/public/i18n/de.json index d8202cf..e6f5804 100644 --- a/public/i18n/de.json +++ b/public/i18n/de.json @@ -809,6 +809,9 @@ "REQUEST": "Anfrage senden", "LINK": "Verknüpfen", "UNLINK": "Verknüpfung lösen", + "SPLIT": "Nicht dasselbe Spiel", + "SPLIT_DONE": "Spiel getrennt. Es steht jetzt allein in deiner Bibliothek.", + "SHARED_STATUS": "Status, Bewertung und Notizen gelten für alle diese Ausgaben.", "DISMISS": "Nicht dasselbe", "REQUESTED": "Anfrage gesendet", "LINKED": "Spiele verknüpft", diff --git a/public/i18n/en.json b/public/i18n/en.json index 5037ad1..a78e0d0 100644 --- a/public/i18n/en.json +++ b/public/i18n/en.json @@ -809,6 +809,9 @@ "REQUEST": "Send request", "LINK": "Link", "UNLINK": "Unlink", + "SPLIT": "Not the same game", + "SPLIT_DONE": "Game split off. It stands alone in your library now.", + "SHARED_STATUS": "Status, rating and notes are shared by these editions.", "DISMISS": "Not the same", "REQUESTED": "Request sent", "LINKED": "Games linked", diff --git a/src/components/EditionsCard.jsx b/src/components/EditionsCard.jsx index 1e3b501..139e045 100644 --- a/src/components/EditionsCard.jsx +++ b/src/components/EditionsCard.jsx @@ -1,37 +1,38 @@ import {useState} from 'react'; import {Link} from 'react-router-dom'; import {useTranslation} from 'react-i18next'; -import {Layers, Link2Off} from 'lucide-react'; +import {Layers, Link2Off, Unlink} from 'lucide-react'; import {toast} from 'sonner'; import {Button} from '@/components/ui/button'; import {Card} from '@/components/ui/card'; import GameStatusBadge from './GameStatusBadge.jsx'; import FitText from './FitText.jsx'; -import {unlinkGame} from '../utils/links.js'; +import {splitGame, unlinkGame} from '../utils/links.js'; import {platformLabel} from '../utils/platforms.js'; /** - * The other editions of this game in the own library, and the buttons to ask for a link (everyone) or to unlink - * (auditor). Shown on every game: a request must be possible from any game. + * The other editions of this game in the own library, and the buttons to ask for a link (everyone), to split the game + * off its set (everyone, for the own library) or to unlink (auditor). Shown on every game: a request must be possible + * from any game. */ export default function EditionsCard({game, auditor, onRequest, onChanged}) { const {t} = useTranslation(); const [busy, setBusy] = useState(false); - const unlink = async () => { + const run = async (call, doneKey) => { setBusy(true); try { - const response = await unlinkGame(game.appid); + const response = await call(game.appid); if (response.status === 401) return; if (response.ok) { - toast.success(t('LINKS.UNLINKED')); + toast.success(t(doneKey)); onChanged(); } else { toast.error(t('LINKS.ERROR')); } } catch (err) { - console.error('Unlink error:', err); + console.error('Edition change error:', err); toast.error(t('INFO.ERROR.NETWORK_FAILURE')); } finally { setBusy(false); @@ -56,14 +57,22 @@ export default function EditionsCard({game, auditor, onRequest, onChanged}) { ))} +
{t('LINKS.SHARED_STATUS')}
)}