fix(customisation-sync): hide identical items with "Hide not applicable items"

The checkbox only toggled a CSS class for rows labelled "All the same or
non-existent", while `hideNotApplicable`, which filters the offered
devices by whether their copy can be applied, was hard-coded to false.
Items present on another device therefore stayed visible even when every
copy was the same.

Drive `hideNotApplicable` from the checkbox, so the comparison only runs
while it is on, and pass it to the reactive update so toggling it
re-evaluates the rows. The term selection moves into PluginTerms.ts so it
can be unit-tested.

Fixes #1193
This commit is contained in:
Immick
2026-09-15 22:03:51 +03:00
parent ba297d1233
commit f1746fa4b8
4 changed files with 102 additions and 18 deletions
+14 -17
View File
@@ -12,6 +12,7 @@
// import { askString } from "../../common/utils";
import { Menu } from "@/deps.ts";
import { $msg as translateMessage } from "@/common/translation";
import { selectSourceTerms } from "./PluginTerms.ts";
export let list: IPluginDataExDisplay[] = [];
export let thisTerm = "";
@@ -182,24 +183,20 @@
}
}
async function updateTerms(list: IPluginDataExDisplay[], selectNewest: boolean, isMaintenanceMode: boolean) {
async function updateTerms(
list: IPluginDataExDisplay[],
selectNewest: boolean,
isMaintenanceMode: boolean,
hideNotApplicable: boolean
) {
const local = list.find((e) => e.term == thisTerm);
// selected = "";
if (isMaintenanceMode) {
terms = [...new Set(list.map((e) => e.term))];
} else if (hideNotApplicable) {
const termsTmp = [];
const wk = [...new Set(list.map((e) => e.term))];
for (const termName of wk) {
const remote = list.find((e) => e.term == termName);
if ((await comparePlugin(local, remote)).canApply) {
termsTmp.push(termName);
}
}
terms = [...termsTmp];
} else {
terms = [...new Set(list.map((e) => e.term))].filter((e) => e != thisTerm);
}
terms = await selectSourceTerms(
list,
thisTerm,
{ isMaintenanceMode, hideNotApplicable },
async (local, remote) => (await comparePlugin(local, remote)).canApply
);
let newest: IPluginDataExDisplay | undefined = local;
if (selectNewest) {
for (const term of terms) {
@@ -230,7 +227,7 @@
}
// currentSelectNewest = selectNewest;
}
updateTerms(list, doSelectNewest, isMaintenanceMode);
updateTerms(list, doSelectNewest, isMaintenanceMode, hideNotApplicable);
currentSelectNewest = selectNewest;
}
$: {
+2 -1
View File
@@ -32,7 +32,8 @@
export let core :LiveSyncBaseCore;
// $: core = plugin.core;
$: hideNotApplicable = false;
// Comparing every copy is only worth it while the user asks to hide the unchanged ones.
$: hideNotApplicable = hideEven;
$: thisTerm = core.services.setting.getDeviceAndVaultName();
const addOn = core.getAddOn<ConfigSync>(ConfigSync.name)!;
+32
View File
@@ -0,0 +1,32 @@
import type { IPluginDataExDisplay } from "./CmdConfigSync.ts";
export type CanApplyFrom = (
local: IPluginDataExDisplay | undefined,
remote: IPluginDataExDisplay | undefined
) => Promise<boolean>;
/**
* Devices offered as sources for one item in the Customisation Sync dialogue.
*
* Maintenance mode lists every device, including this one. Otherwise every other
* device that has the item is listed; with `hideNotApplicable`, only devices whose
* copy can actually be applied remain, so an item that is the same everywhere ends
* up with no source and is shown as "All the same or non-existent".
*/
export async function selectSourceTerms(
list: IPluginDataExDisplay[],
thisTerm: string,
options: { isMaintenanceMode: boolean; hideNotApplicable: boolean },
canApplyFrom: CanApplyFrom
): Promise<string[]> {
const terms = [...new Set(list.map((e) => e.term))];
if (options.isMaintenanceMode) return terms;
if (!options.hideNotApplicable) return terms.filter((term) => term != thisTerm);
const local = list.find((e) => e.term == thisTerm);
const applicable: string[] = [];
for (const term of terms) {
const remote = list.find((e) => e.term == term);
if (await canApplyFrom(local, remote)) applicable.push(term);
}
return applicable;
}
@@ -0,0 +1,54 @@
import { describe, expect, it, vi } from "vitest";
import type { IPluginDataExDisplay } from "./CmdConfigSync.ts";
import { selectSourceTerms } from "./PluginTerms.ts";
const copyOn = (term: string) => ({ term, files: [] }) as unknown as IPluginDataExDisplay;
const list = [copyOn("desktop"), copyOn("phone"), copyOn("tablet")];
const differsOn =
(...terms: string[]) =>
(_local: IPluginDataExDisplay | undefined, remote: IPluginDataExDisplay | undefined) =>
Promise.resolve(terms.includes(remote?.term ?? ""));
describe("selectSourceTerms", () => {
it("offers every other device without comparing copies by default", async () => {
const canApplyFrom = vi.fn(differsOn());
const terms = await selectSourceTerms(
list,
"desktop",
{ isMaintenanceMode: false, hideNotApplicable: false },
canApplyFrom
);
expect(terms).toEqual(["phone", "tablet"]);
expect(canApplyFrom).not.toHaveBeenCalled();
});
it("offers every device, including this one, in maintenance mode", async () => {
const terms = await selectSourceTerms(
list,
"desktop",
{ isMaintenanceMode: true, hideNotApplicable: true },
differsOn()
);
expect(terms).toEqual(["desktop", "phone", "tablet"]);
});
it("leaves out devices whose copy is the same when hiding items that are not applicable", async () => {
const terms = await selectSourceTerms(
list,
"desktop",
{ isMaintenanceMode: false, hideNotApplicable: true },
differsOn("tablet")
);
expect(terms).toEqual(["tablet"]);
});
it("offers no source for an item that is the same on every device", async () => {
const terms = await selectSourceTerms(
list,
"desktop",
{ isMaintenanceMode: false, hideNotApplicable: true },
differsOn()
);
expect(terms).toEqual([]);
});
});