+
+
+ These are this game's settings, not its save — named by the game database or found by
+ what they hold — so each device keeps its own (graphics, resolution). They are still in
+ every snapshot. A file that turns out to change along with the save syncs again by
+ itself; to sync one yourself, untick it under “Pick from your save folder” below.
+
+ {/if}
0} />
@@ -206,6 +237,29 @@
.fields .field {
margin-bottom: 14px;
}
+ .device-settings {
+ margin: 4px 0 6px;
+ padding-left: 18px;
+ color: var(--text-dim, inherit);
+ }
+ .device-settings code {
+ font-size: 0.9em;
+ }
+ .device-settings .source {
+ font-size: 0.82em;
+ color: var(--text-faint);
+ margin-left: 6px;
+ }
+ .device-settings .given-back code {
+ text-decoration: line-through;
+ }
+ .device-block {
+ margin-bottom: 4px !important;
+ }
+ .device-hint {
+ display: block;
+ margin-bottom: 14px;
+ }
.hint-warn {
color: var(--warn);
}
diff --git a/cmd/opensave-app/frontend/src/views/game/GameHeader.svelte b/cmd/opensave-app/frontend/src/views/game/GameHeader.svelte
index 19750020..327f2a34 100644
--- a/cmd/opensave-app/frontend/src/views/game/GameHeader.svelte
+++ b/cmd/opensave-app/frontend/src/views/game/GameHeader.svelte
@@ -13,10 +13,12 @@
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
import Play from 'lucide-svelte/icons/play';
import RefreshCw from 'lucide-svelte/icons/refresh-cw';
+ import Upload from 'lucide-svelte/icons/upload';
import FolderOpen from 'lucide-svelte/icons/folder-open';
import Pencil from 'lucide-svelte/icons/pencil';
import Star from 'lucide-svelte/icons/star';
import TriangleAlert from 'lucide-svelte/icons/triangle-alert';
+ import Info from 'lucide-svelte/icons/info';
import { collections, isFavourite, toggleFavourite } from '../../lib/collections.js';
export let game;
@@ -24,6 +26,14 @@
export let runner;
const { busy, run } = runner;
+ // A game OpenSave cannot find here: its program, chosen by hand, says where
+ // it is installed and what runs when it is played (sessions, launch).
+ async function pickProgram() {
+ const exe = await native.selectFile(`Program of “${game.name}”`);
+ if (!exe) return;
+ await run(`Program set for ${game.name}`, () => api.patch(`/api/games/${game.id}`, { exePath: exe }));
+ }
+
$: activity = $syncActivity[game.id];
// Is my Deck up to date with THIS save? One entry per paired device, in
@@ -42,6 +52,20 @@
const syncNow = () => run('Sync triggered', () => api.post(`/api/games/${game.id}/sync`));
const launchGame = () => run('Launching…', () => api.post(`/api/games/${game.id}/launch`));
+ // For when the devices hold states nobody can rank — a copy that arrived
+ // only half-way, a mixture of two saves — and the person knows which device
+ // has the right one. Every other device takes this save whole, keeping a
+ // snapshot of its own first; one that changed the save on its own since is
+ // still asked.
+ async function useEverywhere() {
+ const ok = await askConfirm(
+ `Make this device's save of ${game.name} the one every other device uses? They each replace theirs with it — every file, including removing files this device does not have — and keep a snapshot of their own first. A device where the save was changed after this update is asked instead.`,
+ { title: 'Use this save everywhere?', confirmText: 'Use this save everywhere', danger: true }
+ );
+ if (!ok) return;
+ await run(`${game.name}: this save goes to your other devices`, () => api.post(`/api/games/${game.id}/use-everywhere`));
+ }
+
let editPath = false;
let pathDraft = '';
@@ -118,7 +142,12 @@
· played {timeAgo(game.lastPlayedAt, now)}, {playLength(game.playtimeMs)} in all
{/if}
{#if game.installed === 'not-found'}
- · not found on this PC
+ ·
{/if}
+ This game isn't on this device.
+ Its saves are on {game.savePath.slice(0, 2)}, which this PC doesn't have, so it's skipped here and keeps syncing
+ between your other devices. If it is installed here, point the game at its save folder with Edit.
+
+
+{:else if game.savePathMissing && !editPath}
@@ -204,6 +245,15 @@
{/if}