wip on fixing seerr media request
This commit is contained in:
18 files changed
+794
-194
No files matched your search
@@ -44,6 +44,9 @@ export const SpotlightGroupActionItem = <TOption extends Record<string, unknown>
|
||||
setQuery(interaction.query);
|
||||
setTimeout(() => selectAction(0, spotlightStore));
|
||||
} else if (interaction.type === "mode") {
|
||||
if (interaction.query !== undefined) {
|
||||
setQuery(interaction.query);
|
||||
}
|
||||
setMode(interaction.mode);
|
||||
} else if (interaction.type === "children") {
|
||||
setChildrenOptions(interaction);
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
"use client";
|
||||
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { ActionIcon, Center, Group, Kbd } from "@mantine/core";
|
||||
import { Spotlight as MantineSpotlight } from "@mantine/spotlight";
|
||||
import { IconSearch, IconX } from "@tabler/icons-react";
|
||||
import { useSetAtom } from "jotai";
|
||||
|
||||
import { hotkeys } from "@homarr/definitions";
|
||||
import type { TranslationObject } from "@homarr/translation";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
|
||||
import type { OpenMediaRequestSearchOptions } from "../index";
|
||||
import type { inferSearchInteractionOptions } from "../lib/interaction";
|
||||
import type { SearchMode } from "../lib/mode";
|
||||
import { searchModes } from "../modes";
|
||||
import { useHomeEmptyGroups } from "../modes/help/home-empty-groups";
|
||||
import { selectAction, spotlightStore } from "../spotlight-store";
|
||||
import {
|
||||
mediaRequestSearchEvent,
|
||||
mediaRequestSearchScopeAtom,
|
||||
selectAction,
|
||||
spotlightActions,
|
||||
spotlightStore,
|
||||
} from "../spotlight-store";
|
||||
import { SpotlightChildrenActions } from "./actions/children-actions";
|
||||
import { SpotlightActionGroups } from "./actions/groups/action-group";
|
||||
|
||||
@@ -49,10 +57,35 @@ const SpotlightWithActiveMode = ({ modeState, queryState, activeMode }: Spotligh
|
||||
const [childrenOptions, setChildrenOptions] = useState<inferSearchInteractionOptions<"children"> | null>(null);
|
||||
const t = useI18n();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const setMediaRequestSearchScope = useSetAtom(mediaRequestSearchScopeAtom);
|
||||
// Works as always the same amount of hooks are executed
|
||||
const useGroups = "groups" in activeMode ? () => activeMode.groups : activeMode.useGroups;
|
||||
const groups = useGroups();
|
||||
const homeEmptyGroups = useHomeEmptyGroups();
|
||||
const hasModeCharacter = activeMode.modeKey !== defaultMode && activeMode.character !== undefined;
|
||||
|
||||
useEffect(() => {
|
||||
const handleMediaRequestSearch = (event: Event) => {
|
||||
const { integrationIds, query } = (event as CustomEvent<OpenMediaRequestSearchOptions>).detail ?? {};
|
||||
setMediaRequestSearchScope({ integrationIds });
|
||||
setMode("media");
|
||||
setChildrenOptions(null);
|
||||
setQuery(query ?? "");
|
||||
spotlightActions.open();
|
||||
setTimeout(() => selectAction(0, spotlightStore));
|
||||
};
|
||||
|
||||
window.addEventListener(mediaRequestSearchEvent, handleMediaRequestSearch);
|
||||
return () => {
|
||||
window.removeEventListener(mediaRequestSearchEvent, handleMediaRequestSearch);
|
||||
};
|
||||
}, [setMediaRequestSearchScope, setMode, setQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mode !== "media") {
|
||||
setMediaRequestSearchScope({});
|
||||
}
|
||||
}, [mode, setMediaRequestSearchScope]);
|
||||
|
||||
return (
|
||||
<MantineSpotlight.Root
|
||||
@@ -87,13 +120,13 @@ const SpotlightWithActiveMode = ({ modeState, queryState, activeMode }: Spotligh
|
||||
<MantineSpotlight.Search
|
||||
placeholder={`${t("search.placeholder")}...`}
|
||||
ref={inputRef}
|
||||
leftSectionWidth={activeMode.modeKey !== defaultMode ? 80 : 48}
|
||||
leftSectionWidth={hasModeCharacter ? 80 : 48}
|
||||
leftSection={
|
||||
<Group align="center" wrap="nowrap" gap="xs" w="100%" h="100%">
|
||||
<Center w={48} h="100%">
|
||||
<IconSearch stroke={1.5} />
|
||||
</Center>
|
||||
{activeMode.modeKey !== defaultMode ? <Kbd size="sm">{activeMode.character}</Kbd> : null}
|
||||
{hasModeCharacter ? <Kbd size="sm">{activeMode.character}</Kbd> : null}
|
||||
</Group>
|
||||
}
|
||||
styles={{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { spotlightActions } from "./spotlight-store";
|
||||
import { mediaRequestSearchEvent, spotlightActions } from "./spotlight-store";
|
||||
|
||||
export { Spotlight } from "./components/spotlight";
|
||||
export { openSpotlight };
|
||||
export { openSpotlight, openMediaRequestSearch };
|
||||
export {
|
||||
SpotlightProvider,
|
||||
useRegisterSpotlightContextResults,
|
||||
@@ -11,3 +11,18 @@ export {
|
||||
} from "./modes/home/context";
|
||||
|
||||
const openSpotlight = spotlightActions.open;
|
||||
|
||||
export interface OpenMediaRequestSearchOptions {
|
||||
integrationIds?: string[];
|
||||
query?: string;
|
||||
}
|
||||
|
||||
const openMediaRequestSearch = (options: OpenMediaRequestSearchOptions = {}) => {
|
||||
if (typeof window !== "undefined") {
|
||||
window.dispatchEvent(new CustomEvent<OpenMediaRequestSearchOptions>(mediaRequestSearchEvent, { detail: options }));
|
||||
}
|
||||
|
||||
spotlightActions.open();
|
||||
};
|
||||
|
||||
export { mediaRequestSearchEvent };
|
||||
@@ -12,7 +12,7 @@ const searchInteractions = [
|
||||
createSearchInteraction("link").optionsType<{ href: string; newTab?: boolean }>(),
|
||||
createSearchInteraction("javaScript").optionsType<{ onSelect: () => MaybePromise<void> }>(),
|
||||
createSearchInteraction("setQuery").optionsType<{ query: string }>(),
|
||||
createSearchInteraction("mode").optionsType<{ mode: keyof TranslationObject["search"]["mode"] }>(),
|
||||
createSearchInteraction("mode").optionsType<{ mode: keyof TranslationObject["search"]["mode"]; query?: string }>(),
|
||||
createSearchInteraction("children").optionsType<{
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
useActions: CreateChildrenOptionsProps<any>["useActions"];
|
||||
|
||||
@@ -64,6 +64,36 @@ type MediaRequestChildrenProps = {
|
||||
kind: IntegrationKind;
|
||||
url: string;
|
||||
id: string;
|
||||
permissions?: {
|
||||
hasInteractAccess: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export const useMediaRequestSearchInteraction = (
|
||||
integration: MediaRequestChildrenProps["integration"],
|
||||
searchResult: FromIntegrationSearchResult,
|
||||
): inferSearchInteractionDefinition<"link" | "children"> => {
|
||||
const { openSearchInNewTab } = useSettings();
|
||||
|
||||
const type = searchResult.type;
|
||||
if (type === "person") {
|
||||
return {
|
||||
type: "link",
|
||||
href: searchResult.link,
|
||||
newTab: openSearchInNewTab,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: "children",
|
||||
...mediaRequestsChildrenOptions({
|
||||
result: {
|
||||
...searchResult,
|
||||
type,
|
||||
},
|
||||
integration,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -71,8 +101,6 @@ export const useFromIntegrationSearchInteraction = (
|
||||
searchEngine: SearchEngine,
|
||||
searchResult: FromIntegrationSearchResult,
|
||||
): inferSearchInteractionDefinition<"link" | "javaScript" | "children"> => {
|
||||
const { openSearchInNewTab } = useSettings();
|
||||
|
||||
if (searchEngine.type !== "fromIntegration") {
|
||||
throw new Error("Invalid search engine type");
|
||||
}
|
||||
@@ -87,25 +115,7 @@ export const useFromIntegrationSearchInteraction = (
|
||||
) &&
|
||||
"type" in searchResult
|
||||
) {
|
||||
const type = searchResult.type;
|
||||
if (type === "person") {
|
||||
return {
|
||||
type: "link",
|
||||
href: searchResult.link,
|
||||
newTab: openSearchInNewTab,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: "children",
|
||||
...mediaRequestsChildrenOptions({
|
||||
result: {
|
||||
...searchResult,
|
||||
type,
|
||||
},
|
||||
integration: searchEngine.integration,
|
||||
}),
|
||||
};
|
||||
return useMediaRequestSearchInteraction(searchEngine.integration, searchResult);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -115,13 +125,13 @@ export const useFromIntegrationSearchInteraction = (
|
||||
};
|
||||
};
|
||||
|
||||
const mediaRequestsChildrenOptions = createChildrenOptions<MediaRequestChildrenProps>({
|
||||
export const mediaRequestsChildrenOptions = createChildrenOptions<MediaRequestChildrenProps>({
|
||||
useActions() {
|
||||
const { openModal } = useModalAction(RequestMediaModal);
|
||||
return [
|
||||
{
|
||||
key: "request",
|
||||
hide: (option) => option.result.inLibrary,
|
||||
hide: (option) => option.result.inLibrary || option.integration.permissions?.hasInteractAccess === false,
|
||||
Component(option) {
|
||||
const t = useScopedI18n("search.mode.media");
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Box, Group, Stack, Text } from "@mantine/core";
|
||||
import { IconSearch, IconSearchOff } from "@tabler/icons-react";
|
||||
import { IconMovie, IconSearch, IconSearchOff } from "@tabler/icons-react";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
@@ -20,6 +20,7 @@ type GroupItem = {
|
||||
name: string;
|
||||
description?: string;
|
||||
icon: TablerIcon | string;
|
||||
disabled?: boolean;
|
||||
useInteraction: (query: string) => inferSearchInteractionDefinition<SearchInteraction>;
|
||||
};
|
||||
|
||||
@@ -37,7 +38,7 @@ export const homeSearchEngineGroup = createGroup<GroupItem>({
|
||||
);
|
||||
|
||||
return (
|
||||
<Group w="100%" wrap="nowrap" align="center" px="md" py="xs">
|
||||
<Group w="100%" wrap="nowrap" align="center" px="md" py="xs" opacity={item.disabled ? 0.55 : 1}>
|
||||
{icon}
|
||||
<Stack gap={0}>
|
||||
<Text>{item.name}</Text>
|
||||
@@ -63,6 +64,10 @@ export const homeSearchEngineGroup = createGroup<GroupItem>({
|
||||
clientApi.searchEngine.getDefaultSearchEngine.useQuery(undefined, {
|
||||
enabled: status !== "loading",
|
||||
});
|
||||
const { data: mediaRequestSearchTargets, ...mediaRequestSearchTargetsQuery } =
|
||||
clientApi.integration.mediaRequestSearchTargets.useQuery(undefined, {
|
||||
enabled: status !== "loading" && session?.user !== undefined,
|
||||
});
|
||||
const fromIntegrationEnabled = defaultSearchEngine?.type === "fromIntegration" && query.length > 0;
|
||||
const { data: results, ...resultQuery } = clientApi.integration.searchInIntegration.useQuery(
|
||||
{
|
||||
@@ -77,9 +82,15 @@ export const homeSearchEngineGroup = createGroup<GroupItem>({
|
||||
|
||||
return {
|
||||
isLoading:
|
||||
defaultSearchEngineQuery.isLoading || (resultQuery.isLoading && fromIntegrationEnabled) || status === "loading",
|
||||
isError: defaultSearchEngineQuery.isError || (resultQuery.isError && fromIntegrationEnabled),
|
||||
data: createDefaultSearchEntries(defaultSearchEngine, results, session, query, t),
|
||||
defaultSearchEngineQuery.isLoading ||
|
||||
mediaRequestSearchTargetsQuery.isLoading ||
|
||||
(resultQuery.isLoading && fromIntegrationEnabled) ||
|
||||
status === "loading",
|
||||
isError:
|
||||
defaultSearchEngineQuery.isError ||
|
||||
mediaRequestSearchTargetsQuery.isError ||
|
||||
(resultQuery.isError && fromIntegrationEnabled),
|
||||
data: createDefaultSearchEntries(defaultSearchEngine, results, mediaRequestSearchTargets, session, query, t),
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -87,16 +98,40 @@ export const homeSearchEngineGroup = createGroup<GroupItem>({
|
||||
const createDefaultSearchEntries = (
|
||||
defaultSearchEngine: RouterOutputs["searchEngine"]["getDefaultSearchEngine"] | null,
|
||||
results: RouterOutputs["integration"]["searchInIntegration"] | undefined,
|
||||
mediaRequestSearchTargets: RouterOutputs["integration"]["mediaRequestSearchTargets"] | undefined,
|
||||
session: Session | null,
|
||||
query: string,
|
||||
t: TranslationFunction,
|
||||
): GroupItem[] => {
|
||||
if (!session?.user && !defaultSearchEngine) {
|
||||
return [];
|
||||
}
|
||||
const mediaRequestSearchEntry: GroupItem = {
|
||||
id: "media-request-search",
|
||||
name: t("search.mode.media.action.search.label"),
|
||||
description:
|
||||
mediaRequestSearchTargets && mediaRequestSearchTargets.length === 0
|
||||
? t("search.mode.media.action.search.disabled.noIntegration")
|
||||
: t("search.mode.media.action.search.description"),
|
||||
icon: IconMovie,
|
||||
disabled: !mediaRequestSearchTargets || mediaRequestSearchTargets.length === 0,
|
||||
useInteraction(query) {
|
||||
if (!mediaRequestSearchTargets || mediaRequestSearchTargets.length === 0) {
|
||||
return {
|
||||
type: "none",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: "mode",
|
||||
mode: "media",
|
||||
query,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
if (!session?.user && !defaultSearchEngine) return [mediaRequestSearchEntry];
|
||||
|
||||
if (!defaultSearchEngine) {
|
||||
return [
|
||||
mediaRequestSearchEntry,
|
||||
{
|
||||
id: "no-default",
|
||||
name: t("search.mode.home.group.search.option.no-default.label"),
|
||||
@@ -115,6 +150,7 @@ const createDefaultSearchEntries = (
|
||||
|
||||
if (defaultSearchEngine.type === "generic") {
|
||||
return [
|
||||
mediaRequestSearchEntry,
|
||||
{
|
||||
id: "search",
|
||||
name: t("search.mode.home.group.search.option.search.label", {
|
||||
@@ -137,6 +173,7 @@ const createDefaultSearchEntries = (
|
||||
|
||||
if (!results) {
|
||||
return [
|
||||
mediaRequestSearchEntry,
|
||||
{
|
||||
id: "from-integration",
|
||||
name: defaultSearchEngine.name,
|
||||
@@ -151,13 +188,16 @@ const createDefaultSearchEntries = (
|
||||
];
|
||||
}
|
||||
|
||||
return results.map((result) => ({
|
||||
id: `search-${result.id}`,
|
||||
name: result.name,
|
||||
description: result.text,
|
||||
icon: result.image ?? IconSearch,
|
||||
useInteraction() {
|
||||
return useFromIntegrationSearchInteraction(defaultSearchEngine, result);
|
||||
},
|
||||
}));
|
||||
return [
|
||||
mediaRequestSearchEntry,
|
||||
...results.map((result) => ({
|
||||
id: `search-${result.id}`,
|
||||
name: result.name,
|
||||
description: result.text,
|
||||
icon: result.image ?? IconSearch,
|
||||
useInteraction() {
|
||||
return useFromIntegrationSearchInteraction(defaultSearchEngine, result);
|
||||
},
|
||||
})),
|
||||
];
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import { appIntegrationBoardMode } from "./app-integration-board";
|
||||
import { commandMode } from "./command";
|
||||
import { externalMode } from "./external";
|
||||
import { homeMode } from "./home";
|
||||
import { mediaMode } from "./media";
|
||||
import { pageMode } from "./page";
|
||||
import { userGroupMode } from "./user-group";
|
||||
|
||||
@@ -11,5 +12,6 @@ export const searchModes = [
|
||||
externalMode,
|
||||
commandMode,
|
||||
pageMode,
|
||||
mediaMode,
|
||||
homeMode,
|
||||
] as const;
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { SearchMode } from "../../lib/mode";
|
||||
import { mediaRequestSearchGroup } from "./media-request-search-group";
|
||||
|
||||
export const mediaMode = {
|
||||
modeKey: "media",
|
||||
character: undefined,
|
||||
groups: [mediaRequestSearchGroup],
|
||||
} satisfies SearchMode;
|
||||
@@ -0,0 +1,150 @@
|
||||
import { Group, Image, Stack, Text } from "@mantine/core";
|
||||
import { useDebouncedValue } from "@mantine/hooks";
|
||||
import { IconMovie, IconSearch } from "@tabler/icons-react";
|
||||
import { keepPreviousData } from "@tanstack/react-query";
|
||||
import { useAtomValue } from "jotai";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { getIntegrationName } from "@homarr/definitions";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import { createGroup } from "../../lib/group";
|
||||
import { mediaRequestSearchScopeAtom } from "../../spotlight-store";
|
||||
import { useMediaRequestSearchInteraction } from "../external/search-engines-search-group";
|
||||
|
||||
type MediaRequestSearchResult = RouterOutputs["integration"]["searchMediaRequests"][number];
|
||||
|
||||
type MediaRequestSearchOption =
|
||||
| {
|
||||
key: string;
|
||||
kind: "disabled" | "hint";
|
||||
label: string;
|
||||
description: string;
|
||||
}
|
||||
| {
|
||||
key: string;
|
||||
kind: "result";
|
||||
result: MediaRequestSearchResult;
|
||||
};
|
||||
|
||||
export const mediaRequestSearchGroup = createGroup<MediaRequestSearchOption>({
|
||||
keyPath: "key",
|
||||
title: (t) => t("search.mode.media.group.title"),
|
||||
Component(option) {
|
||||
if (option.kind !== "result") {
|
||||
return (
|
||||
<Group w="100%" wrap="nowrap" align="center" px="md" py="xs" opacity={option.kind === "disabled" ? 0.55 : 1}>
|
||||
{option.kind === "disabled" ? <IconMovie stroke={1.5} /> : <IconSearch stroke={1.5} />}
|
||||
<Stack gap={0}>
|
||||
<Text size="sm">{option.label}</Text>
|
||||
<Text size="xs" c="gray.6">
|
||||
{option.description}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
const { result } = option;
|
||||
return (
|
||||
<Group w="100%" wrap="nowrap" align="center" px="md" py="xs">
|
||||
{result.image ? (
|
||||
<Image src={result.image} w={35} h={50} fit="cover" radius="md" />
|
||||
) : (
|
||||
<IconSearch stroke={1.5} size={35} />
|
||||
)}
|
||||
<Stack gap={2}>
|
||||
<Text>{result.name}</Text>
|
||||
<Text c="dimmed" size="sm" lineClamp={2}>
|
||||
{result.text ?? getIntegrationName(result.integration.kind)}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
);
|
||||
},
|
||||
useInteraction(option) {
|
||||
if (option.kind !== "result") {
|
||||
return {
|
||||
type: "none",
|
||||
};
|
||||
}
|
||||
|
||||
return useMediaRequestSearchInteraction(option.result.integration, option.result);
|
||||
},
|
||||
useQueryOptions(query) {
|
||||
const tMedia = useScopedI18n("search.mode.media");
|
||||
const scope = useAtomValue(mediaRequestSearchScopeAtom);
|
||||
const [debouncedQuery] = useDebouncedValue(query.trim(), 150);
|
||||
const targetsQuery = clientApi.integration.mediaRequestSearchTargets.useQuery();
|
||||
|
||||
const scopedTargets = (targetsQuery.data ?? []).filter((target) =>
|
||||
scope.integrationIds ? scope.integrationIds.includes(target.id) : true,
|
||||
);
|
||||
const hasScopedTargets = scopedTargets.length > 0;
|
||||
const shouldSearch = debouncedQuery.length > 0 && hasScopedTargets;
|
||||
const integrationIds = scope.integrationIds ? scopedTargets.map((target) => target.id) : undefined;
|
||||
|
||||
const resultsQuery = clientApi.integration.searchMediaRequests.useQuery(
|
||||
{ query: debouncedQuery, integrationIds },
|
||||
{
|
||||
enabled: shouldSearch,
|
||||
placeholderData: keepPreviousData,
|
||||
select: (data) => data.slice(0, 10),
|
||||
},
|
||||
);
|
||||
|
||||
const isLoading = targetsQuery.isLoading || (shouldSearch && resultsQuery.isLoading);
|
||||
const isError = targetsQuery.isError || (shouldSearch && resultsQuery.isError);
|
||||
|
||||
if (!targetsQuery.data) {
|
||||
return {
|
||||
isLoading,
|
||||
isError,
|
||||
data: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (!hasScopedTargets) {
|
||||
return {
|
||||
isLoading,
|
||||
isError,
|
||||
data: [
|
||||
{
|
||||
key: "disabled",
|
||||
kind: "disabled",
|
||||
label: tMedia("action.search.label"),
|
||||
description: scope.integrationIds
|
||||
? tMedia("action.search.disabled.scoped")
|
||||
: tMedia("action.search.disabled.noIntegration"),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (debouncedQuery.length === 0) {
|
||||
return {
|
||||
isLoading,
|
||||
isError,
|
||||
data: [
|
||||
{
|
||||
key: "hint",
|
||||
kind: "hint",
|
||||
label: tMedia("action.search.label"),
|
||||
description: tMedia("action.search.description"),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
isError,
|
||||
data: (resultsQuery.data ?? []).map((result) => ({
|
||||
key: `result-${result.integration.id}-${result.type}-${result.id}`,
|
||||
kind: "result",
|
||||
result,
|
||||
})),
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -3,9 +3,17 @@
|
||||
import { clamp } from "@mantine/hooks";
|
||||
import type { SpotlightStore } from "@mantine/spotlight";
|
||||
import { createSpotlight } from "@mantine/spotlight";
|
||||
import { atom } from "jotai";
|
||||
|
||||
export const [spotlightStore, spotlightActions] = createSpotlight();
|
||||
|
||||
export interface MediaRequestSearchScope {
|
||||
integrationIds?: string[];
|
||||
}
|
||||
|
||||
export const mediaRequestSearchScopeAtom = atom<MediaRequestSearchScope>({});
|
||||
export const mediaRequestSearchEvent = "homarr:spotlight:media-request-search";
|
||||
|
||||
export const setSelectedAction = (index: number, store: SpotlightStore) => {
|
||||
store.updateState((state) => ({ ...state, selected: index }));
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user