Fix application role filtering
This commit is contained in:
@@ -14,13 +14,13 @@ SettingsPage {
|
|||||||
property string expandedRole: ""
|
property string expandedRole: ""
|
||||||
readonly property var applications: DesktopEntries.applications.values
|
readonly property var applications: DesktopEntries.applications.values
|
||||||
readonly property var roles: [
|
readonly property var roles: [
|
||||||
{ key: "browser", label: "Browser", detail: "Web links and HTML pages", categories: ["webbrowser"], terms: ["browser", "web"] },
|
{ key: "browser", label: "Browser", detail: "Web links and HTML pages", categorySets: [["webbrowser"]], terms: ["web browser", "browser"] },
|
||||||
{ key: "mail", label: "Mail", detail: "Email links", categories: ["email"], terms: ["mail", "email"] },
|
{ key: "mail", label: "Mail", detail: "Email links", categorySets: [["email"]], terms: ["mail client", "email client"] },
|
||||||
{ key: "files", label: "Files", detail: "Folders and file locations", categories: ["filemanager"], terms: ["file manager", "files"] },
|
{ key: "files", label: "Files", detail: "Folders and file locations", categorySets: [["filemanager"]], terms: ["file manager"] },
|
||||||
{ key: "terminal", label: "Terminal", detail: "Terminal links and command-line handoffs", categories: ["terminalemulator"], terms: ["terminal", "console"] },
|
{ key: "terminal", label: "Terminal", detail: "Terminal links and command-line handoffs", categorySets: [["terminalemulator"]], terms: ["terminal emulator", "terminal"] },
|
||||||
{ key: "music", label: "Music", detail: "MP3 audio", categories: ["audio", "player"], terms: ["music", "audio player"] },
|
{ key: "music", label: "Music", detail: "MP3 audio", categorySets: [["music"], ["audio", "player"]], terms: ["music player", "audio player"] },
|
||||||
{ key: "images", label: "Images", detail: "PNG images", categories: ["graphics", "viewer"], terms: ["image", "photo", "picture"] },
|
{ key: "images", label: "Images", detail: "PNG images", categorySets: [], terms: ["image viewer", "image editor", "photo viewer", "photo editor", "picture viewer"] },
|
||||||
{ key: "video", label: "Video", detail: "MP4 video", categories: ["video", "player"], terms: ["video", "media player"] }
|
{ key: "video", label: "Video", detail: "MP4 video", categorySets: [["video"]], terms: ["video player", "movie player"] }
|
||||||
]
|
]
|
||||||
|
|
||||||
function desktopId(entry: var): string {
|
function desktopId(entry: var): string {
|
||||||
@@ -42,13 +42,21 @@ SettingsPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function matchesRole(entry: var, role: var): bool {
|
function matchesRole(entry: var, role: var): bool {
|
||||||
const categories = Array.isArray(entry.categories)
|
const rawCategories = Array.isArray(entry.categories)
|
||||||
? entry.categories.join(" ").toLowerCase()
|
? entry.categories
|
||||||
: String(entry.categories ?? "").toLowerCase();
|
: [String(entry.categories ?? "")];
|
||||||
const metadata = [entry.name, entry.genericName, entry.comment]
|
const categories = [];
|
||||||
|
for (const rawCategory of rawCategories) {
|
||||||
|
for (const value of String(rawCategory).split(";")) {
|
||||||
|
const category = value.trim().toLowerCase();
|
||||||
|
if (category !== "")
|
||||||
|
categories.push(category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const metadata = [entry.name, entry.genericName]
|
||||||
.map(value => String(value ?? "").toLowerCase())
|
.map(value => String(value ?? "").toLowerCase())
|
||||||
.join(" ");
|
.join(" ");
|
||||||
return role.categories.some(category => categories.includes(category))
|
return role.categorySets.some(set => set.every(category => categories.includes(category)))
|
||||||
|| role.terms.some(term => metadata.includes(term));
|
|| role.terms.some(term => metadata.includes(term));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +70,7 @@ SettingsPage {
|
|||||||
|
|
||||||
TextRow {
|
TextRow {
|
||||||
visible: DefaultApps.lastError !== ""
|
visible: DefaultApps.lastError !== ""
|
||||||
label: "Could not apply the change"
|
label: "Application settings need attention"
|
||||||
detail: DefaultApps.lastError
|
detail: DefaultApps.lastError
|
||||||
value: ""
|
value: ""
|
||||||
divider: false
|
divider: false
|
||||||
@@ -88,9 +96,11 @@ SettingsPage {
|
|||||||
SettingRow {
|
SettingRow {
|
||||||
label: roleBlock.modelData.label
|
label: roleBlock.modelData.label
|
||||||
detail: roleBlock.modelData.detail
|
detail: roleBlock.modelData.detail
|
||||||
value: roleBlock.selectedEntry
|
value: DefaultApps.busy ? "Loading…" : (
|
||||||
? root.displayName(roleBlock.selectedEntry)
|
roleBlock.selectedEntry
|
||||||
: (root.currentHandler(roleBlock.modelData.key) || "Not set")
|
? root.displayName(roleBlock.selectedEntry)
|
||||||
|
: (root.currentHandler(roleBlock.modelData.key) || "Not set")
|
||||||
|
)
|
||||||
activatable: roleBlock.choices.length > 0 && !DefaultApps.busy
|
activatable: roleBlock.choices.length > 0 && !DefaultApps.busy
|
||||||
divider: root.expandedRole !== roleBlock.modelData.key && roleBlock.index < root.roles.length - 1
|
divider: root.expandedRole !== roleBlock.modelData.key && roleBlock.index < root.roles.length - 1
|
||||||
onActivated: {
|
onActivated: {
|
||||||
@@ -137,7 +147,7 @@ SettingsPage {
|
|||||||
subtitle: "These desktop entries live in your user configuration. Select a row to toggle it."
|
subtitle: "These desktop entries live in your user configuration. Select a row to toggle it."
|
||||||
|
|
||||||
TextRow {
|
TextRow {
|
||||||
visible: DefaultApps.autostartEntries.length === 0
|
visible: !DefaultApps.busy && DefaultApps.autostartEntries.length === 0
|
||||||
label: "No user autostart entries"
|
label: "No user autostart entries"
|
||||||
detail: "Applications can add entries to ~/.config/autostart."
|
detail: "Applications can add entries to ~/.config/autostart."
|
||||||
value: ""
|
value: ""
|
||||||
@@ -168,7 +178,7 @@ SettingsPage {
|
|||||||
subtitle: "Panama starts these from Hyprland configuration. They are read-only here."
|
subtitle: "Panama starts these from Hyprland configuration. They are read-only here."
|
||||||
|
|
||||||
TextRow {
|
TextRow {
|
||||||
visible: DefaultApps.luaAutostartEntries.length === 0
|
visible: !DefaultApps.busy && DefaultApps.luaAutostartEntries.length === 0
|
||||||
label: "No compositor entries found"
|
label: "No compositor entries found"
|
||||||
detail: "No hl.exec_cmd entries were found in config/dot/hypr/autostart.lua."
|
detail: "No hl.exec_cmd entries were found in config/dot/hypr/autostart.lua."
|
||||||
value: ""
|
value: ""
|
||||||
|
|||||||
@@ -38,10 +38,84 @@ assert_contains 'genericName'
|
|||||||
assert_contains '.sort('
|
assert_contains '.sort('
|
||||||
assert_contains 'currentEntry'
|
assert_contains 'currentEntry'
|
||||||
assert_contains 'read-only'
|
assert_contains 'read-only'
|
||||||
|
assert_contains 'choices.push(currentEntry)'
|
||||||
|
assert_contains 'label: "Application settings need attention"'
|
||||||
|
assert_contains 'DefaultApps.busy ? "Loading…"'
|
||||||
|
assert_contains 'visible: !DefaultApps.busy && DefaultApps.autostartEntries.length === 0'
|
||||||
|
assert_contains 'visible: !DefaultApps.busy && DefaultApps.luaAutostartEntries.length === 0'
|
||||||
|
|
||||||
|
PAGE_PATH="$page" bun -e '
|
||||||
|
const source = await Bun.file(process.env.PAGE_PATH).text();
|
||||||
|
const rolesSource = source.match(/readonly property var roles:\s*(\[[\s\S]*?\n \])/);
|
||||||
|
const matcherSource = source.match(/function matchesRole\(entry: var, role: var\): bool \{([\s\S]*?)\n \}/);
|
||||||
|
if (!rolesSource || !matcherSource) {
|
||||||
|
console.error("applications settings contract: role matcher could not be loaded");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const roles = Function(`return (${rolesSource[1]})`)();
|
||||||
|
const matchesRole = Function("entry", "role", matcherSource[1]);
|
||||||
|
const role = key => roles.find(candidate => candidate.key === key);
|
||||||
|
const fixtures = [
|
||||||
|
{
|
||||||
|
name: "AudioVideo does not imply music",
|
||||||
|
entry: { name: "Kodi", genericName: "Media Center", comment: "Entertainment hub", categories: "AudioVideo;Player;" },
|
||||||
|
role: "music",
|
||||||
|
expected: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Graphics does not imply image handler",
|
||||||
|
entry: { name: "Document Scanner", genericName: "Document Scanner", comment: "Scan documents", categories: ["Graphics"] },
|
||||||
|
role: "images",
|
||||||
|
expected: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Viewer does not imply image handler",
|
||||||
|
entry: { name: "Papers", genericName: "Document Viewer", comment: "Read documents", categories: "Office;Viewer;" },
|
||||||
|
role: "images",
|
||||||
|
expected: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "comment does not nominate a default handler",
|
||||||
|
entry: { name: "Settings", genericName: "System Settings", comment: "Configure your video player", categories: ["System"] },
|
||||||
|
role: "video",
|
||||||
|
expected: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exact audio player categories match music",
|
||||||
|
entry: { name: "Rhythmbox", genericName: "Music Player", comment: "Play music", categories: "AudioVideo;Audio;Player;" },
|
||||||
|
role: "music",
|
||||||
|
expected: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exact video category matches video",
|
||||||
|
entry: { name: "Videos", genericName: "Video Player", comment: "Play movies", categories: ["AudioVideo", "Video", "Player"] },
|
||||||
|
role: "video",
|
||||||
|
expected: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "descriptive metadata matches image handler",
|
||||||
|
entry: { name: "Loupe", genericName: "Image Viewer", comment: "Browse pictures", categories: "Graphics;Viewer;" },
|
||||||
|
role: "images",
|
||||||
|
expected: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const fixture of fixtures) {
|
||||||
|
const actual = matchesRole(fixture.entry, role(fixture.role));
|
||||||
|
if (actual !== fixture.expected) {
|
||||||
|
console.error(`applications settings contract: ${fixture.name}: expected ${fixture.expected}, got ${actual}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'
|
||||||
|
|
||||||
if rg --quiet 'Component\.onCompleted|DesktopEntries\.(byId|heuristicLookup)' "$page"; then
|
if rg --quiet 'Component\.onCompleted|DesktopEntries\.(byId|heuristicLookup)' "$page"; then
|
||||||
fail 'page snapshots or performs a one-time desktop-entry lookup'
|
fail 'page snapshots or performs a one-time desktop-entry lookup'
|
||||||
fi
|
fi
|
||||||
|
if rg -F --quiet 'label: "Could not apply the change"' "$page"; then
|
||||||
|
fail 'error heading incorrectly describes read failures as apply failures'
|
||||||
|
fi
|
||||||
if rg --quiet '#[0-9A-Fa-f]{3,8}' "$page"; then
|
if rg --quiet '#[0-9A-Fa-f]{3,8}' "$page"; then
|
||||||
fail 'page introduces a color literal instead of the shared visual system'
|
fail 'page introduces a color literal instead of the shared visual system'
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user