diff --git a/apps/nextjs/src/app/[locale]/widgets/[kind]/page.tsx b/apps/nextjs/src/app/[locale]/widgets/[kind]/page.tsx
index 454e7a805..620a7792e 100644
--- a/apps/nextjs/src/app/[locale]/widgets/[kind]/page.tsx
+++ b/apps/nextjs/src/app/[locale]/widgets/[kind]/page.tsx
@@ -2,6 +2,9 @@ import { Suspense } from "react";
import { notFound } from "next/navigation";
import { Center, Loader } from "@mantine/core";
+import { IntegrationProvider } from "@homarr/auth/client";
+import { auth } from "@homarr/auth/next";
+import { getIntegrationsWithPermissionsAsync } from "@homarr/auth/server";
import type { WidgetKind } from "@homarr/definitions";
import { widgetKinds } from "@homarr/widgets/manifest";
@@ -16,12 +19,15 @@ export default async function WidgetPreview(props: Props) {
if (!widgetKinds.includes(kind as WidgetKind)) {
notFound();
}
+ const integrations = await getIntegrationsWithPermissionsAsync(await auth());
return (
-
- }>
-
-
-
+
+
+ }>
+
+
+
+
);
}
diff --git a/apps/nextjs/src/components/board/items/item-content.tsx b/apps/nextjs/src/components/board/items/item-content.tsx
index 49ed7ffab..4a771e769 100644
--- a/apps/nextjs/src/components/board/items/item-content.tsx
+++ b/apps/nextjs/src/components/board/items/item-content.tsx
@@ -27,6 +27,7 @@ import { loadWidgetResources, reduceWidgetOptionsWithDefinition } from "@homarr/
import type { SectionItem } from "~/app/[locale]/boards/_types";
import { getLogicalTrackSize } from "~/components/board/layout";
+import { useBoardCanvasScale } from "~/components/board/layout/scaled-board-canvas";
import advancedFocusClasses from "../advanced-focus/advanced-focus.module.css";
import { useAdvancedFocus } from "../advanced-focus/context";
import { startAdvancedFocusEntrance } from "../advanced-focus/entrance";
@@ -167,6 +168,7 @@ const LoadedBoardItemContent = ({
const cardRef = useRef(null);
const previewEntranceFrameRef = useRef(null);
const board = useRequiredBoard();
+ const boardCanvasScale = useBoardCanvasScale();
const t = useI18n();
const [isEditMode] = useEditMode();
const settings = useSettings();
@@ -180,7 +182,10 @@ const LoadedBoardItemContent = ({
const { width: viewportWidth, height: viewportHeight } = viewportSize;
const supportsAdvancedFocus = definitionSupportsAdvancedFocus(definition);
const widgetName = getWidgetName(item.kind, t);
- const previewDimensions = useMemo(() => ({ width, height }), [height, width]);
+ const previewDimensions = useMemo(
+ () => ({ width, height, scale: boardCanvasScale }),
+ [boardCanvasScale, height, width],
+ );
const advancedViewLabel = t("item.advancedFocus.label", { widget: widgetName });
const advancedViewId = `advanced-focus-${item.id}`;
const activeFocus = supportsAdvancedFocus && active?.itemId === item.id ? active : null;
diff --git a/apps/nextjs/src/components/board/items/item-menu.tsx b/apps/nextjs/src/components/board/items/item-menu.tsx
index b14ea80d2..8cb01d47b 100644
--- a/apps/nextjs/src/components/board/items/item-menu.tsx
+++ b/apps/nextjs/src/components/board/items/item-menu.tsx
@@ -21,7 +21,7 @@ import { LazyWidgetEditModal, preloadWidgetEditModal } from "./lazy-widget-edit-
interface BoardItemMenuProps {
item: SectionItem;
definition: WidgetDefinition;
- previewDimensions: { width: number; height: number };
+ previewDimensions: { width: number; height: number; scale?: number };
resetErrorBoundary?: () => void;
}
diff --git a/apps/nextjs/src/components/board/items/widget-context-menu.tsx b/apps/nextjs/src/components/board/items/widget-context-menu.tsx
index bf9688b8e..e0d104358 100644
--- a/apps/nextjs/src/components/board/items/widget-context-menu.tsx
+++ b/apps/nextjs/src/components/board/items/widget-context-menu.tsx
@@ -35,7 +35,7 @@ import { matchesWidgetItemQuery } from "./widget-query-scope";
interface WidgetContextMenuProps {
item: SectionItem;
definition: WidgetDefinition;
- previewDimensions: { width: number; height: number };
+ previewDimensions: { width: number; height: number; scale?: number };
widgetStateRef: MutableRefObject | null>;
widgetRuntimeRef: WidgetRuntimeRef;
sourceRef: RefObject;
diff --git a/packages/widgets/src/modals/widget-edit-modal.module.css b/packages/widgets/src/modals/widget-edit-modal.module.css
index 222cece6c..0ca0d61ea 100644
--- a/packages/widgets/src/modals/widget-edit-modal.module.css
+++ b/packages/widgets/src/modals/widget-edit-modal.module.css
@@ -12,7 +12,10 @@
.previewPanel {
position: sticky;
top: 0;
+ width: 100%;
+ max-width: 52rem;
min-width: 0;
+ justify-self: center;
padding: var(--mantine-spacing-md);
border: 1px solid var(--mantine-color-default-border);
border-radius: var(--mantine-radius-md);
@@ -141,13 +144,14 @@
}
}
-@media (max-width: 75em) {
+@media (max-width: 68em) {
.workspace[data-with-preview] {
grid-template-columns: 1fr;
}
.previewPanel {
position: static;
+ max-width: 44rem;
}
.previewCanvas {
diff --git a/packages/widgets/src/modals/widget-edit-modal.tsx b/packages/widgets/src/modals/widget-edit-modal.tsx
index 5fe3690ae..7de2666aa 100644
--- a/packages/widgets/src/modals/widget-edit-modal.tsx
+++ b/packages/widgets/src/modals/widget-edit-modal.tsx
@@ -14,7 +14,7 @@ import { ErrorBoundary } from "react-error-boundary";
import { z } from "zod/v4";
import { objectEntries } from "@homarr/common";
-import { useSession } from "@homarr/auth/client";
+import { IntegrationProvider, useSession } from "@homarr/auth/client";
import { useOptionalBoard } from "@homarr/boards/context";
import type { WidgetKind } from "@homarr/definitions";
import { createModal, useModalAction } from "@homarr/modals";
@@ -68,7 +68,7 @@ export interface WidgetEditModalProps {
onIntegrationSaved?: () => void;
onOpenNewIntegration?: (onCreated?: (id: string) => void) => void;
previewComponent?: ComponentType>;
- previewDimensions?: { width: number; height: number };
+ previewDimensions?: { width: number; height: number; scale?: number };
}
interface WidgetEditPreviewProps {
@@ -78,7 +78,8 @@ interface WidgetEditPreviewProps {
state: WidgetEditModalState;
itemId?: string;
boardId?: string;
- dimensions?: { width: number; height: number };
+ dimensions?: { width: number; height: number; scale?: number };
+ integrationData: IntegrationSelectOption[];
onChangeOptions: (newOptions: Record) => void;
}
@@ -90,15 +91,19 @@ const WidgetEditPreview = ({
itemId,
boardId,
dimensions = { width: 400, height: 240 },
+ integrationData,
onChangeOptions,
}: WidgetEditPreviewProps) => {
const tItem = useI18n("item.edit");
const { ref, width: availableWidth, height: availableHeight } = useElementSize();
const sourceWidth = Math.max(dimensions.width, 1);
const sourceHeight = Math.max(dimensions.height, 1);
- let previewScale = 1;
+ let maximumScale = dimensions.scale ?? 0.9;
+ if (!Number.isFinite(maximumScale) || maximumScale <= 0) maximumScale = 0.9;
+ maximumScale = Math.min(maximumScale, 0.9);
+ let previewScale = maximumScale;
if (availableWidth > 0 && availableHeight > 0) {
- previewScale = Math.min(availableWidth / sourceWidth, availableHeight / sourceHeight, 1);
+ previewScale = Math.min(availableWidth / sourceWidth, availableHeight / sourceHeight, maximumScale);
}
const previewWidth = sourceWidth * previewScale;
const previewHeight = sourceHeight * previewScale;
@@ -106,6 +111,14 @@ const WidgetEditPreview = ({
const hasIntegrationSupport = "supportedIntegrations" in definition;
const integrationRequired = hasIntegrationSupport && definition.integrationsRequired !== false;
const isMissingIntegration = integrationRequired && state.integrationIds.length === 0;
+ const previewIntegrations = integrationData.map((integration) => ({
+ id: integration.id,
+ permissions: integration.permissions ?? {
+ hasFullAccess: false,
+ hasInteractAccess: false,
+ hasUseAccess: false,
+ },
+ }));
return (
@@ -149,19 +162,21 @@ const WidgetEditPreview = ({
)}
>
-
- onChangeOptions(newOptions as Record)}
- />
-
+
+
+ onChangeOptions(newOptions as Record)}
+ />
+
+
)}
@@ -375,6 +390,7 @@ export const WidgetEditModal = createModal>(({
itemId={innerProps.itemId}
boardId={innerProps.boardId ?? board?.id}
dimensions={innerProps.previewDimensions}
+ integrationData={innerProps.integrationData}
onChangeOptions={handlePreviewOptionsChange}
/>
)}