Fix navigation (#7376)
* Removed double entries from the search to improve navigation * change input on search field to improve searchability * added type to search to make sure that LXC and VM's dont get mixed up * run linting over changes --------- Co-authored-by: Bram Suurd <bram.suurd@infracom.nl>
This commit is contained in:
parent
45a2163e66
commit
a24169e9b8
@ -36,19 +36,24 @@ export function formattedBadge(type: string) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// random Script
|
||||
function getRandomScript(categories: Category[]): Script | null {
|
||||
function getRandomScript(categories: Category[], previouslySelected: Set<string> = new Set()): Script | null {
|
||||
const allScripts = categories.flatMap(cat => cat.scripts || []);
|
||||
if (allScripts.length === 0)
|
||||
return null;
|
||||
const idx = Math.floor(Math.random() * allScripts.length);
|
||||
return allScripts[idx];
|
||||
|
||||
const availableScripts = allScripts.filter(script => !previouslySelected.has(script.slug));
|
||||
if (availableScripts.length === 0) {
|
||||
return allScripts[Math.floor(Math.random() * allScripts.length)];
|
||||
}
|
||||
const idx = Math.floor(Math.random() * availableScripts.length);
|
||||
return availableScripts[idx];
|
||||
}
|
||||
|
||||
export default function CommandMenu() {
|
||||
function CommandMenu() {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [links, setLinks] = React.useState<Category[]>([]);
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
const [selectedScripts, setSelectedScripts] = React.useState<Set<string>>(new Set());
|
||||
const router = useRouter();
|
||||
|
||||
const fetchSortedCategories = () => {
|
||||
@ -65,25 +70,26 @@ export default function CommandMenu() {
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
const down = (e: KeyboardEvent) => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault();
|
||||
fetchSortedCategories();
|
||||
setOpen(open => !open);
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", down);
|
||||
return () => document.removeEventListener("keydown", down);
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => document.removeEventListener("keydown", handleKeyDown);
|
||||
}, []);
|
||||
|
||||
const openRandomScript = async () => {
|
||||
const handleOpenRandomScript = async () => {
|
||||
if (links.length === 0) {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const categories = await fetchCategories();
|
||||
setLinks(categories);
|
||||
const randomScript = getRandomScript(categories);
|
||||
const randomScript = getRandomScript(categories, selectedScripts);
|
||||
if (randomScript) {
|
||||
setSelectedScripts(prev => new Set([...prev, randomScript.slug]));
|
||||
router.push(`/scripts?id=${randomScript.slug}`);
|
||||
}
|
||||
}
|
||||
@ -92,13 +98,54 @@ export default function CommandMenu() {
|
||||
}
|
||||
}
|
||||
else {
|
||||
const randomScript = getRandomScript(links);
|
||||
const randomScript = getRandomScript(links, selectedScripts);
|
||||
if (randomScript) {
|
||||
setSelectedScripts(prev => new Set([...prev, randomScript.slug]));
|
||||
router.push(`/scripts?id=${randomScript.slug}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getUniqueScriptsMap = React.useCallback(() => {
|
||||
const scriptMap = new Map<string, { script: Script; categoryName: string }>();
|
||||
for (const category of links) {
|
||||
for (const script of category.scripts) {
|
||||
if (!scriptMap.has(script.slug)) {
|
||||
scriptMap.set(script.slug, { script, categoryName: category.name });
|
||||
}
|
||||
}
|
||||
}
|
||||
return scriptMap;
|
||||
}, [links]);
|
||||
|
||||
const getUniqueScriptsByCategory = React.useCallback(() => {
|
||||
const scriptMap = getUniqueScriptsMap();
|
||||
const categoryOrder = links.map(cat => cat.name);
|
||||
const grouped: Record<string, Script[]> = {};
|
||||
|
||||
for (const name of categoryOrder) {
|
||||
grouped[name] = [];
|
||||
}
|
||||
|
||||
for (const { script, categoryName } of scriptMap.values()) {
|
||||
if (grouped[categoryName]) {
|
||||
grouped[categoryName].push(script);
|
||||
}
|
||||
else {
|
||||
grouped[categoryName] = [script];
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(grouped).forEach((cat) => {
|
||||
if (grouped[cat].length === 0)
|
||||
delete grouped[cat];
|
||||
});
|
||||
|
||||
return grouped;
|
||||
}, [getUniqueScriptsMap, links]);
|
||||
|
||||
const uniqueScriptsByCategory = getUniqueScriptsByCategory();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex gap-2">
|
||||
@ -122,7 +169,20 @@ export default function CommandMenu() {
|
||||
<TooltipProvider>
|
||||
<Tooltip delayDuration={100}>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="outline" size="icon" onClick={openRandomScript} disabled={isLoading} className="hidden lg:flex">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={handleOpenRandomScript}
|
||||
disabled={isLoading}
|
||||
className="hidden lg:flex"
|
||||
aria-label="Open Random Script"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
handleOpenRandomScript();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Sparkles className="size-4" />
|
||||
<span className="sr-only">Open Random Script</span>
|
||||
</Button>
|
||||
@ -139,16 +199,24 @@ export default function CommandMenu() {
|
||||
<CommandInput placeholder="Search for a script..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>{isLoading ? "Loading..." : "No scripts found."}</CommandEmpty>
|
||||
{links.map(category => (
|
||||
<CommandGroup key={`category:${category.name}`} heading={category.name}>
|
||||
{category.scripts.map(script => (
|
||||
{Object.entries(uniqueScriptsByCategory).map(([categoryName, scripts]) => (
|
||||
<CommandGroup key={`category:${categoryName}`} heading={categoryName}>
|
||||
{scripts.map(script => (
|
||||
<CommandItem
|
||||
key={`script:${script.slug}`}
|
||||
value={`${script.slug}-${script.name}`}
|
||||
value={`${script.name}-${script.type}`}
|
||||
onSelect={() => {
|
||||
setOpen(false);
|
||||
router.push(`/scripts?id=${script.slug}`);
|
||||
}}
|
||||
tabIndex={0}
|
||||
aria-label={`Open script ${script.name}`}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
setOpen(false);
|
||||
router.push(`/scripts?id=${script.slug}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex gap-2" onClick={() => setOpen(false)}>
|
||||
<Image
|
||||
@ -172,3 +240,5 @@ export default function CommandMenu() {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default CommandMenu;
|
||||
|
Loading…
x
Reference in New Issue
Block a user