Merge branch 'dev' of https://github.com/homarr-labs/homarr into dev
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
"@homarr/db": "workspace:^0.1.0",
|
"@homarr/db": "workspace:^0.1.0",
|
||||||
"@homarr/definitions": "workspace:^0.1.0",
|
"@homarr/definitions": "workspace:^0.1.0",
|
||||||
"@homarr/form": "workspace:^0.1.0",
|
"@homarr/form": "workspace:^0.1.0",
|
||||||
|
"@homarr/gridstack": "^1.0.0",
|
||||||
"@homarr/notifications": "workspace:^0.1.0",
|
"@homarr/notifications": "workspace:^0.1.0",
|
||||||
"@homarr/spotlight": "workspace:^0.1.0",
|
"@homarr/spotlight": "workspace:^0.1.0",
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
@@ -29,9 +30,9 @@
|
|||||||
"@mantine/modals": "^7.5.3",
|
"@mantine/modals": "^7.5.3",
|
||||||
"@mantine/tiptap": "^7.5.3",
|
"@mantine/tiptap": "^7.5.3",
|
||||||
"@t3-oss/env-nextjs": "^0.9.2",
|
"@t3-oss/env-nextjs": "^0.9.2",
|
||||||
"@tanstack/react-query": "^5.20.5",
|
"@tanstack/react-query": "^5.21.2",
|
||||||
"@tanstack/react-query-devtools": "^5.21.0",
|
"@tanstack/react-query-devtools": "^5.21.3",
|
||||||
"@tanstack/react-query-next-experimental": "5.20.5",
|
"@tanstack/react-query-next-experimental": "5.21.2",
|
||||||
"@tiptap/extension-link": "^2.2.3",
|
"@tiptap/extension-link": "^2.2.3",
|
||||||
"@tiptap/react": "^2.2.3",
|
"@tiptap/react": "^2.2.3",
|
||||||
"@tiptap/starter-kit": "^2.2.3",
|
"@tiptap/starter-kit": "^2.2.3",
|
||||||
@@ -40,22 +41,22 @@
|
|||||||
"@trpc/react-query": "next",
|
"@trpc/react-query": "next",
|
||||||
"@trpc/server": "next",
|
"@trpc/server": "next",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"@homarr/gridstack": "^1.0.0",
|
|
||||||
"jotai": "^2.6.4",
|
"jotai": "^2.6.4",
|
||||||
"mantine-modal-manager": "^7.5.2",
|
"mantine-modal-manager": "^7.5.2",
|
||||||
"next": "^14.1.0",
|
"next": "^14.1.1-canary.58",
|
||||||
"postcss-preset-mantine": "^1.13.0",
|
"postcss-preset-mantine": "^1.13.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"sass": "^1.71.0",
|
"superjson": "2.2.1",
|
||||||
"superjson": "2.2.1"
|
"use-deep-compare-effect": "^1.8.1",
|
||||||
|
"sass": "^1.71.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node": "^20.11.19",
|
"@types/node": "^20.11.19",
|
||||||
"@types/react": "^18.2.55",
|
"@types/react": "^18.2.56",
|
||||||
"@types/react-dom": "^18.2.19",
|
"@types/react-dom": "^18.2.19",
|
||||||
"dotenv-cli": "^7.3.0",
|
"dotenv-cli": "^7.3.0",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default async function EditIntegrationPage({
|
|||||||
params,
|
params,
|
||||||
}: EditIntegrationPageProps) {
|
}: EditIntegrationPageProps) {
|
||||||
const t = await getScopedI18n("integration.page.edit");
|
const t = await getScopedI18n("integration.page.edit");
|
||||||
const integration = await api.integration.byId.query({ id: params.id });
|
const integration = await api.integration.byId({ id: params.id });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ interface IntegrationsPageProps {
|
|||||||
export default async function IntegrationsPage({
|
export default async function IntegrationsPage({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: IntegrationsPageProps) {
|
}: IntegrationsPageProps) {
|
||||||
const integrations = await api.integration.all.query();
|
const integrations = await api.integration.all();
|
||||||
const t = await getScopedI18n("integration");
|
const t = await getScopedI18n("integration");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
8
apps/nextjs/src/app/[locale]/_client-providers/jotai.tsx
Normal file
8
apps/nextjs/src/app/[locale]/_client-providers/jotai.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import type { PropsWithChildren } from "react";
|
||||||
|
import { Provider } from "jotai";
|
||||||
|
|
||||||
|
export const JotaiProvider = ({ children }: PropsWithChildren) => {
|
||||||
|
return <Provider>{children}</Provider>;
|
||||||
|
};
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import type { PropsWithChildren } from "react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||||
@@ -9,19 +10,7 @@ import superjson from "superjson";
|
|||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
|
||||||
import { env } from "~/env.mjs";
|
export function TRPCReactProvider(props: PropsWithChildren) {
|
||||||
|
|
||||||
const getBaseUrl = () => {
|
|
||||||
if (typeof window !== "undefined") return ""; // browser should use relative url
|
|
||||||
if (env.VERCEL_URL) return env.VERCEL_URL; // SSR should use vercel url
|
|
||||||
|
|
||||||
return `http://localhost:${env.PORT}`; // dev SSR should use localhost
|
|
||||||
};
|
|
||||||
|
|
||||||
export function TRPCReactProvider(props: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
headers?: Headers;
|
|
||||||
}) {
|
|
||||||
const [queryClient] = useState(
|
const [queryClient] = useState(
|
||||||
() =>
|
() =>
|
||||||
new QueryClient({
|
new QueryClient({
|
||||||
@@ -35,7 +24,6 @@ export function TRPCReactProvider(props: {
|
|||||||
|
|
||||||
const [trpcClient] = useState(() =>
|
const [trpcClient] = useState(() =>
|
||||||
clientApi.createClient({
|
clientApi.createClient({
|
||||||
transformer: superjson,
|
|
||||||
links: [
|
links: [
|
||||||
loggerLink({
|
loggerLink({
|
||||||
enabled: (opts) =>
|
enabled: (opts) =>
|
||||||
@@ -43,11 +31,12 @@ export function TRPCReactProvider(props: {
|
|||||||
(opts.direction === "down" && opts.result instanceof Error),
|
(opts.direction === "down" && opts.result instanceof Error),
|
||||||
}),
|
}),
|
||||||
unstable_httpBatchStreamLink({
|
unstable_httpBatchStreamLink({
|
||||||
url: `${getBaseUrl()}/api/trpc`,
|
transformer: superjson,
|
||||||
|
url: getBaseUrl() + "/api/trpc",
|
||||||
headers() {
|
headers() {
|
||||||
const headers = new Map(props.headers);
|
const headers = new Headers();
|
||||||
headers.set("x-trpc-source", "nextjs-react");
|
headers.set("x-trpc-source", "nextjs-react");
|
||||||
return Object.fromEntries(headers);
|
return headers;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
@@ -65,3 +54,9 @@ export function TRPCReactProvider(props: {
|
|||||||
</clientApi.Provider>
|
</clientApi.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getBaseUrl() {
|
||||||
|
if (typeof window !== "undefined") return window.location.origin;
|
||||||
|
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
|
||||||
|
return `http://localhost:${process.env.PORT ?? 3000}`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ import { createBoardPage } from "../_creator";
|
|||||||
|
|
||||||
export default createBoardPage<{ locale: string }>({
|
export default createBoardPage<{ locale: string }>({
|
||||||
async getInitialBoard() {
|
async getInitialBoard() {
|
||||||
return await api.board.default.query();
|
return await api.board.default();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ import { createBoardPage } from "../_creator";
|
|||||||
|
|
||||||
export default createBoardPage<{ locale: string; name: string }>({
|
export default createBoardPage<{ locale: string; name: string }>({
|
||||||
async getInitialBoard({ name }) {
|
async getInitialBoard({ name }) {
|
||||||
return await api.board.byName.query({ name });
|
return await api.board.byName({ name });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function BoardSettingsPage({ params }: Props) {
|
export default async function BoardSettingsPage({ params }: Props) {
|
||||||
const board = await api.board.byName.query({ name: params.name });
|
const board = await api.board.byName({ name: params.name });
|
||||||
const t = await getScopedI18n("board.setting");
|
const t = await getScopedI18n("board.setting");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
18
apps/nextjs/src/app/[locale]/compose.tsx
Normal file
18
apps/nextjs/src/app/[locale]/compose.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
type PropsWithChildren = Required<React.PropsWithChildren>;
|
||||||
|
|
||||||
|
export const composeWrappers = (
|
||||||
|
wrappers: React.FunctionComponent<PropsWithChildren>[],
|
||||||
|
): React.FunctionComponent<PropsWithChildren> => {
|
||||||
|
return wrappers
|
||||||
|
.reverse()
|
||||||
|
.reduce((Acc, Current): React.FunctionComponent<PropsWithChildren> => {
|
||||||
|
// eslint-disable-next-line react/display-name
|
||||||
|
return (props) => (
|
||||||
|
<Current>
|
||||||
|
<Acc {...props} />
|
||||||
|
</Current>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -30,7 +30,6 @@ export const InitUserForm = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = async (values: FormType) => {
|
const handleSubmit = async (values: FormType) => {
|
||||||
console.log(values);
|
|
||||||
await mutateAsync(values, {
|
await mutateAsync(values, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
showSuccessNotification({
|
showSuccessNotification({
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata, Viewport } from "next";
|
||||||
import { Inter } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
|
|
||||||
import "@homarr/ui/styles.css";
|
|
||||||
import "@homarr/notifications/styles.css";
|
import "@homarr/notifications/styles.css";
|
||||||
import "@homarr/spotlight/styles.css";
|
import "@homarr/spotlight/styles.css";
|
||||||
|
import "@homarr/ui/styles.css";
|
||||||
import { headers } from "next/headers";
|
|
||||||
|
|
||||||
import { Notifications } from "@homarr/notifications";
|
import { Notifications } from "@homarr/notifications";
|
||||||
import {
|
import {
|
||||||
@@ -14,25 +12,39 @@ import {
|
|||||||
uiConfiguration,
|
uiConfiguration,
|
||||||
} from "@homarr/ui";
|
} from "@homarr/ui";
|
||||||
|
|
||||||
|
import { JotaiProvider } from "./_client-providers/jotai";
|
||||||
import { ModalsProvider } from "./_client-providers/modals";
|
import { ModalsProvider } from "./_client-providers/modals";
|
||||||
import { NextInternationalProvider } from "./_client-providers/next-international";
|
import { NextInternationalProvider } from "./_client-providers/next-international";
|
||||||
import { TRPCReactProvider } from "./_client-providers/trpc";
|
import { TRPCReactProvider } from "./_client-providers/trpc";
|
||||||
|
import { composeWrappers } from "./compose";
|
||||||
|
|
||||||
const fontSans = Inter({
|
const fontSans = Inter({
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
variable: "--font-sans",
|
variable: "--font-sans",
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Since we're passing `headers()` to the `TRPCReactProvider` we need to
|
|
||||||
* make the entire app dynamic. You can move the `TRPCReactProvider` further
|
|
||||||
* down the tree (e.g. /dashboard and onwards) to make part of the app statically rendered.
|
|
||||||
*/
|
|
||||||
export const dynamic = "force-dynamic";
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
|
metadataBase: new URL("http://localhost:3000"),
|
||||||
title: "Create T3 Turbo",
|
title: "Create T3 Turbo",
|
||||||
description: "Simple monorepo with shared backend for web & mobile apps",
|
description: "Simple monorepo with shared backend for web & mobile apps",
|
||||||
|
openGraph: {
|
||||||
|
title: "Create T3 Turbo",
|
||||||
|
description: "Simple monorepo with shared backend for web & mobile apps",
|
||||||
|
url: "https://create-t3-turbo.vercel.app",
|
||||||
|
siteName: "Create T3 Turbo",
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: "summary_large_image",
|
||||||
|
site: "@jullerino",
|
||||||
|
creator: "@jullerino",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const viewport: Viewport = {
|
||||||
|
themeColor: [
|
||||||
|
{ media: "(prefers-color-scheme: light)", color: "white" },
|
||||||
|
{ media: "(prefers-color-scheme: dark)", color: "black" },
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Layout(props: {
|
export default function Layout(props: {
|
||||||
@@ -41,25 +53,32 @@ export default function Layout(props: {
|
|||||||
}) {
|
}) {
|
||||||
const colorScheme = "dark";
|
const colorScheme = "dark";
|
||||||
|
|
||||||
|
const StackedProvider = composeWrappers([
|
||||||
|
(innerProps) => <JotaiProvider {...innerProps} />,
|
||||||
|
(innerProps) => <TRPCReactProvider {...innerProps} />,
|
||||||
|
(innerProps) => (
|
||||||
|
<NextInternationalProvider {...innerProps} locale={props.params.locale} />
|
||||||
|
),
|
||||||
|
(innerProps) => (
|
||||||
|
<MantineProvider
|
||||||
|
{...innerProps}
|
||||||
|
defaultColorScheme={colorScheme}
|
||||||
|
{...uiConfiguration}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
(innerProps) => <ModalsProvider {...innerProps} />,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en" suppressHydrationWarning>
|
||||||
<head>
|
<head>
|
||||||
<ColorSchemeScript defaultColorScheme={colorScheme} />
|
<ColorSchemeScript defaultColorScheme={colorScheme} />
|
||||||
</head>
|
</head>
|
||||||
<body className={["font-sans", fontSans.variable].join(" ")}>
|
<body className={["font-sans", fontSans.variable].join(" ")}>
|
||||||
<TRPCReactProvider headers={headers()}>
|
<StackedProvider>
|
||||||
<NextInternationalProvider locale={props.params.locale}>
|
<Notifications />
|
||||||
<MantineProvider
|
{props.children}
|
||||||
defaultColorScheme={colorScheme}
|
</StackedProvider>
|
||||||
{...uiConfiguration}
|
|
||||||
>
|
|
||||||
<ModalsProvider>
|
|
||||||
<Notifications />
|
|
||||||
{props.children}
|
|
||||||
</ModalsProvider>
|
|
||||||
</MantineProvider>
|
|
||||||
</NextInternationalProvider>
|
|
||||||
</TRPCReactProvider>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { DeleteBoardButton } from "./_components/delete-board-button";
|
|||||||
export default async function ManageBoardsPage() {
|
export default async function ManageBoardsPage() {
|
||||||
const t = await getScopedI18n("management.page.board");
|
const t = await getScopedI18n("management.page.board");
|
||||||
|
|
||||||
const boards = await api.board.getAll.query();
|
const boards = await api.board.getAll();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const handler = auth(async (req) => {
|
|||||||
router: appRouter,
|
router: appRouter,
|
||||||
req,
|
req,
|
||||||
createContext: () =>
|
createContext: () =>
|
||||||
createTRPCContext({ auth: req.auth, headers: req.headers }),
|
createTRPCContext({ session: req.auth, headers: req.headers }),
|
||||||
onError({ error, path }) {
|
onError({ error, path }) {
|
||||||
console.error(`>>> tRPC Error on '${path}'`, error);
|
console.error(`>>> tRPC Error on '${path}'`, error);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -34,26 +34,28 @@ interface Props {
|
|||||||
export const SectionContent = ({ items, refs }: Props) => {
|
export const SectionContent = ({ items, refs }: Props) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{items.map((item) => (
|
{items.map((item) => {
|
||||||
<div
|
return (
|
||||||
key={item.id}
|
<div
|
||||||
className="grid-stack-item"
|
key={item.id}
|
||||||
data-id={item.id}
|
className="grid-stack-item"
|
||||||
gs-x={item.xOffset}
|
data-id={item.id}
|
||||||
gs-y={item.yOffset}
|
gs-x={item.xOffset}
|
||||||
gs-w={item.width}
|
gs-y={item.yOffset}
|
||||||
gs-h={item.height}
|
gs-w={item.width}
|
||||||
gs-min-w={1}
|
gs-h={item.height}
|
||||||
gs-min-h={1}
|
gs-min-w={1}
|
||||||
gs-max-w={4}
|
gs-min-h={1}
|
||||||
gs-max-h={4}
|
gs-max-w={4}
|
||||||
ref={refs.items.current[item.id] as RefObject<HTMLDivElement>}
|
gs-max-h={4}
|
||||||
>
|
ref={refs.items.current[item.id] as RefObject<HTMLDivElement>}
|
||||||
<Card className="grid-stack-item-content" withBorder>
|
>
|
||||||
<BoardItem item={item} />
|
<Card className="grid-stack-item-content" withBorder>
|
||||||
</Card>
|
<BoardItem item={item} />
|
||||||
</div>
|
</Card>
|
||||||
))}
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export const useGridstack = ({
|
|||||||
|
|
||||||
// Add listener for moving items in config from one wrapper to another
|
// Add listener for moving items in config from one wrapper to another
|
||||||
currentGrid?.on("added", (_, nodes) => {
|
currentGrid?.on("added", (_, nodes) => {
|
||||||
nodes.forEach((node) => onAdd(node));
|
nodes.forEach(onAdd);
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@@ -192,8 +192,6 @@ const useCssVariableConfiguration = ({
|
|||||||
const widgetWidth = mainRef.current.clientWidth / sectionColumnCount;
|
const widgetWidth = mainRef.current.clientWidth / sectionColumnCount;
|
||||||
// widget width is used to define sizes of gridstack items within global.scss
|
// widget width is used to define sizes of gridstack items within global.scss
|
||||||
root?.style.setProperty("--gridstack-widget-width", widgetWidth.toString());
|
root?.style.setProperty("--gridstack-widget-width", widgetWidth.toString());
|
||||||
console.log("widgetWidth", widgetWidth);
|
|
||||||
console.log(gridRef.current);
|
|
||||||
gridRef.current?.cellHeight(widgetWidth);
|
gridRef.current?.cellHeight(widgetWidth);
|
||||||
// gridRef.current is required otherwise the cellheight is run on production as undefined
|
// gridRef.current is required otherwise the cellheight is run on production as undefined
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import { Spotlight } from "@homarr/spotlight";
|
||||||
import { AppShellHeader, Group, UnstyledButton } from "@homarr/ui";
|
import { AppShellHeader, Group, UnstyledButton } from "@homarr/ui";
|
||||||
|
|
||||||
import { ClientBurger } from "./header/burger";
|
import { ClientBurger } from "./header/burger";
|
||||||
import { DesktopSearchInput, MobileSearchButton } from "./header/search";
|
import { DesktopSearchInput, MobileSearchButton } from "./header/search";
|
||||||
import { ClientSpotlight } from "./header/spotlight";
|
|
||||||
import { UserButton } from "./header/user";
|
import { UserButton } from "./header/user";
|
||||||
import { HomarrLogoWithTitle } from "./logo/homarr-logo";
|
import { HomarrLogoWithTitle } from "./logo/homarr-logo";
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ export const MainHeader = ({ logo, actions, hasNavigation = true }: Props) => {
|
|||||||
<UserButton />
|
<UserButton />
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
<ClientSpotlight />
|
<Spotlight />
|
||||||
</AppShellHeader>
|
</AppShellHeader>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { spotlight } from "@homarr/spotlight";
|
import { openSpotlight } from "@homarr/spotlight";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
import { IconSearch, TextInput, UnstyledButton } from "@homarr/ui";
|
import { IconSearch, TextInput, UnstyledButton } from "@homarr/ui";
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ export const DesktopSearchInput = () => {
|
|||||||
w={400}
|
w={400}
|
||||||
size="sm"
|
size="sm"
|
||||||
leftSection={<IconSearch size={20} stroke={1.5} />}
|
leftSection={<IconSearch size={20} stroke={1.5} />}
|
||||||
onClick={spotlight.open}
|
onClick={openSpotlight}
|
||||||
>
|
>
|
||||||
{t("placeholder")}
|
{t("placeholder")}
|
||||||
</TextInput>
|
</TextInput>
|
||||||
@@ -26,7 +26,7 @@ export const DesktopSearchInput = () => {
|
|||||||
|
|
||||||
export const MobileSearchButton = () => {
|
export const MobileSearchButton = () => {
|
||||||
return (
|
return (
|
||||||
<HeaderButton onClick={spotlight.open} className={classes.mobileSearch}>
|
<HeaderButton onClick={openSpotlight} className={classes.mobileSearch}>
|
||||||
<IconSearch size={20} stroke={1.5} />
|
<IconSearch size={20} stroke={1.5} />
|
||||||
</HeaderButton>
|
</HeaderButton>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { Spotlight } from "@homarr/spotlight";
|
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
|
||||||
import { IconSearch } from "@homarr/ui";
|
|
||||||
|
|
||||||
export const ClientSpotlight = () => {
|
|
||||||
const t = useScopedI18n("common.search");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Spotlight
|
|
||||||
actions={[]}
|
|
||||||
nothingFound={t("nothingFound")}
|
|
||||||
highlightQuery
|
|
||||||
searchProps={{
|
|
||||||
leftSection: <IconSearch size={20} stroke={1.5} />,
|
|
||||||
placeholder: `${t("placeholder")}`,
|
|
||||||
}}
|
|
||||||
yOffset={12}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,12 +1,7 @@
|
|||||||
import { cache } from "react";
|
import { cache } from "react";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
import { createTRPCClient, loggerLink, TRPCClientError } from "@trpc/client";
|
|
||||||
import { callProcedure } from "@trpc/server";
|
|
||||||
import { observable } from "@trpc/server/observable";
|
|
||||||
import type { TRPCErrorResponse } from "@trpc/server/rpc";
|
|
||||||
import SuperJSON from "superjson";
|
|
||||||
|
|
||||||
import { appRouter, createTRPCContext } from "@homarr/api";
|
import { createCaller, createTRPCContext } from "@homarr/api";
|
||||||
import { auth } from "@homarr/auth";
|
import { auth } from "@homarr/auth";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,44 +13,9 @@ const createContext = cache(async () => {
|
|||||||
heads.set("x-trpc-source", "rsc");
|
heads.set("x-trpc-source", "rsc");
|
||||||
|
|
||||||
return createTRPCContext({
|
return createTRPCContext({
|
||||||
auth: await auth(),
|
session: await auth(),
|
||||||
headers: heads,
|
headers: heads,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export const api = createTRPCClient<typeof appRouter>({
|
export const api = createCaller(createContext);
|
||||||
transformer: SuperJSON,
|
|
||||||
links: [
|
|
||||||
loggerLink({
|
|
||||||
enabled: (op) =>
|
|
||||||
process.env.NODE_ENV === "development" ||
|
|
||||||
(op.direction === "down" && op.result instanceof Error),
|
|
||||||
}),
|
|
||||||
/**
|
|
||||||
* Custom RSC link that invokes procedures directly in the server component Don't be too afraid
|
|
||||||
* about the complexity here, it's just wrapping `callProcedure` with an observable to make it a
|
|
||||||
* valid ending link for tRPC.
|
|
||||||
*/
|
|
||||||
() =>
|
|
||||||
({ op }) =>
|
|
||||||
observable((observer) => {
|
|
||||||
createContext()
|
|
||||||
.then((ctx) => {
|
|
||||||
return callProcedure({
|
|
||||||
procedures: appRouter._def.procedures,
|
|
||||||
path: op.path,
|
|
||||||
getRawInput: () => Promise.resolve(op.input),
|
|
||||||
ctx,
|
|
||||||
type: op.type,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
observer.next({ result: { data } });
|
|
||||||
observer.complete();
|
|
||||||
})
|
|
||||||
.catch((cause: TRPCErrorResponse) => {
|
|
||||||
observer.error(TRPCClientError.from(cause));
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server";
|
|
||||||
|
|
||||||
import type { AppRouter } from "./src/root";
|
|
||||||
|
|
||||||
export { appRouter, type AppRouter } from "./src/root";
|
|
||||||
export { createTRPCContext } from "./src/trpc";
|
|
||||||
/**
|
|
||||||
* Inference helpers for input types
|
|
||||||
* @example type HelloInput = RouterInputs['example']['hello']
|
|
||||||
**/
|
|
||||||
export type RouterInputs = inferRouterInputs<AppRouter>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inference helpers for output types
|
|
||||||
* @example type HelloOutput = RouterOutputs['example']['hello']
|
|
||||||
**/
|
|
||||||
export type RouterOutputs = inferRouterOutputs<AppRouter>;
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "@homarr/api",
|
"name": "@homarr/api",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./index.ts",
|
".": "./src/index.ts",
|
||||||
"./client": "./src/client.ts"
|
"./client": "./src/client.ts"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createTRPCReact } from "@trpc/react-query";
|
import { createTRPCReact } from "@trpc/react-query";
|
||||||
|
|
||||||
import type { AppRouter } from "..";
|
import type { AppRouter } from ".";
|
||||||
|
|
||||||
export const clientApi = createTRPCReact<AppRouter>();
|
export const clientApi = createTRPCReact<AppRouter>();
|
||||||
|
|||||||
33
packages/api/src/index.ts
Normal file
33
packages/api/src/index.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server";
|
||||||
|
|
||||||
|
import type { AppRouter } from "./root";
|
||||||
|
import { appRouter } from "./root";
|
||||||
|
import { createCallerFactory, createTRPCContext } from "./trpc";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a server-side caller for the tRPC API
|
||||||
|
* @example
|
||||||
|
* const trpc = createCaller(createContext);
|
||||||
|
* const res = await trpc.post.all();
|
||||||
|
* ^? Post[]
|
||||||
|
*/
|
||||||
|
const createCaller = createCallerFactory(appRouter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inference helpers for input types
|
||||||
|
* @example
|
||||||
|
* type PostByIdInput = RouterInputs['post']['byId']
|
||||||
|
* ^? { id: number }
|
||||||
|
**/
|
||||||
|
type RouterInputs = inferRouterInputs<AppRouter>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inference helpers for output types
|
||||||
|
* @example
|
||||||
|
* type AllPostsOutput = RouterOutputs['post']['all']
|
||||||
|
* ^? Post[]
|
||||||
|
**/
|
||||||
|
type RouterOutputs = inferRouterOutputs<AppRouter>;
|
||||||
|
|
||||||
|
export { createTRPCContext, appRouter, createCaller };
|
||||||
|
export type { AppRouter, RouterInputs, RouterOutputs };
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
} from "@homarr/db/schema/sqlite";
|
} from "@homarr/db/schema/sqlite";
|
||||||
import { createDb } from "@homarr/db/test";
|
import { createDb } from "@homarr/db/test";
|
||||||
|
|
||||||
import type { RouterOutputs } from "../../..";
|
import type { RouterOutputs } from "../..";
|
||||||
import { boardRouter } from "../board";
|
import { boardRouter } from "../board";
|
||||||
|
|
||||||
// Mock the auth module to return an empty session
|
// Mock the auth module to return an empty session
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { createId } from "@homarr/db";
|
|||||||
import { integrations, integrationSecrets } from "@homarr/db/schema/sqlite";
|
import { integrations, integrationSecrets } from "@homarr/db/schema/sqlite";
|
||||||
import { createDb } from "@homarr/db/test";
|
import { createDb } from "@homarr/db/test";
|
||||||
|
|
||||||
import type { RouterInputs } from "../../..";
|
import type { RouterInputs } from "../..";
|
||||||
import { encryptSecret, integrationRouter } from "../integration";
|
import { encryptSecret, integrationRouter } from "../integration";
|
||||||
import { expectToBeDefined } from "./board.spec";
|
import { expectToBeDefined } from "./board.spec";
|
||||||
|
|
||||||
|
|||||||
@@ -17,49 +17,28 @@ import { ZodError } from "@homarr/validation";
|
|||||||
/**
|
/**
|
||||||
* 1. CONTEXT
|
* 1. CONTEXT
|
||||||
*
|
*
|
||||||
* This section defines the "contexts" that are available in the backend API
|
* This section defines the "contexts" that are available in the backend API.
|
||||||
*
|
*
|
||||||
* These allow you to access things like the database, the session, etc, when
|
* These allow you to access things when processing a request, like the database, the session, etc.
|
||||||
* processing a request
|
|
||||||
*
|
*
|
||||||
*/
|
* This helper generates the "internals" for a tRPC context. The API handler and RSC clients each
|
||||||
interface CreateContextOptions {
|
* wrap this and provides the required context.
|
||||||
session: Session | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This helper generates the "internals" for a tRPC context. If you need to use
|
|
||||||
* it, you can export it from here
|
|
||||||
*
|
*
|
||||||
* Examples of things you may need it for:
|
* @see https://trpc.io/docs/server/context
|
||||||
* - testing, so we dont have to mock Next.js' req/res
|
|
||||||
* - trpc's `createSSGHelpers` where we don't have req/res
|
|
||||||
* @see https://create.t3.gg/en/usage/trpc#-servertrpccontextts
|
|
||||||
*/
|
|
||||||
const createInnerTRPCContext = (opts: CreateContextOptions) => {
|
|
||||||
return {
|
|
||||||
session: opts.session,
|
|
||||||
db,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the actual context you'll use in your router. It will be used to
|
|
||||||
* process every request that goes through your tRPC endpoint
|
|
||||||
* @link https://trpc.io/docs/context
|
|
||||||
*/
|
*/
|
||||||
export const createTRPCContext = async (opts: {
|
export const createTRPCContext = async (opts: {
|
||||||
headers?: Headers;
|
headers: Headers;
|
||||||
auth: Session | null;
|
session: Session | null;
|
||||||
}) => {
|
}) => {
|
||||||
const session = opts.auth ?? (await auth());
|
const session = opts.session ?? (await auth());
|
||||||
const source = opts.headers?.get("x-trpc-source") ?? "unknown";
|
const source = opts.headers.get("x-trpc-source") ?? "unknown";
|
||||||
|
|
||||||
console.log(">>> tRPC Request from", source, "by", session?.user);
|
console.log(">>> tRPC Request from", source, "by", session?.user);
|
||||||
|
|
||||||
return createInnerTRPCContext({
|
return {
|
||||||
session,
|
session,
|
||||||
});
|
db,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,18 +49,21 @@ export const createTRPCContext = async (opts: {
|
|||||||
*/
|
*/
|
||||||
const t = initTRPC.context<typeof createTRPCContext>().create({
|
const t = initTRPC.context<typeof createTRPCContext>().create({
|
||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
errorFormatter({ shape, error }) {
|
errorFormatter: ({ shape, error }) => ({
|
||||||
return {
|
...shape,
|
||||||
...shape,
|
data: {
|
||||||
data: {
|
...shape.data,
|
||||||
...shape.data,
|
zodError: error.cause instanceof ZodError ? error.cause.flatten() : null,
|
||||||
zodError:
|
},
|
||||||
error.cause instanceof ZodError ? error.cause.flatten() : null,
|
}),
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a server-side caller
|
||||||
|
* @see https://trpc.io/docs/server/server-side-calls
|
||||||
|
*/
|
||||||
|
export const createCallerFactory = t.createCallerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 3. ROUTER & PROCEDURE (THE IMPORTANT BIT)
|
* 3. ROUTER & PROCEDURE (THE IMPORTANT BIT)
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -7,18 +7,10 @@ export const env = createEnv({
|
|||||||
process.env.NODE_ENV === "production"
|
process.env.NODE_ENV === "production"
|
||||||
? z.string().min(1)
|
? z.string().min(1)
|
||||||
: z.string().min(1).optional(),
|
: z.string().min(1).optional(),
|
||||||
AUTH_URL: z.preprocess(
|
|
||||||
// This makes Vercel deployments not fail if you don't set NEXTAUTH_URL
|
|
||||||
// Since NextAuth.js automatically uses the VERCEL_URL if present.
|
|
||||||
(str) => process.env.VERCEL_URL ?? str,
|
|
||||||
// VERCEL_URL doesn't include `https` so it cant be validated as a URL
|
|
||||||
process.env.VERCEL ? z.string() : z.string().url(),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
client: {},
|
client: {},
|
||||||
runtimeEnv: {
|
runtimeEnv: {
|
||||||
AUTH_SECRET: process.env.AUTH_SECRET,
|
AUTH_SECRET: process.env.AUTH_SECRET,
|
||||||
AUTH_URL: process.env.AUTH_URL,
|
|
||||||
},
|
},
|
||||||
skipValidation:
|
skipValidation:
|
||||||
Boolean(process.env.CI) || Boolean(process.env.SKIP_ENV_VALIDATION),
|
Boolean(process.env.CI) || Boolean(process.env.SKIP_ENV_VALIDATION),
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
"@t3-oss/env-nextjs": "^0.9.2",
|
"@t3-oss/env-nextjs": "^0.9.2",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"cookies": "^0.9.1",
|
"cookies": "^0.9.1",
|
||||||
"next": "^14.1.0",
|
"next": "^14.1.1-canary.58",
|
||||||
"next-auth": "5.0.0-beta.11",
|
"next-auth": "5.0.0-beta.11",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0"
|
"react-dom": "18.2.0"
|
||||||
|
|||||||
146
packages/spotlight/ReadMe.md
Normal file
146
packages/spotlight/ReadMe.md
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
# Spotlight
|
||||||
|
|
||||||
|
Spotlight is the search functionality of Homarr. It can be opened by pressing `Ctrl + K` or `Cmd + K` on Mac. It is a quick way to search for anything in Homarr.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### SpotlightActionData
|
||||||
|
|
||||||
|
The [SpotlightActionData](./src/type.ts) is the data structure that is used to define the actions that are shown in the spotlight.
|
||||||
|
|
||||||
|
#### Common properties
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ------------------------------ | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
|
||||||
|
| id | `string` | The id of the action. |
|
||||||
|
| title | `string \| (t: TranslationFunction) => string` | The title of the action. Either static or generated with translation function |
|
||||||
|
| description | `string \| (t: TranslationFunction) => string` | The description of the action. Either static or generated with translation function |
|
||||||
|
| icon | `string \| (props: TablerIconProps) => JSX.Element` | The icon of the action. Either a url to an image or a TablerIcon |
|
||||||
|
| group | `string` | The group of the action. By default the groups all, web and action exist. |
|
||||||
|
| ignoreSearchAndOnlyShowInGroup | `boolean` | If true, the action will only be shown in the group and not in the search results. |
|
||||||
|
| type | `'link' \| 'button'` | The type of the action. Either link or button |
|
||||||
|
|
||||||
|
#### Properties for links
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---- | -------- | ---------------------------------------------------------------------------------------------------------- |
|
||||||
|
| href | `string` | The url the link should navigate to. If %s is contained it will be replaced with the current search query. |
|
||||||
|
|
||||||
|
#### Properties for buttons
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ------- | -------------------------- | ----------------------------------------------------------------------------------------- |
|
||||||
|
| onClick | `() => MaybePromise<void>` | The function that should be called when the button is clicked. It can be async if needed. |
|
||||||
|
|
||||||
|
### useRegisterSpotlightActions
|
||||||
|
|
||||||
|
The [useRegisterSpotlightActions](./src/data-store.ts) hook is used to register actions to the spotlight. It takes an unique key and the array of [SpotlightActionData](#SpotlightActionData).
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
The following example shows how to use the `useRegisterSpotlightActions` hook to register an action to the spotlight.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useRegisterSpotlightActions } from "@homarr/spotlight";
|
||||||
|
|
||||||
|
const MyComponent = () => {
|
||||||
|
useRegisterSpotlightActions("my-component", [
|
||||||
|
{
|
||||||
|
id: "my-action",
|
||||||
|
title: "My Action",
|
||||||
|
description: "This is my action",
|
||||||
|
icon: "https://example.com/icon.png",
|
||||||
|
group: "web",
|
||||||
|
type: "link",
|
||||||
|
href: "https://example.com",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
return <div>My Component</div>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Using translation function
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useRegisterSpotlightActions } from "@homarr/spotlight";
|
||||||
|
|
||||||
|
const MyComponent = () => {
|
||||||
|
useRegisterSpotlightActions("my-component", [
|
||||||
|
{
|
||||||
|
id: "my-action",
|
||||||
|
title: (t) => t("some.path.to.translation.key"),
|
||||||
|
description: (t) => t("some.other.path.to.translation.key"),
|
||||||
|
icon: "https://example.com/icon.png",
|
||||||
|
group: "web",
|
||||||
|
type: "link",
|
||||||
|
href: "https://example.com",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
return <div>Component implementation</div>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Using TablerIcon
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { IconUserCog } from "tabler-react";
|
||||||
|
|
||||||
|
import { useRegisterSpotlightActions } from "@homarr/spotlight";
|
||||||
|
|
||||||
|
const UserMenu = () => {
|
||||||
|
useRegisterSpotlightActions("header-user-menu", [
|
||||||
|
{
|
||||||
|
id: "user-preferences",
|
||||||
|
title: (t) => t("user.preferences.title"),
|
||||||
|
description: (t) => t("user.preferences.description"),
|
||||||
|
icon: IconUserCog,
|
||||||
|
group: "action",
|
||||||
|
type: "link",
|
||||||
|
href: "/user/preferences",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
return <div>Component implementation</div>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Using dependency array
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { IconUserCog } from "tabler-react";
|
||||||
|
|
||||||
|
import { useRegisterSpotlightActions } from "@homarr/spotlight";
|
||||||
|
|
||||||
|
const ColorSchemeButton = () => {
|
||||||
|
const { colorScheme, toggleColorScheme } = useColorScheme();
|
||||||
|
|
||||||
|
useRegisterSpotlightActions(
|
||||||
|
"toggle-color-scheme",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: "toggle-color-scheme",
|
||||||
|
title: (t) => t("common.colorScheme.toggle.title"),
|
||||||
|
description: (t) =>
|
||||||
|
t(`common.colorScheme.toggle.${colorScheme}.description`),
|
||||||
|
icon: colorScheme === "light" ? IconSun : IconMoon,
|
||||||
|
group: "action",
|
||||||
|
type: "button",
|
||||||
|
onClick: toggleColorScheme,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[colorScheme],
|
||||||
|
);
|
||||||
|
|
||||||
|
return <div>Component implementation</div>;
|
||||||
|
};
|
||||||
|
```
|
||||||
@@ -1,2 +1 @@
|
|||||||
export * from "./src";
|
export * from "./src";
|
||||||
export { spotlight, Spotlight } from "@mantine/spotlight";
|
|
||||||
|
|||||||
@@ -34,6 +34,8 @@
|
|||||||
},
|
},
|
||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mantine/spotlight": "^7.5.3"
|
"@mantine/spotlight": "^7.5.3",
|
||||||
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
|
"@homarr/translation": "workspace:^0.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
54
packages/spotlight/src/chip-group.tsx
Normal file
54
packages/spotlight/src/chip-group.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
|
import { Chip } from "@homarr/ui";
|
||||||
|
|
||||||
|
import {
|
||||||
|
selectNextAction,
|
||||||
|
selectPreviousAction,
|
||||||
|
spotlightStore,
|
||||||
|
triggerSelectedAction,
|
||||||
|
} from "./spotlight-store";
|
||||||
|
import type { SpotlightActionGroup } from "./type";
|
||||||
|
|
||||||
|
const disableArrowUpAndDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.key === "ArrowDown") {
|
||||||
|
selectNextAction(spotlightStore);
|
||||||
|
e.preventDefault();
|
||||||
|
} else if (e.key === "ArrowUp") {
|
||||||
|
selectPreviousAction(spotlightStore);
|
||||||
|
e.preventDefault();
|
||||||
|
} else if (e.key === "Enter") {
|
||||||
|
triggerSelectedAction(spotlightStore);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const focusActiveByDefault = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||||
|
const relatedTarget = e.relatedTarget;
|
||||||
|
|
||||||
|
const isPreviousTargetRadio =
|
||||||
|
relatedTarget && "type" in relatedTarget && relatedTarget.type === "radio";
|
||||||
|
if (isPreviousTargetRadio) return;
|
||||||
|
|
||||||
|
const group = e.currentTarget.parentElement?.parentElement;
|
||||||
|
if (!group) return;
|
||||||
|
const label = group.querySelector<HTMLLabelElement>("label[data-checked]");
|
||||||
|
if (!label) return;
|
||||||
|
label.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
group: SpotlightActionGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GroupChip = ({ group }: Props) => {
|
||||||
|
const t = useScopedI18n("common.search.group");
|
||||||
|
return (
|
||||||
|
<Chip
|
||||||
|
key={group}
|
||||||
|
value={group}
|
||||||
|
onFocus={focusActiveByDefault}
|
||||||
|
onKeyDown={disableArrowUpAndDown}
|
||||||
|
>
|
||||||
|
{t(group)}
|
||||||
|
</Chip>
|
||||||
|
);
|
||||||
|
};
|
||||||
7
packages/spotlight/src/component.module.css
Normal file
7
packages/spotlight/src/component.module.css
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.spotlightAction:hover {
|
||||||
|
background-color: alpha(var(--mantine-primary-color-filled), 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spotlightAction[data-selected="true"] {
|
||||||
|
background-color: alpha(var(--mantine-primary-color-filled), 0.3);
|
||||||
|
}
|
||||||
154
packages/spotlight/src/component.tsx
Normal file
154
packages/spotlight/src/component.tsx
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useCallback, useState } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import {
|
||||||
|
Spotlight as MantineSpotlight,
|
||||||
|
SpotlightAction,
|
||||||
|
} from "@mantine/spotlight";
|
||||||
|
import { useAtomValue } from "jotai";
|
||||||
|
|
||||||
|
import type { TranslationFunction } from "@homarr/translation";
|
||||||
|
import { useI18n } from "@homarr/translation/client";
|
||||||
|
import {
|
||||||
|
Center,
|
||||||
|
Chip,
|
||||||
|
Divider,
|
||||||
|
Flex,
|
||||||
|
Group,
|
||||||
|
IconSearch,
|
||||||
|
Text,
|
||||||
|
} from "@homarr/ui";
|
||||||
|
|
||||||
|
import { GroupChip } from "./chip-group";
|
||||||
|
import classes from "./component.module.css";
|
||||||
|
import { actionsAtomRead, groupsAtomRead } from "./data-store";
|
||||||
|
import { setSelectedAction, spotlightStore } from "./spotlight-store";
|
||||||
|
import type { SpotlightActionData } from "./type";
|
||||||
|
|
||||||
|
export const Spotlight = () => {
|
||||||
|
const [query, setQuery] = useState("");
|
||||||
|
const [group, setGroup] = useState("all");
|
||||||
|
const groups = useAtomValue(groupsAtomRead);
|
||||||
|
const actions = useAtomValue(actionsAtomRead);
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
|
const preparedActions = actions.map((action) => prepareAction(action, t));
|
||||||
|
const items = preparedActions
|
||||||
|
.filter(
|
||||||
|
(item) =>
|
||||||
|
(item.ignoreSearchAndOnlyShowInGroup
|
||||||
|
? item.group === group
|
||||||
|
: item.title.toLowerCase().includes(query.toLowerCase().trim())) &&
|
||||||
|
(group === "all" || item.group === group),
|
||||||
|
)
|
||||||
|
.map((item) => {
|
||||||
|
const renderRoot =
|
||||||
|
item.type === "link"
|
||||||
|
? (props: Record<string, unknown>) => (
|
||||||
|
<Link href={prepareHref(item.href, query)} {...props} />
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SpotlightAction
|
||||||
|
key={item.id}
|
||||||
|
renderRoot={renderRoot}
|
||||||
|
onClick={item.type === "button" ? item.onClick : undefined}
|
||||||
|
className={classes.spotlightAction}
|
||||||
|
>
|
||||||
|
<Group wrap="nowrap" w="100%">
|
||||||
|
{item.icon && (
|
||||||
|
<Center w={50} h={50}>
|
||||||
|
{typeof item.icon !== "string" && <item.icon size={24} />}
|
||||||
|
{typeof item.icon === "string" && (
|
||||||
|
<img
|
||||||
|
src={item.icon}
|
||||||
|
alt={item.title}
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Flex direction="column">
|
||||||
|
<Text>{item.title}</Text>
|
||||||
|
|
||||||
|
{item.description && (
|
||||||
|
<Text opacity={0.6} size="xs">
|
||||||
|
{item.description}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Group>
|
||||||
|
</SpotlightAction>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const onGroupChange = useCallback(
|
||||||
|
(group: string) => {
|
||||||
|
setSelectedAction(-1, spotlightStore);
|
||||||
|
setGroup(group);
|
||||||
|
},
|
||||||
|
[setGroup, setSelectedAction],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MantineSpotlight.Root
|
||||||
|
query={query}
|
||||||
|
onQueryChange={setQuery}
|
||||||
|
store={spotlightStore}
|
||||||
|
>
|
||||||
|
<MantineSpotlight.Search
|
||||||
|
placeholder={t("common.search.placeholder")}
|
||||||
|
leftSection={<IconSearch stroke={1.5} />}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<Group wrap="nowrap" p="sm">
|
||||||
|
<Chip.Group multiple={false} value={group} onChange={onGroupChange}>
|
||||||
|
<Group justify="start">
|
||||||
|
{groups.map((group) => (
|
||||||
|
<GroupChip key={group} group={group} />
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
</Chip.Group>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<MantineSpotlight.ActionsList>
|
||||||
|
{items.length > 0 ? (
|
||||||
|
items
|
||||||
|
) : (
|
||||||
|
<MantineSpotlight.Empty>
|
||||||
|
{t("common.search.nothingFound")}
|
||||||
|
</MantineSpotlight.Empty>
|
||||||
|
)}
|
||||||
|
</MantineSpotlight.ActionsList>
|
||||||
|
</MantineSpotlight.Root>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const prepareHref = (href: string, query: string) => {
|
||||||
|
return href.replace("%s", query);
|
||||||
|
};
|
||||||
|
|
||||||
|
const translateIfNecessary = (
|
||||||
|
value: string | ((t: TranslationFunction) => string),
|
||||||
|
t: TranslationFunction,
|
||||||
|
) => {
|
||||||
|
if (typeof value === "function") {
|
||||||
|
return value(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const prepareAction = (
|
||||||
|
action: SpotlightActionData,
|
||||||
|
t: TranslationFunction,
|
||||||
|
) => ({
|
||||||
|
...action,
|
||||||
|
title: translateIfNecessary(action.title, t),
|
||||||
|
description: translateIfNecessary(action.description, t),
|
||||||
|
});
|
||||||
72
packages/spotlight/src/data-store.ts
Normal file
72
packages/spotlight/src/data-store.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { atom, useSetAtom } from "jotai";
|
||||||
|
import useDeepCompareEffect from "use-deep-compare-effect";
|
||||||
|
|
||||||
|
import type { SpotlightActionData, SpotlightActionGroup } from "./type";
|
||||||
|
|
||||||
|
const defaultGroups = ["all", "web", "action"] as const;
|
||||||
|
const reversedDefaultGroups = [...defaultGroups].reverse();
|
||||||
|
const actionsAtom = atom<Record<string, readonly SpotlightActionData[]>>({});
|
||||||
|
export const actionsAtomRead = atom((get) =>
|
||||||
|
Object.values(get(actionsAtom)).flatMap((item) => item),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const groupsAtomRead = atom((get) =>
|
||||||
|
Array.from(
|
||||||
|
new Set(
|
||||||
|
get(actionsAtomRead)
|
||||||
|
.map((item) => item.group as SpotlightActionGroup) // Allow "all" group to be included in the list of groups
|
||||||
|
.concat(...defaultGroups),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.sort((groupA, groupB) => {
|
||||||
|
const groupAIndex = reversedDefaultGroups.indexOf(groupA);
|
||||||
|
const groupBIndex = reversedDefaultGroups.indexOf(groupB);
|
||||||
|
|
||||||
|
// if both groups are not in the default groups, sort them by name (here reversed because we reverse the array afterwards)
|
||||||
|
if (groupAIndex === -1 && groupBIndex === -1) {
|
||||||
|
return groupB.localeCompare(groupA);
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupAIndex - groupBIndex;
|
||||||
|
})
|
||||||
|
.reverse(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const registrations = new Map<string, number>();
|
||||||
|
|
||||||
|
export const useRegisterSpotlightActions = (
|
||||||
|
key: string,
|
||||||
|
actions: SpotlightActionData[],
|
||||||
|
dependencies: readonly unknown[] = [],
|
||||||
|
) => {
|
||||||
|
const setActions = useSetAtom(actionsAtom);
|
||||||
|
|
||||||
|
// Use deep compare effect if there are dependencies for the actions, this supports deep compare of the action dependencies
|
||||||
|
const useSpecificEffect =
|
||||||
|
dependencies.length >= 1 ? useDeepCompareEffect : useEffect;
|
||||||
|
|
||||||
|
useSpecificEffect(() => {
|
||||||
|
if (!registrations.has(key) || dependencies.length >= 1) {
|
||||||
|
setActions((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[key]: actions,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
registrations.set(key, (registrations.get(key) ?? 0) + 1);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (registrations.get(key) === 1) {
|
||||||
|
setActions((prev) => {
|
||||||
|
const { [key]: _, ...rest } = prev;
|
||||||
|
return rest;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registrations.set(key, (registrations.get(key) ?? 0) - 1);
|
||||||
|
if (registrations.get(key) === 0) {
|
||||||
|
registrations.delete(key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [key, dependencies.length >= 1 ? dependencies : undefined]);
|
||||||
|
};
|
||||||
@@ -1 +1,8 @@
|
|||||||
export const name = "spotlight";
|
"use client";
|
||||||
|
|
||||||
|
import { spotlightActions } from "./spotlight-store";
|
||||||
|
|
||||||
|
export { Spotlight } from "./component";
|
||||||
|
|
||||||
|
const openSpotlight = spotlightActions.open;
|
||||||
|
export { openSpotlight };
|
||||||
|
|||||||
45
packages/spotlight/src/spotlight-store.ts
Normal file
45
packages/spotlight/src/spotlight-store.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { clamp } from "@mantine/hooks";
|
||||||
|
import type { SpotlightStore } from "@mantine/spotlight";
|
||||||
|
import { createSpotlight } from "@mantine/spotlight";
|
||||||
|
|
||||||
|
export const [spotlightStore, spotlightActions] = createSpotlight();
|
||||||
|
|
||||||
|
export const setSelectedAction = (index: number, store: SpotlightStore) => {
|
||||||
|
store.updateState((state) => ({ ...state, selected: index }));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const selectAction = (index: number, store: SpotlightStore): number => {
|
||||||
|
const state = store.getState();
|
||||||
|
const actionsList = document.getElementById(state.listId);
|
||||||
|
const selected =
|
||||||
|
actionsList?.querySelector<HTMLButtonElement>("[data-selected]");
|
||||||
|
const actions =
|
||||||
|
actionsList?.querySelectorAll<HTMLButtonElement>("[data-action]") ?? [];
|
||||||
|
const nextIndex =
|
||||||
|
index === -1 ? actions.length - 1 : index === actions.length ? 0 : index;
|
||||||
|
|
||||||
|
const selectedIndex = clamp(nextIndex, 0, actions.length - 1);
|
||||||
|
selected?.removeAttribute("data-selected");
|
||||||
|
actions[selectedIndex]?.scrollIntoView({ block: "nearest" });
|
||||||
|
actions[selectedIndex]?.setAttribute("data-selected", "true");
|
||||||
|
setSelectedAction(selectedIndex, store);
|
||||||
|
|
||||||
|
return selectedIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const selectNextAction = (store: SpotlightStore) => {
|
||||||
|
return selectAction(store.getState().selected + 1, store);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const selectPreviousAction = (store: SpotlightStore) => {
|
||||||
|
return selectAction(store.getState().selected - 1, store);
|
||||||
|
};
|
||||||
|
export const triggerSelectedAction = (store: SpotlightStore) => {
|
||||||
|
const state = store.getState();
|
||||||
|
const selected = document.querySelector<HTMLButtonElement>(
|
||||||
|
`#${state.listId} [data-selected]`,
|
||||||
|
);
|
||||||
|
selected?.click();
|
||||||
|
};
|
||||||
31
packages/spotlight/src/type.ts
Normal file
31
packages/spotlight/src/type.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import type {
|
||||||
|
TranslationFunction,
|
||||||
|
TranslationObject,
|
||||||
|
} from "@homarr/translation";
|
||||||
|
import type { TablerIconsProps } from "@homarr/ui";
|
||||||
|
|
||||||
|
export type SpotlightActionGroup =
|
||||||
|
keyof TranslationObject["common"]["search"]["group"];
|
||||||
|
|
||||||
|
interface BaseSpotlightAction {
|
||||||
|
id: string;
|
||||||
|
title: string | ((t: TranslationFunction) => string);
|
||||||
|
description: string | ((t: TranslationFunction) => string);
|
||||||
|
group: Exclude<SpotlightActionGroup, "all">; // actions can not be assigned to the "all" group
|
||||||
|
icon: ((props: TablerIconsProps) => JSX.Element) | string;
|
||||||
|
ignoreSearchAndOnlyShowInGroup?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SpotlightActionLink extends BaseSpotlightAction {
|
||||||
|
type: "link";
|
||||||
|
href: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type MaybePromise<T> = T | Promise<T>;
|
||||||
|
|
||||||
|
interface SpotlightActionButton extends BaseSpotlightAction {
|
||||||
|
type: "button";
|
||||||
|
onClick: () => MaybePromise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SpotlightActionData = SpotlightActionLink | SpotlightActionButton;
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
export * from "./type";
|
||||||
|
|
||||||
export const supportedLanguages = ["en", "de"] as const;
|
export const supportedLanguages = ["en", "de"] as const;
|
||||||
export type SupportedLanguage = (typeof supportedLanguages)[number];
|
export type SupportedLanguage = (typeof supportedLanguages)[number];
|
||||||
|
|
||||||
|
|||||||
@@ -154,6 +154,11 @@ export default {
|
|||||||
search: {
|
search: {
|
||||||
placeholder: "Search for anything...",
|
placeholder: "Search for anything...",
|
||||||
nothingFound: "Nothing found",
|
nothingFound: "Nothing found",
|
||||||
|
group: {
|
||||||
|
all: "All",
|
||||||
|
web: "Web",
|
||||||
|
action: "Actions",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
userAvatar: {
|
userAvatar: {
|
||||||
menu: {
|
menu: {
|
||||||
|
|||||||
5
packages/translation/src/type.ts
Normal file
5
packages/translation/src/type.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import type { useI18n } from "./client";
|
||||||
|
import type enTranslation from "./lang/en";
|
||||||
|
|
||||||
|
export type TranslationFunction = ReturnType<typeof useI18n>;
|
||||||
|
export type TranslationObject = typeof enTranslation;
|
||||||
@@ -55,7 +55,6 @@ export const WidgetEditModal: ManagedModal<ModalProps<WidgetKind>> = ({
|
|||||||
)}
|
)}
|
||||||
{Object.entries(definition.options).map(
|
{Object.entries(definition.options).map(
|
||||||
([key, value]: [string, OptionsBuilderResult[string]]) => {
|
([key, value]: [string, OptionsBuilderResult[string]]) => {
|
||||||
console.log(value);
|
|
||||||
const Input = getInputForType(value.type);
|
const Input = getInputForType(value.type);
|
||||||
|
|
||||||
if (!Input || value.shouldHide?.(form.values.options as never)) {
|
if (!Input || value.shouldHide?.(form.values.options as never)) {
|
||||||
|
|||||||
187
pnpm-lock.yaml
generated
187
pnpm-lock.yaml
generated
@@ -106,14 +106,14 @@ importers:
|
|||||||
specifier: ^0.9.2
|
specifier: ^0.9.2
|
||||||
version: 0.9.2(typescript@5.3.3)(zod@3.22.4)
|
version: 0.9.2(typescript@5.3.3)(zod@3.22.4)
|
||||||
'@tanstack/react-query':
|
'@tanstack/react-query':
|
||||||
specifier: ^5.20.5
|
specifier: ^5.21.2
|
||||||
version: 5.20.5(react@18.2.0)
|
version: 5.21.2(react@18.2.0)
|
||||||
'@tanstack/react-query-devtools':
|
'@tanstack/react-query-devtools':
|
||||||
specifier: ^5.21.0
|
specifier: ^5.21.3
|
||||||
version: 5.21.0(@tanstack/react-query@5.20.5)(react@18.2.0)
|
version: 5.21.3(@tanstack/react-query@5.21.2)(react@18.2.0)
|
||||||
'@tanstack/react-query-next-experimental':
|
'@tanstack/react-query-next-experimental':
|
||||||
specifier: 5.20.5
|
specifier: 5.21.2
|
||||||
version: 5.20.5(@tanstack/react-query@5.20.5)(next@14.1.0)(react@18.2.0)
|
version: 5.21.2(@tanstack/react-query@5.21.2)(next@14.1.1-canary.58)(react@18.2.0)
|
||||||
'@tiptap/extension-link':
|
'@tiptap/extension-link':
|
||||||
specifier: ^2.2.3
|
specifier: ^2.2.3
|
||||||
version: 2.2.3(@tiptap/core@2.2.3)(@tiptap/pm@2.2.3)
|
version: 2.2.3(@tiptap/core@2.2.3)(@tiptap/pm@2.2.3)
|
||||||
@@ -128,10 +128,10 @@ importers:
|
|||||||
version: 11.0.0-next-beta.289(@trpc/server@11.0.0-next-beta.289)
|
version: 11.0.0-next-beta.289(@trpc/server@11.0.0-next-beta.289)
|
||||||
'@trpc/next':
|
'@trpc/next':
|
||||||
specifier: next
|
specifier: next
|
||||||
version: 11.0.0-next-beta.289(@tanstack/react-query@5.20.5)(@trpc/client@11.0.0-next-beta.289)(@trpc/react-query@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(next@14.1.0)(react-dom@18.2.0)(react@18.2.0)
|
version: 11.0.0-next-beta.289(@tanstack/react-query@5.21.2)(@trpc/client@11.0.0-next-beta.289)(@trpc/react-query@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(next@14.1.1-canary.58)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@trpc/react-query':
|
'@trpc/react-query':
|
||||||
specifier: next
|
specifier: next
|
||||||
version: 11.0.0-next-beta.289(@tanstack/react-query@5.20.5)(@trpc/client@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(react-dom@18.2.0)(react@18.2.0)
|
version: 11.0.0-next-beta.289(@tanstack/react-query@5.21.2)(@trpc/client@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@trpc/server':
|
'@trpc/server':
|
||||||
specifier: next
|
specifier: next
|
||||||
version: 11.0.0-next-beta.289
|
version: 11.0.0-next-beta.289
|
||||||
@@ -140,13 +140,13 @@ importers:
|
|||||||
version: 1.11.10
|
version: 1.11.10
|
||||||
jotai:
|
jotai:
|
||||||
specifier: ^2.6.4
|
specifier: ^2.6.4
|
||||||
version: 2.6.4(@types/react@18.2.55)(react@18.2.0)
|
version: 2.6.4(@types/react@18.2.56)(react@18.2.0)
|
||||||
mantine-modal-manager:
|
mantine-modal-manager:
|
||||||
specifier: ^7.5.2
|
specifier: ^7.5.2
|
||||||
version: 7.5.2(@mantine/hooks@7.5.3)(react-dom@18.2.0)(react@18.2.0)
|
version: 7.5.2(@mantine/hooks@7.5.3)(react-dom@18.2.0)(react@18.2.0)
|
||||||
next:
|
next:
|
||||||
specifier: ^14.1.0
|
specifier: ^14.1.1-canary.58
|
||||||
version: 14.1.0(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0)
|
version: 14.1.1-canary.58(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0)
|
||||||
postcss-preset-mantine:
|
postcss-preset-mantine:
|
||||||
specifier: ^1.13.0
|
specifier: ^1.13.0
|
||||||
version: 1.13.0(postcss@8.4.35)
|
version: 1.13.0(postcss@8.4.35)
|
||||||
@@ -162,6 +162,9 @@ importers:
|
|||||||
superjson:
|
superjson:
|
||||||
specifier: 2.2.1
|
specifier: 2.2.1
|
||||||
version: 2.2.1
|
version: 2.2.1
|
||||||
|
use-deep-compare-effect:
|
||||||
|
specifier: ^1.8.1
|
||||||
|
version: 1.8.1(react@18.2.0)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@homarr/eslint-config':
|
'@homarr/eslint-config':
|
||||||
specifier: workspace:^0.2.0
|
specifier: workspace:^0.2.0
|
||||||
@@ -176,8 +179,8 @@ importers:
|
|||||||
specifier: ^20.11.19
|
specifier: ^20.11.19
|
||||||
version: 20.11.19
|
version: 20.11.19
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: ^18.2.55
|
specifier: ^18.2.56
|
||||||
version: 18.2.55
|
version: 18.2.56
|
||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
specifier: ^18.2.19
|
specifier: ^18.2.19
|
||||||
version: 18.2.19
|
version: 18.2.19
|
||||||
@@ -258,11 +261,11 @@ importers:
|
|||||||
specifier: ^0.9.1
|
specifier: ^0.9.1
|
||||||
version: 0.9.1
|
version: 0.9.1
|
||||||
next:
|
next:
|
||||||
specifier: ^14.1.0
|
specifier: ^14.1.1-canary.58
|
||||||
version: 14.1.0(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0)
|
version: 14.1.1-canary.58(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0)
|
||||||
next-auth:
|
next-auth:
|
||||||
specifier: 5.0.0-beta.11
|
specifier: 5.0.0-beta.11
|
||||||
version: 5.0.0-beta.11(next@14.1.0)(react@18.2.0)
|
version: 5.0.0-beta.11(next@14.1.1-canary.58)(react@18.2.0)
|
||||||
react:
|
react:
|
||||||
specifier: 18.2.0
|
specifier: 18.2.0
|
||||||
version: 18.2.0
|
version: 18.2.0
|
||||||
@@ -433,6 +436,12 @@ importers:
|
|||||||
|
|
||||||
packages/spotlight:
|
packages/spotlight:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@homarr/translation':
|
||||||
|
specifier: workspace:^0.1.0
|
||||||
|
version: link:../translation
|
||||||
|
'@homarr/ui':
|
||||||
|
specifier: workspace:^0.1.0
|
||||||
|
version: link:../ui
|
||||||
'@mantine/spotlight':
|
'@mantine/spotlight':
|
||||||
specifier: ^7.5.3
|
specifier: ^7.5.3
|
||||||
version: 7.5.3(@mantine/core@7.5.3)(@mantine/hooks@7.5.3)(react-dom@18.2.0)(react@18.2.0)
|
version: 7.5.3(@mantine/core@7.5.3)(@mantine/hooks@7.5.3)(react-dom@18.2.0)(react@18.2.0)
|
||||||
@@ -1659,8 +1668,8 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@next/env@14.1.0:
|
/@next/env@14.1.1-canary.58:
|
||||||
resolution: {integrity: sha512-Py8zIo+02ht82brwwhTg36iogzFqGLPXlRGKQw5s+qP/kMNc4MAyDeEwBKDijk6zTIbegEgu8Qy7C1LboslQAw==}
|
resolution: {integrity: sha512-xMoPPiWVEIyIPoWaLhswlMq2Tnfkcv+XRmUAEhKkJgWdHIMFh45tiniWrZQLfrn4DAnF8gEAoejJaniNShFm7w==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@next/eslint-plugin-next@14.1.0:
|
/@next/eslint-plugin-next@14.1.0:
|
||||||
@@ -1669,8 +1678,8 @@ packages:
|
|||||||
glob: 10.3.10
|
glob: 10.3.10
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@next/swc-darwin-arm64@14.1.0:
|
/@next/swc-darwin-arm64@14.1.1-canary.58:
|
||||||
resolution: {integrity: sha512-nUDn7TOGcIeyQni6lZHfzNoo9S0euXnu0jhsbMOmMJUBfgsnESdjN97kM7cBqQxZa8L/bM9om/S5/1dzCrW6wQ==}
|
resolution: {integrity: sha512-exDpEe9ptK0NJEEyA+sThnR/6Oif/XaPK1y10fkcTNoQJkUCENL46nR1bCEYDAQrS5w03C1BHs3LSIOuoQcIag==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
@@ -1678,8 +1687,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-darwin-x64@14.1.0:
|
/@next/swc-darwin-x64@14.1.1-canary.58:
|
||||||
resolution: {integrity: sha512-1jgudN5haWxiAl3O1ljUS2GfupPmcftu2RYJqZiMJmmbBT5M1XDffjUtRUzP4W3cBHsrvkfOFdQ71hAreNQP6g==}
|
resolution: {integrity: sha512-Q+38djBg+yaZjB8R6kst8kqHLIpTIM2RzuIvFGOflb8WaVI1ApXERTJKd345BOp42s0rIJ1UqynRplNCqYebeA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
@@ -1687,8 +1696,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-arm64-gnu@14.1.0:
|
/@next/swc-linux-arm64-gnu@14.1.1-canary.58:
|
||||||
resolution: {integrity: sha512-RHo7Tcj+jllXUbK7xk2NyIDod3YcCPDZxj1WLIYxd709BQ7WuRYl3OWUNG+WUfqeQBds6kvZYlc42NJJTNi4tQ==}
|
resolution: {integrity: sha512-wv2457qPqh7ENqQ3t8sCWCsNStEW/o8qejZ6Ywdwl3eKP1jnFNtn4OWfOuP2FUl1XmVEzbABozuOyiCGAtD63Q==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
@@ -1696,8 +1705,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-arm64-musl@14.1.0:
|
/@next/swc-linux-arm64-musl@14.1.1-canary.58:
|
||||||
resolution: {integrity: sha512-v6kP8sHYxjO8RwHmWMJSq7VZP2nYCkRVQ0qolh2l6xroe9QjbgV8siTbduED4u0hlk0+tjS6/Tuy4n5XCp+l6g==}
|
resolution: {integrity: sha512-xvfU/+mZfLpW/tRy743JtkNPlNI2t1dSkIzvDHHn/V28k1c+4YmsNxKMZRqqcxi73hwnuWYgor/96Sm4r9pmQQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
@@ -1705,8 +1714,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-x64-gnu@14.1.0:
|
/@next/swc-linux-x64-gnu@14.1.1-canary.58:
|
||||||
resolution: {integrity: sha512-zJ2pnoFYB1F4vmEVlb/eSe+VH679zT1VdXlZKX+pE66grOgjmKJHKacf82g/sWE4MQ4Rk2FMBCRnX+l6/TVYzQ==}
|
resolution: {integrity: sha512-OgayNv+ChIjsPYMkBit2LTI0tMJ72or4w5DfQe2i1M2Ccy09p/LSRCiOptkV3XKyO+Tz8+fAqN+PdLRxfxQiuQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
@@ -1714,8 +1723,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-x64-musl@14.1.0:
|
/@next/swc-linux-x64-musl@14.1.1-canary.58:
|
||||||
resolution: {integrity: sha512-rbaIYFt2X9YZBSbH/CwGAjbBG2/MrACCVu2X0+kSykHzHnYH5FjHxwXLkcoJ10cX0aWCEynpu+rP76x0914atg==}
|
resolution: {integrity: sha512-6epwXWxSUyNKzOt0FSRW0QioPaWA9xlu8LM5N0c9eg8vaI3FvZmQegtHIzuzNc7RyWE1a5ZY4uDBVA1JaUJRow==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
@@ -1723,8 +1732,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-arm64-msvc@14.1.0:
|
/@next/swc-win32-arm64-msvc@14.1.1-canary.58:
|
||||||
resolution: {integrity: sha512-o1N5TsYc8f/HpGt39OUQpQ9AKIGApd3QLueu7hXk//2xq5Z9OxmV6sQfNp8C7qYmiOlHYODOGqNNa0e9jvchGQ==}
|
resolution: {integrity: sha512-Qa79WuVRcrkmbxzTXr/tmWvFHVMjUu9KpxNrKn+HO/DmrVEnR4NxmuzOOxc6QkCb+Rb86rfV7tttPQqw7YigBA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
@@ -1732,8 +1741,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-ia32-msvc@14.1.0:
|
/@next/swc-win32-ia32-msvc@14.1.1-canary.58:
|
||||||
resolution: {integrity: sha512-XXIuB1DBRCFwNO6EEzCTMHT5pauwaSj4SWs7CYnME57eaReAKBXCnkUE80p/pAZcewm7hs+vGvNqDPacEXHVkw==}
|
resolution: {integrity: sha512-fuqdt6XKc4kvUpgOMh/LL2eqJ/eltxknClpaAg9Nt0dGTQIF0rjKEVm8NRwv832URZKKBbz9AtBp5TEmOquG9g==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [ia32]
|
cpu: [ia32]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
@@ -1741,8 +1750,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-x64-msvc@14.1.0:
|
/@next/swc-win32-x64-msvc@14.1.1-canary.58:
|
||||||
resolution: {integrity: sha512-9WEbVRRAqJ3YFVqEZIxUqkiO8l1nool1LmNxygr5HWF8AcSYsEpneUDhmjUVJEzO2A04+oPtZdombzzPPkTtgg==}
|
resolution: {integrity: sha512-P/DkKFiZo1mILb2UGt1YiurmuKo9LLvgC0l77YQ5g2b8jrTbYS9JeJJcdhLXnsHPPQrR/QgnEDJ2zWRtIhUJZA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
@@ -1934,9 +1943,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@swc/helpers@0.5.2:
|
/@swc/counter@0.1.3:
|
||||||
resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==}
|
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@swc/helpers@0.5.5:
|
||||||
|
resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@swc/counter': 0.1.3
|
||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@@ -1981,43 +1995,43 @@ packages:
|
|||||||
resolution: {integrity: sha512-4w5evLh+7FUUiA1GucvGj2ReX2TvOjEr4ejXdwL/bsjoSkof6r1gQmzqI+VHrE2CpJpB3al7bCTulOkFa/RcyA==}
|
resolution: {integrity: sha512-4w5evLh+7FUUiA1GucvGj2ReX2TvOjEr4ejXdwL/bsjoSkof6r1gQmzqI+VHrE2CpJpB3al7bCTulOkFa/RcyA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@tanstack/query-core@5.20.5:
|
/@tanstack/query-core@5.21.2:
|
||||||
resolution: {integrity: sha512-T1W28gGgWn0A++tH3lxj3ZuUVZZorsiKcv+R50RwmPYz62YoDEkG4/aXHZELGkRp4DfrW07dyq2K5dvJ4Wl1aA==}
|
resolution: {integrity: sha512-jg7OcDG44oLT3uuGQQ9BM65ZBIdAq9xNZXPEk7Gr6w1oM1wo2/95H3dPDjLVs0yZKwrmE/ORUOC2Pyi1sp2fDA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@tanstack/query-devtools@5.21.0:
|
/@tanstack/query-devtools@5.21.3:
|
||||||
resolution: {integrity: sha512-hbfuh9xredeehLhlJY38sey7Ezlr3KduDHRkrnvbwuTQCgkvixFdcJGMpUx0khh74q3Ay8QGa2uO+fV/kSNlww==}
|
resolution: {integrity: sha512-tRJ3uYpF8mLqb+Na25DqPsqdsK38Ff1HIse3znQIEoGMzf7rJKTwV9HCn/9cPQTyTcAuSUZOajyCVz7hnbdCdQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@tanstack/react-query-devtools@5.21.0(@tanstack/react-query@5.20.5)(react@18.2.0):
|
/@tanstack/react-query-devtools@5.21.3(@tanstack/react-query@5.21.2)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-79Rq5gtf9iuOANtrAIvSukC2ZT5d8iPXrJtCnkpJWKM2i+pIl6aBJR56sLJiFOvtlrJ1Cy8dkWZzWSi1s4tBbg==}
|
resolution: {integrity: sha512-xU6nSshTneHppN9xQnjLgIAy36GHsQsHp/aCa515dRzcgEqC99Ix4TdwbduPSRTk5ZATsUEO0kKMZzSbubO0Pg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tanstack/react-query': ^5.20.5
|
'@tanstack/react-query': ^5.21.2
|
||||||
react: ^18.0.0
|
react: ^18.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tanstack/query-devtools': 5.21.0
|
'@tanstack/query-devtools': 5.21.3
|
||||||
'@tanstack/react-query': 5.20.5(react@18.2.0)
|
'@tanstack/react-query': 5.21.2(react@18.2.0)
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@tanstack/react-query-next-experimental@5.20.5(@tanstack/react-query@5.20.5)(next@14.1.0)(react@18.2.0):
|
/@tanstack/react-query-next-experimental@5.21.2(@tanstack/react-query@5.21.2)(next@14.1.1-canary.58)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-P4r357MckowLGUAeQJ9UWTBK4i/JS/G4alBuXkNNyDp8md/pzk/VXG4y+c6/kJWOoi/Qtawz122l4oMJFp3MHA==}
|
resolution: {integrity: sha512-IKx6MvXxUWe2uAONZJZAsYBGhDXNpVoN5B81+JsS7AdOADO4+EiASk+3VPPg8vtkdYioiqbJ6sNp9zRn7pOGTA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tanstack/react-query': ^5.20.5
|
'@tanstack/react-query': ^5.21.2
|
||||||
next: ^13 || ^14
|
next: ^13 || ^14
|
||||||
react: ^18.0.0
|
react: ^18.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tanstack/react-query': 5.20.5(react@18.2.0)
|
'@tanstack/react-query': 5.21.2(react@18.2.0)
|
||||||
next: 14.1.0(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0)
|
next: 14.1.1-canary.58(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0)
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@tanstack/react-query@5.20.5(react@18.2.0):
|
/@tanstack/react-query@5.21.2(react@18.2.0):
|
||||||
resolution: {integrity: sha512-6MHwJ8G9cnOC/XKrwt56QMc91vN7hLlAQNUA0ubP7h9Jj3a/CmkUwT6ALdFbnVP+PsYdhW3WONa8WQ4VcTaSLQ==}
|
resolution: {integrity: sha512-/Vv1qTumNDDVA5EYk40kivHZ2kICs1w38GBLRvV6A/lrixUJR5bfqZMKqHA1S6ND3gR9hvSyAAYejBbjLrQnSA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^18.0.0
|
react: ^18.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tanstack/query-core': 5.20.5
|
'@tanstack/query-core': 5.21.2
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@@ -2314,7 +2328,7 @@ packages:
|
|||||||
'@trpc/server': 11.0.0-next-beta.289
|
'@trpc/server': 11.0.0-next-beta.289
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@trpc/next@11.0.0-next-beta.289(@tanstack/react-query@5.20.5)(@trpc/client@11.0.0-next-beta.289)(@trpc/react-query@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(next@14.1.0)(react-dom@18.2.0)(react@18.2.0):
|
/@trpc/next@11.0.0-next-beta.289(@tanstack/react-query@5.21.2)(@trpc/client@11.0.0-next-beta.289)(@trpc/react-query@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(next@14.1.1-canary.58)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-AKCrcbtHh/zFrld6lMG0RC37d/aac4ZisLDjJcViMnEmJXCo0J5nhoZa6f+G9N683NdMWZVmY2rmJidw9IX3QQ==}
|
resolution: {integrity: sha512-AKCrcbtHh/zFrld6lMG0RC37d/aac4ZisLDjJcViMnEmJXCo0J5nhoZa6f+G9N683NdMWZVmY2rmJidw9IX3QQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tanstack/react-query': ^5.0.0
|
'@tanstack/react-query': ^5.0.0
|
||||||
@@ -2330,16 +2344,16 @@ packages:
|
|||||||
'@trpc/react-query':
|
'@trpc/react-query':
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tanstack/react-query': 5.20.5(react@18.2.0)
|
'@tanstack/react-query': 5.21.2(react@18.2.0)
|
||||||
'@trpc/client': 11.0.0-next-beta.289(@trpc/server@11.0.0-next-beta.289)
|
'@trpc/client': 11.0.0-next-beta.289(@trpc/server@11.0.0-next-beta.289)
|
||||||
'@trpc/react-query': 11.0.0-next-beta.289(@tanstack/react-query@5.20.5)(@trpc/client@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(react-dom@18.2.0)(react@18.2.0)
|
'@trpc/react-query': 11.0.0-next-beta.289(@tanstack/react-query@5.21.2)(@trpc/client@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@trpc/server': 11.0.0-next-beta.289
|
'@trpc/server': 11.0.0-next-beta.289
|
||||||
next: 14.1.0(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0)
|
next: 14.1.1-canary.58(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0)
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@trpc/react-query@11.0.0-next-beta.289(@tanstack/react-query@5.20.5)(@trpc/client@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(react-dom@18.2.0)(react@18.2.0):
|
/@trpc/react-query@11.0.0-next-beta.289(@tanstack/react-query@5.21.2)(@trpc/client@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-SAn09DmZ4eFYLS0cCHOVNvRHJhHZ2ssUj4LUTj56wym0MieaCSrcxTqiolnaMfF+mWc1SJlLOzebrxaTHPwJSw==}
|
resolution: {integrity: sha512-SAn09DmZ4eFYLS0cCHOVNvRHJhHZ2ssUj4LUTj56wym0MieaCSrcxTqiolnaMfF+mWc1SJlLOzebrxaTHPwJSw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tanstack/react-query': ^5.0.0
|
'@tanstack/react-query': ^5.0.0
|
||||||
@@ -2348,7 +2362,7 @@ packages:
|
|||||||
react: '>=18.2.0'
|
react: '>=18.2.0'
|
||||||
react-dom: '>=18.2.0'
|
react-dom: '>=18.2.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tanstack/react-query': 5.20.5(react@18.2.0)
|
'@tanstack/react-query': 5.21.2(react@18.2.0)
|
||||||
'@trpc/client': 11.0.0-next-beta.289(@trpc/server@11.0.0-next-beta.289)
|
'@trpc/client': 11.0.0-next-beta.289(@trpc/server@11.0.0-next-beta.289)
|
||||||
'@trpc/server': 11.0.0-next-beta.289
|
'@trpc/server': 11.0.0-next-beta.289
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
@@ -2587,11 +2601,11 @@ packages:
|
|||||||
/@types/react-dom@18.2.19:
|
/@types/react-dom@18.2.19:
|
||||||
resolution: {integrity: sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==}
|
resolution: {integrity: sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 18.2.55
|
'@types/react': 18.2.56
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/react@18.2.55:
|
/@types/react@18.2.56:
|
||||||
resolution: {integrity: sha512-Y2Tz5P4yz23brwm2d7jNon39qoAtMMmalOQv6+fEFt1mT+FcM3D841wDpoUvFXhaYenuROCy3FZYqdTjM7qVyA==}
|
resolution: {integrity: sha512-NpwHDMkS/EFZF2dONFQHgkPRwhvgq/OAvIaGQzxGSBmaeR++kTg6njr15Vatz0/2VcCEwJQFi6Jf4Q0qBu0rLA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/prop-types': 15.7.11
|
'@types/prop-types': 15.7.11
|
||||||
'@types/scheduler': 0.16.8
|
'@types/scheduler': 0.16.8
|
||||||
@@ -5378,7 +5392,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-/WByRr4jDcsKlvMd1dRJnPfS1GVO3WuKyaurJ/vvXcOaUQO8rnNObCQMlv/5uCceVQIq5Q4WLF44ohsdiTohdg==}
|
resolution: {integrity: sha512-/WByRr4jDcsKlvMd1dRJnPfS1GVO3WuKyaurJ/vvXcOaUQO8rnNObCQMlv/5uCceVQIq5Q4WLF44ohsdiTohdg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/jotai@2.6.4(@types/react@18.2.55)(react@18.2.0):
|
/jotai@2.6.4(@types/react@18.2.56)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-RniwQPX4893YlNR1muOtyUGHYaTD1fhEN4qnOuZJSrDHj6xdEMrqlRSN/hCm2fshwk78ruecB/P2l+NCVWe6TQ==}
|
resolution: {integrity: sha512-RniwQPX4893YlNR1muOtyUGHYaTD1fhEN4qnOuZJSrDHj6xdEMrqlRSN/hCm2fshwk78ruecB/P2l+NCVWe6TQ==}
|
||||||
engines: {node: '>=12.20.0'}
|
engines: {node: '>=12.20.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -5390,7 +5404,7 @@ packages:
|
|||||||
react:
|
react:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 18.2.55
|
'@types/react': 18.2.56
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@@ -5890,7 +5904,7 @@ packages:
|
|||||||
engines: {node: '>= 0.4.0'}
|
engines: {node: '>= 0.4.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/next-auth@5.0.0-beta.11(next@14.1.0)(react@18.2.0):
|
/next-auth@5.0.0-beta.11(next@14.1.1-canary.58)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-OrfOVyXBGC69O8lEe81ZwFzQuWnY3Bsqee7kawr1iBycgSD64oNCtoFvRXiOWe7R0jOaafqQQlpsOS0mARhT3Q==}
|
resolution: {integrity: sha512-OrfOVyXBGC69O8lEe81ZwFzQuWnY3Bsqee7kawr1iBycgSD64oNCtoFvRXiOWe7R0jOaafqQQlpsOS0mARhT3Q==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@simplewebauthn/browser': ^9.0.1
|
'@simplewebauthn/browser': ^9.0.1
|
||||||
@@ -5907,7 +5921,7 @@ packages:
|
|||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@auth/core': 0.27.0
|
'@auth/core': 0.27.0
|
||||||
next: 14.1.0(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0)
|
next: 14.1.1-canary.58(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0)
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@@ -5923,8 +5937,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
|
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/next@14.1.0(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0):
|
/next@14.1.1-canary.58(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0):
|
||||||
resolution: {integrity: sha512-wlzrsbfeSU48YQBjZhDzOwhWhGsy+uQycR8bHAOt1LY1bn3zZEcDyHQOEoN3aWzQ8LHCAJ1nqrWCc9XF2+O45Q==}
|
resolution: {integrity: sha512-rL4Tmm5ldL4VA+lTz0sQfJcDOe3vXB7LItcWuILb8eDs/6X1wrXNF9KDtviJH0tq/jrrTOihN7aBCkkge85m6g==}
|
||||||
engines: {node: '>=18.17.0'}
|
engines: {node: '>=18.17.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -5938,8 +5952,8 @@ packages:
|
|||||||
sass:
|
sass:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/env': 14.1.0
|
'@next/env': 14.1.1-canary.58
|
||||||
'@swc/helpers': 0.5.2
|
'@swc/helpers': 0.5.5
|
||||||
busboy: 1.6.0
|
busboy: 1.6.0
|
||||||
caniuse-lite: 1.0.30001587
|
caniuse-lite: 1.0.30001587
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
@@ -5949,15 +5963,15 @@ packages:
|
|||||||
sass: 1.71.0
|
sass: 1.71.0
|
||||||
styled-jsx: 5.1.1(@babel/core@7.23.9)(react@18.2.0)
|
styled-jsx: 5.1.1(@babel/core@7.23.9)(react@18.2.0)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@next/swc-darwin-arm64': 14.1.0
|
'@next/swc-darwin-arm64': 14.1.1-canary.58
|
||||||
'@next/swc-darwin-x64': 14.1.0
|
'@next/swc-darwin-x64': 14.1.1-canary.58
|
||||||
'@next/swc-linux-arm64-gnu': 14.1.0
|
'@next/swc-linux-arm64-gnu': 14.1.1-canary.58
|
||||||
'@next/swc-linux-arm64-musl': 14.1.0
|
'@next/swc-linux-arm64-musl': 14.1.1-canary.58
|
||||||
'@next/swc-linux-x64-gnu': 14.1.0
|
'@next/swc-linux-x64-gnu': 14.1.1-canary.58
|
||||||
'@next/swc-linux-x64-musl': 14.1.0
|
'@next/swc-linux-x64-musl': 14.1.1-canary.58
|
||||||
'@next/swc-win32-arm64-msvc': 14.1.0
|
'@next/swc-win32-arm64-msvc': 14.1.1-canary.58
|
||||||
'@next/swc-win32-ia32-msvc': 14.1.0
|
'@next/swc-win32-ia32-msvc': 14.1.1-canary.58
|
||||||
'@next/swc-win32-x64-msvc': 14.1.0
|
'@next/swc-win32-x64-msvc': 14.1.1-canary.58
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
@@ -7847,6 +7861,17 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/use-deep-compare-effect@1.8.1(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-kbeNVZ9Zkc0RFGpfMN3MNfaKNvcLNyxOAAd9O4CBZ+kCBXXscn9s/4I+8ytUER4RDpEYs5+O6Rs4PqiZ+rHr5Q==}
|
||||||
|
engines: {node: '>=10', npm: '>=6'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.13'
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.23.9
|
||||||
|
dequal: 2.0.3
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/use-isomorphic-layout-effect@1.1.2(react@18.2.0):
|
/use-isomorphic-layout-effect@1.1.2(react@18.2.0):
|
||||||
resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
|
resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user