Skip to content

Commit 234c5b3

Browse files
authored
Merge pull request #4120 from sdornan/claude/romm-playmatch-disabled-bug-60843f
fix(v2): keep Playmatch selectable when providers are set to All
2 parents a5097b6 + 198f471 commit 234c5b3

5 files changed

Lines changed: 508 additions & 587 deletions

File tree

frontend/src/v2/components/Dialogs/RefreshMetadataDialog.vue

Lines changed: 23 additions & 181 deletions
Original file line numberDiff line numberDiff line change
@@ -15,46 +15,18 @@ import {
1515
RSwitch,
1616
RTooltip,
1717
} from "@v2/lib";
18-
import { useLocalStorage } from "@vueuse/core";
1918
import type { Emitter } from "mitt";
20-
import { storeToRefs } from "pinia";
2119
import { computed, inject, onBeforeUnmount, ref, watch } from "vue";
2220
import { useI18n } from "vue-i18n";
2321
import socket from "@/services/socket";
24-
import storeConfig from "@/stores/config";
25-
import storeHeartbeat, { type MetadataOption } from "@/stores/heartbeat";
2622
import { type SimpleRom } from "@/stores/roms";
2723
import storeScanning from "@/stores/scanning";
2824
import type { Events } from "@/types/emitter";
25+
import { useScanProviders } from "@/v2/composables/useScanProviders";
2926
import { useSnackbar } from "@/v2/composables/useSnackbar";
3027
3128
defineOptions({ inheritAttrs: false });
3229
33-
const LOCAL_STORAGE_METADATA_SOURCES_KEY = "scan.metadataSources";
34-
const LOCAL_STORAGE_LAUNCHBOX_REMOTE_ENABLED_KEY =
35-
"scan.launchboxRemoteEnabled";
36-
const LOCAL_STORAGE_HASHEOUS_ENABLED_KEY = "scan.hasheousEnabled";
37-
const LOCAL_STORAGE_PLAYMATCH_ENABLED_KEY = "scan.playmatchEnabled";
38-
39-
// Hash-matcher providers — proxies that match files by hash and feed
40-
// IDs into the primary catalogs. Filtered out of the main provider
41-
// select and rendered as switch pills so users don't read them as
42-
// standalone sources.
43-
const HASH_MATCHER_KEYS = ["hasheous", "playmatch"] as const;
44-
45-
// Provider categorisation — mirrors the Scan view's split so this
46-
// dialog reads as a sibling surface, not a parallel vocabulary.
47-
const GENERAL_PROVIDER_KEYS = new Set([
48-
"igdb",
49-
"ss",
50-
"moby",
51-
"launchbox",
52-
"flashpoint",
53-
"gamelist",
54-
"libretro",
55-
]);
56-
const SPECIFIC_PROVIDER_KEYS = new Set(["ra", "sgdb", "hltb"]);
57-
5830
const { t } = useI18n();
5931
const emitter = inject<Emitter<Events>>("emitter");
6032
const snackbar = useSnackbar();
@@ -64,138 +36,24 @@ const show = ref(false);
6436
// normalise to an array so the scan emit groups by platform without
6537
// branching on the input shape.
6638
const roms = ref<SimpleRom[]>([]);
67-
const heartbeat = storeHeartbeat();
6839
const scanningStore = storeScanning();
69-
const configStore = storeConfig();
70-
const { config } = storeToRefs(configStore);
71-
72-
const calculateHashes = computed(() => !config.value.SKIP_HASH_CALCULATION);
73-
74-
const metadataOptions = computed(() =>
75-
heartbeat
76-
.getMetadataOptionsByPriority()
77-
.filter(
78-
(option) =>
79-
!(HASH_MATCHER_KEYS as readonly string[]).includes(option.value),
80-
)
81-
.map((option) => {
82-
const requiresHashes = option.value === "ra";
83-
const hashingDisabled = !calculateHashes.value;
84-
let disabled = option.disabled;
85-
if (hashingDisabled && requiresHashes) {
86-
disabled = t("scan.requires-hashes", { source: option.name });
87-
}
88-
const name = option.value === "igdb" ? "IGDB" : option.name;
89-
return { ...option, name, disabled };
90-
}),
91-
);
9240
93-
const generalProviders = computed<MetadataOption[]>(() =>
94-
metadataOptions.value.filter((o) => GENERAL_PROVIDER_KEYS.has(o.value)),
95-
);
96-
const specificProviders = computed<MetadataOption[]>(() =>
97-
metadataOptions.value.filter((o) => SPECIFIC_PROVIDER_KEYS.has(o.value)),
98-
);
99-
100-
const storedMetadataSources = useLocalStorage(
101-
LOCAL_STORAGE_METADATA_SOURCES_KEY,
102-
[] as string[],
103-
);
104-
const launchboxRemoteEnabled = useLocalStorage(
105-
LOCAL_STORAGE_LAUNCHBOX_REMOTE_ENABLED_KEY,
106-
true,
107-
);
108-
const hasheousEnabled = useLocalStorage(
109-
LOCAL_STORAGE_HASHEOUS_ENABLED_KEY,
110-
true,
111-
);
112-
const playmatchEnabled = useLocalStorage(
113-
LOCAL_STORAGE_PLAYMATCH_ENABLED_KEY,
114-
true,
115-
);
116-
117-
const metadataSources = ref<MetadataOption[]>([]);
118-
const isLaunchboxSelected = computed(() =>
119-
metadataSources.value.some((s) => s.value === "launchbox"),
120-
);
121-
122-
watch(
123-
[metadataOptions, storedMetadataSources],
124-
([newOptions, newStoredMetadataSources]) => {
125-
const filteredMetadataSources = newOptions.filter(
126-
(option) =>
127-
newStoredMetadataSources.includes(option.value) && !option.disabled,
128-
);
129-
metadataSources.value =
130-
filteredMetadataSources.length > 0
131-
? filteredMetadataSources
132-
: heartbeat
133-
.getEnabledMetadataOptions()
134-
.filter(
135-
(o) =>
136-
!(HASH_MATCHER_KEYS as readonly string[]).includes(o.value),
137-
);
138-
},
139-
{ immediate: true },
140-
);
141-
142-
interface HashMatcher {
143-
value: "hasheous" | "playmatch";
144-
name: string;
145-
logo: string;
146-
/** Reason the switch is forced off — surfaced in the hover tooltip.
147-
* null when the switch is interactable. */
148-
blockedReason: string | null;
149-
switchEnabled: boolean;
150-
}
151-
152-
const hashMatchers = computed<HashMatcher[]>(() => {
153-
const sources = heartbeat.value.METADATA_SOURCES;
154-
const igdbSelected = metadataSources.value.some((s) => s.value === "igdb");
155-
const noHashes = !calculateHashes.value;
156-
157-
const hasheousAdmin = Boolean(sources?.HASHEOUS_API_ENABLED);
158-
const playmatchAdmin = Boolean(sources?.PLAYMATCH_API_ENABLED);
159-
160-
return [
161-
{
162-
value: "hasheous",
163-
name: "Hasheous",
164-
logo: "/assets/scrappers/hasheous.png",
165-
blockedReason: !hasheousAdmin
166-
? t("scan.disabled-by-admin")
167-
: noHashes
168-
? t("scan.requires-hashes", { source: "Hasheous" })
169-
: null,
170-
switchEnabled: hasheousAdmin && !noHashes,
171-
},
172-
{
173-
value: "playmatch",
174-
name: "Playmatch",
175-
logo: "/assets/scrappers/playmatch.png",
176-
blockedReason: !playmatchAdmin
177-
? t("scan.disabled-by-admin")
178-
: noHashes
179-
? t("scan.requires-hashes", { source: "Playmatch" })
180-
: !igdbSelected
181-
? t("scan.playmatch-requires-igdb")
182-
: null,
183-
switchEnabled: playmatchAdmin && !noHashes && igdbSelected,
184-
},
185-
];
186-
});
187-
188-
function setHashMatcher(value: HashMatcher["value"], next: boolean) {
189-
if (value === "hasheous") hasheousEnabled.value = next;
190-
else playmatchEnabled.value = next;
191-
}
192-
193-
function isHashMatcherOn(matcher: HashMatcher): boolean {
194-
if (!matcher.switchEnabled) return false;
195-
return matcher.value === "hasheous"
196-
? hasheousEnabled.value
197-
: playmatchEnabled.value;
198-
}
41+
const {
42+
calculateHashes,
43+
generalProviders,
44+
specificProviders,
45+
metadataSources,
46+
effectiveMetadataSources,
47+
generalAllSelected,
48+
specificAllSelected,
49+
isLaunchboxSelected,
50+
launchboxRemoteEnabled,
51+
hashMatchers,
52+
setHashMatcher,
53+
isHashMatcherOn,
54+
buildScanPayload,
55+
persistSelection,
56+
} = useScanProviders();
19957
20058
// Per-ROM scan types — the Scan view's "new platforms" / "quick"
20159
// options don't apply to an already-ingested ROM. We keep `update`
@@ -280,7 +138,7 @@ function onScan() {
280138
if (roms.value.length === 0) return;
281139
282140
scanningStore.setScanning(true);
283-
storedMetadataSources.value = metadataSources.value.map((s) => s.value);
141+
persistSelection();
284142
285143
// Group rom ids by platform — the scan socket event accepts one
286144
// platform list + one rom-id list, so a selection that spans
@@ -305,31 +163,13 @@ function onScan() {
305163
306164
if (!socket.connected) socket.connect();
307165
308-
// Build the apis payload — providers + hasheous (when its switch is
309-
// on; the backend accepts it as a MetadataSource enum value).
310-
// Playmatch has no enum entry; the backend gates it via the separate
311-
// `playmatch_enabled` flag below.
312-
const apis = metadataSources.value.map((s) => s.value);
313-
const hasheousMatcher = hashMatchers.value.find(
314-
(m) => m.value === "hasheous",
315-
);
316-
if (hasheousMatcher && isHashMatcherOn(hasheousMatcher)) {
317-
apis.push("hasheous");
318-
}
319-
const playmatchMatcher = hashMatchers.value.find(
320-
(m) => m.value === "playmatch",
321-
);
322-
166+
const payload = buildScanPayload();
323167
for (const [platformId, romIds] of byPlatform) {
324168
socket.emit("scan", {
325169
platforms: [platformId],
326170
roms_ids: romIds,
327171
type: scanType.value,
328-
apis,
329-
launchbox_remote_enabled: launchboxRemoteEnabled.value,
330-
playmatch_enabled: playmatchMatcher
331-
? isHashMatcherOn(playmatchMatcher)
332-
: false,
172+
...payload,
333173
});
334174
}
335175
@@ -413,6 +253,7 @@ function closeDialog() {
413253
chips
414254
chip-tone="plain"
415255
show-all-option
256+
@update:all-selected="generalAllSelected = $event"
416257
>
417258
<template #chip="{ item }">
418259
<RTooltip :text="item.raw.name" location="bottom">
@@ -503,6 +344,7 @@ function closeDialog() {
503344
chips
504345
chip-tone="plain"
505346
show-all-option
347+
@update:all-selected="specificAllSelected = $event"
506348
>
507349
<template #chip="{ item }">
508350
<RTooltip :text="item.raw.name" location="bottom">
@@ -640,7 +482,7 @@ function closeDialog() {
640482
variant="translucent"
641483
color="primary"
642484
prepend-icon="mdi-magnify-scan"
643-
:disabled="metadataSources.length === 0"
485+
:disabled="effectiveMetadataSources.length === 0"
644486
@click="onScan"
645487
>
646488
{{ t("rom.refresh-metadata") }}

0 commit comments

Comments
 (0)