diff --git a/i18n/english.js b/i18n/english.js
index 64a7784c..d4cdfdf8 100644
--- a/i18n/english.js
+++ b/i18n/english.js
@@ -273,6 +273,9 @@ const ui = {
placeholder: "Search packages...",
placeholder_filter_hint: "or use",
placeholder_refine: "Add another filter...",
+ section_actions: "Actions",
+ action_toggle_theme_to_dark: "Switch to dark theme",
+ action_toggle_theme_to_light: "Switch to light theme",
section_presets: "Quick filters",
preset_has_vulnerabilities: "Has vulnerabilities",
preset_has_scripts: "Has install scripts",
diff --git a/i18n/french.js b/i18n/french.js
index 6e3acc1b..020f0977 100644
--- a/i18n/french.js
+++ b/i18n/french.js
@@ -273,6 +273,9 @@ const ui = {
placeholder: "Rechercher des packages...",
placeholder_filter_hint: "ou utiliser",
placeholder_refine: "Ajouter un autre filtre...",
+ section_actions: "Actions",
+ action_toggle_theme_to_dark: "Passer en thème sombre",
+ action_toggle_theme_to_light: "Passer en thème clair",
section_presets: "Filtres rapides",
preset_has_vulnerabilities: "Contient des vulnérabilités",
preset_has_scripts: "Scripts d'installation",
diff --git a/public/components/search-command/search-command-panels.js b/public/components/command-palette/command-palette-panels.js
similarity index 88%
rename from public/components/search-command/search-command-panels.js
rename to public/components/command-palette/command-palette-panels.js
index 918e3650..465079bf 100644
--- a/public/components/search-command/search-command-panels.js
+++ b/public/components/command-palette/command-palette-panels.js
@@ -159,6 +159,32 @@ export function renderPresets({ presets, onApply }) {
`;
}
+/**
+ * @param {{ actions: Array<{ id: string, label: string, kbd: string|null }>, onExecute: Function }} props
+ */
+export function renderActions({ actions, onExecute }) {
+ const i18n = window.i18n[currentLang()].search_command;
+
+ return html`
+
+
${i18n.section_actions}
+
+
+ ${actions.map((action) => html`
+
+ `)}
+
+
+
+ `;
+}
+
/**
* @param {{ results: Array, selectedIndex: number, helperCount: number, onFocus: Function }} props
*/
diff --git a/public/components/search-command/search-command-styles.js b/public/components/command-palette/command-palette-styles.js
similarity index 98%
rename from public/components/search-command/search-command-styles.js
rename to public/components/command-palette/command-palette-styles.js
index efeb8d80..32156717 100644
--- a/public/components/search-command/search-command-styles.js
+++ b/public/components/command-palette/command-palette-styles.js
@@ -4,7 +4,7 @@ import { css } from "lit";
// Import Internal Dependencies
import { scrollbarStyle } from "../../common/scrollbar-style.js";
-export const searchCommandStyles = [
+export const commandPaletteStyles = [
scrollbarStyle,
css`
:host {
@@ -380,5 +380,11 @@ kbd {
font-size: 11px;
color: var(--sc-kbd-text);
}
+
+.action-kbd {
+ margin-left: 6px;
+ opacity: 0.7;
+ border: 1px solid var(--sc-border);
+}
`
];
diff --git a/public/components/search-command/search-command.js b/public/components/command-palette/command-palette.js
similarity index 88%
rename from public/components/search-command/search-command.js
rename to public/components/command-palette/command-palette.js
index 3dc3d7bc..d99b6472 100644
--- a/public/components/search-command/search-command.js
+++ b/public/components/command-palette/command-palette.js
@@ -13,23 +13,39 @@ import {
computeMatches,
getHelperValues
} from "./filters.js";
-import { searchCommandStyles } from "./search-command-styles.js";
+import { commandPaletteStyles } from "./command-palette-styles.js";
import {
renderFlagPanel,
renderRangePanel,
renderListPanel,
renderFilterList,
renderPresets,
+ renderActions,
renderResults
-} from "./search-command-panels.js";
+} from "./command-palette-panels.js";
import "./search-chip.js";
-class SearchCommand extends LitElement {
+// CONSTANTS
+const kActions = [
+ { id: "toggle_theme", shortcut: "t" }
+];
+
+function resolveKbd(shortcut) {
+ if (!shortcut) {
+ return null;
+ }
+
+ const key = shortcut.toUpperCase();
+
+ return navigator.userAgent.includes("Mac") ? `⌥${key}` : `Alt+${key}`;
+}
+
+class CommandPalette extends LitElement {
#linker = null;
#network = null;
#packages = [];
- static styles = searchCommandStyles;
+ static styles = commandPaletteStyles;
static properties = {
open: { type: Boolean },
@@ -55,6 +71,16 @@ class SearchCommand extends LitElement {
if (event.key === "Escape" && this.open) {
this.#close();
+
+ return;
+ }
+
+ if (this.open && event.altKey) {
+ const action = kActions.find((a) => a.shortcut && `Key${a.shortcut.toUpperCase()}` === event.code);
+ if (action) {
+ event.preventDefault();
+ this.#executeAction(action);
+ }
}
};
@@ -84,12 +110,12 @@ class SearchCommand extends LitElement {
connectedCallback() {
super.connectedCallback();
document.addEventListener("keydown", this.#handleKeydown);
- window.addEventListener(EVENTS.SEARCH_COMMAND_INIT, this.#init);
+ window.addEventListener(EVENTS.COMMAND_PALETTE_INIT, this.#init);
}
disconnectedCallback() {
document.removeEventListener("keydown", this.#handleKeydown);
- window.removeEventListener(EVENTS.SEARCH_COMMAND_INIT, this.#init);
+ window.removeEventListener(EVENTS.COMMAND_PALETTE_INIT, this.#init);
super.disconnectedCallback();
}
@@ -323,6 +349,17 @@ class SearchCommand extends LitElement {
this.#close();
}
+ #executeAction(action) {
+ if (action.id === "toggle_theme") {
+ const nextTheme = window.settings.config.theme === "dark" ? "light" : "dark";
+ window.dispatchEvent(new CustomEvent(EVENTS.SETTINGS_SAVED, {
+ detail: { ...window.settings.config, theme: nextTheme }
+ }));
+ }
+
+ this.#close();
+ }
+
#getEmptyQueryMessage() {
const i18n = window.i18n[currentLang()].search_command;
if (this.queries.length === 1) {
@@ -407,6 +444,20 @@ class SearchCommand extends LitElement {
}
}
+ #resolveActions() {
+ const i18n = window.i18n[currentLang()].search_command;
+ const currentTheme = window.settings?.config?.theme ?? "light";
+ const targetTheme = currentTheme === "dark" ? "light" : "dark";
+
+ return kActions.map((action) => {
+ return {
+ ...action,
+ label: i18n[`action_${action.id}_to_${targetTheme}`],
+ kbd: resolveKbd(action.shortcut)
+ };
+ });
+ }
+
render() {
if (!this.open) {
return nothing;
@@ -483,6 +534,10 @@ class SearchCommand extends LitElement {
presets: PRESETS,
onApply: (preset) => this.#addQuery(preset.filter, preset.value)
}) : nothing}
+ ${showRichPlaceholder ? renderActions({
+ actions: this.#resolveActions(),
+ onExecute: (action) => this.#executeAction(action)
+ }) : nothing}
${renderResults({
results: this.results,
selectedIndex: this.selectedIndex,
@@ -505,4 +560,4 @@ class SearchCommand extends LitElement {
}
}
-customElements.define("search-command", SearchCommand);
+customElements.define("command-palette", CommandPalette);
diff --git a/public/components/search-command/filters.js b/public/components/command-palette/filters.js
similarity index 100%
rename from public/components/search-command/filters.js
rename to public/components/command-palette/filters.js
diff --git a/public/components/search-command/search-chip.js b/public/components/command-palette/search-chip.js
similarity index 100%
rename from public/components/search-command/search-chip.js
rename to public/components/command-palette/search-chip.js
diff --git a/public/components/navigation/navigation.js b/public/components/navigation/navigation.js
index 99cd89db..845c4d4a 100644
--- a/public/components/navigation/navigation.js
+++ b/public/components/navigation/navigation.js
@@ -37,7 +37,7 @@ export class ViewNavigation {
const isTargetPopup = event.target.id === "popup--background";
const isPopupOpened = document.querySelector("#popup--background.show");
const isTargetInput = event.target.tagName === "INPUT";
- const isSearchCommandOpen = Boolean(document.querySelector("search-command")?.open);
+ const isSearchCommandOpen = Boolean(document.querySelector("command-palette")?.open);
if (isTargetPopup || isWikiOpen || isTargetInput || isPopupOpened || isSearchCommandOpen) {
return;
}
diff --git a/public/components/wiki/wiki.js b/public/components/wiki/wiki.js
index efc14d8f..7b1aec97 100644
--- a/public/components/wiki/wiki.js
+++ b/public/components/wiki/wiki.js
@@ -58,7 +58,7 @@ export class Wiki {
#keydownHotkeys(event) {
const isTargetInput = event.target.tagName === "INPUT";
const isTargetPopup = event.target.id === "popup--background";
- const isSearchCommandOpen = Boolean(document.querySelector("search-command")?.open);
+ const isSearchCommandOpen = Boolean(document.querySelector("command-palette")?.open);
if (isTargetInput || isTargetPopup || isSearchCommandOpen) {
return;
}
diff --git a/public/core/events.js b/public/core/events.js
index 3602f101..a7733455 100644
--- a/public/core/events.js
+++ b/public/core/events.js
@@ -10,7 +10,7 @@ export const EVENTS = {
MODAL_OPENED: "modal-opened",
NETWORK_VIEW_HID: "network-view-hid",
NETWORK_VIEW_SHOWED: "network-view-showed",
- SEARCH_COMMAND_INIT: "search-command-init",
+ COMMAND_PALETTE_INIT: "command-palette-init",
DRILL_RESET: "drill-reset",
DRILL_BACK: "drill-back",
DRILL_SWITCH: "drill-switch",
diff --git a/public/core/network-navigation.js b/public/core/network-navigation.js
index 978b0b08..9e4bd11d 100644
--- a/public/core/network-navigation.js
+++ b/public/core/network-navigation.js
@@ -131,7 +131,7 @@ export class NetworkNavigation {
const isWikiOpen = document.getElementById("documentation-root-element").classList.contains("slide-in");
const isTargetPopup = event.target.id === "popup--background";
const isTargetInput = event.target.tagName === "INPUT";
- const isSearchCommandOpen = Boolean(document.querySelector("search-command")?.open);
+ const isSearchCommandOpen = Boolean(document.querySelector("command-palette")?.open);
if (isNetworkViewHidden || isWikiOpen || isTargetPopup || isTargetInput || isSearchCommandOpen) {
return;
}
diff --git a/public/main.js b/public/main.js
index 950a8c80..88392420 100644
--- a/public/main.js
+++ b/public/main.js
@@ -9,7 +9,7 @@ import "./components/popup/popup.js";
import "./components/locker/locker.js";
import "./components/legend/legend.js";
import "./components/locked-navigation/locked-navigation.js";
-import "./components/search-command/search-command.js";
+import "./components/command-palette/command-palette.js";
import "./components/views/settings/settings.js";
import { HomeView } from "./components/views/home/home.js";
import "./components/views/search/search.js";
@@ -194,7 +194,7 @@ async function onSocketPayload(event) {
window.navigation.setNavByName(targetView);
}
- dispatchSearchCommandInit();
+ dispatchCommandPaletteInit();
}
async function onSocketInitOrReload(event) {
@@ -231,15 +231,15 @@ async function onSocketInitOrReload(event) {
window.navigation.setNavByName("search--view");
}
- dispatchSearchCommandInit();
+ dispatchCommandPaletteInit();
}
-function dispatchSearchCommandInit() {
+function dispatchCommandPaletteInit() {
if (!nsn || !secureDataSet) {
return;
}
- const event = new CustomEvent(EVENTS.SEARCH_COMMAND_INIT, {
+ const event = new CustomEvent(EVENTS.COMMAND_PALETTE_INIT, {
detail: {
network: nsn,
linker: secureDataSet.linker,
diff --git a/test/e2e/search-command.spec.js b/test/e2e/command-palette.spec.js
similarity index 59%
rename from test/e2e/search-command.spec.js
rename to test/e2e/command-palette.spec.js
index 173246f6..9d0c5c45 100644
--- a/test/e2e/search-command.spec.js
+++ b/test/e2e/command-palette.spec.js
@@ -1,7 +1,7 @@
// Import Third-party Dependencies
import { test, expect } from "@playwright/test";
-test.describe("[search-command] presets", () => {
+test.describe("[command-palette] presets and actions", () => {
let i18n;
test.beforeEach(async({ page }) => {
@@ -26,7 +26,13 @@ test.describe("[search-command] presets", () => {
});
test("renders all five preset buttons", async({ page }) => {
- await expect(page.locator(".range-preset")).toHaveCount(5);
+ const presetsSection = page.locator(".section").filter({ hasText: i18n.section_presets });
+ await expect(presetsSection.locator(".range-preset")).toHaveCount(5);
+ });
+
+ test("renders the theme toggle action button", async({ page }) => {
+ const actionsSection = page.locator(".section").filter({ hasText: i18n.section_actions });
+ await expect(actionsSection.locator(".range-preset")).toHaveCount(1);
});
test("clicking a preset adds a chip and hides the presets section", async({ page }) => {
@@ -55,6 +61,29 @@ test.describe("[search-command] presets", () => {
await expect(page.locator(".empty-state")).toHaveText(i18n.empty_after_filter);
});
+ test("clicking the theme action closes the palette and toggles the theme", async({ page }) => {
+ const initialTheme = await page.evaluate(() => window.settings.config.theme);
+ const expectedTheme = initialTheme === "dark" ? "light" : "dark";
+
+ const actionsSection = page.locator(".section").filter({ hasText: i18n.section_actions });
+ await actionsSection.locator(".range-preset").click();
+
+ await expect(page.locator(".backdrop")).not.toBeVisible();
+ const newTheme = await page.evaluate(() => window.settings.config.theme);
+ expect(newTheme).toBe(expectedTheme);
+ });
+
+ test("Alt+T triggers the theme toggle and closes the palette", async({ page }) => {
+ const initialTheme = await page.evaluate(() => window.settings.config.theme);
+ const expectedTheme = initialTheme === "dark" ? "light" : "dark";
+
+ await page.keyboard.press("Alt+t");
+
+ await expect(page.locator(".backdrop")).not.toBeVisible();
+ const newTheme = await page.evaluate(() => window.settings.config.theme);
+ expect(newTheme).toBe(expectedTheme);
+ });
+
test("pressing Escape closes the palette", async({ page }) => {
await page.keyboard.press("Escape");
diff --git a/test/ui/search-command-filters.test.js b/test/ui/command-palette-filters.test.js
similarity index 99%
rename from test/ui/search-command-filters.test.js
rename to test/ui/command-palette-filters.test.js
index 71b79387..d8c623ed 100644
--- a/test/ui/search-command-filters.test.js
+++ b/test/ui/command-palette-filters.test.js
@@ -8,7 +8,7 @@ import {
getFlagCounts,
getFilterValueCounts,
getHelperValues
-} from "../../public/components/search-command/filters.js";
+} from "../../public/components/command-palette/filters.js";
const kLinker = new Map([
[0, {
diff --git a/views/index.html b/views/index.html
index 778105fa..6c7efe71 100644
--- a/views/index.html
+++ b/views/index.html
@@ -105,5 +105,5 @@
-
+