Replace entire codebase with homarr-labs/homarr

This commit is contained in:
Thomas Camlong
2026-01-15 21:54:44 +01:00
parent c5bc3b1559
commit 4fdd1fe351
4666 changed files with 409577 additions and 147434 deletions

View File

@@ -0,0 +1,8 @@
.iframe {
border-radius: var(--mantine-radius-sm);
width: 100%;
height: 100%;
border: none;
background: none;
background-color: transparent;
}

View File

@@ -0,0 +1,118 @@
"use client";
import { Box, Stack, Text, Title } from "@mantine/core";
import { IconBrowserOff, IconProtocol } from "@tabler/icons-react";
import { objectEntries } from "@homarr/common";
import { useI18n } from "@homarr/translation/client";
import type { WidgetComponentProps } from "../definition";
import classes from "./component.module.css";
export default function IFrameWidget({ options, isEditMode }: WidgetComponentProps<"iframe">) {
const t = useI18n();
const { embedUrl, allowScrolling, ...permissions } = options;
const allowedPermissions = getAllowedPermissions(permissions);
const sandboxFlags = getSandboxFlags(permissions);
if (embedUrl.trim() === "") return <NoUrl />;
if (!isSupportedProtocol(embedUrl)) {
return <UnsupportedProtocol />;
}
return (
<Box h="100%" w="100%">
<iframe
style={isEditMode ? { userSelect: "none", pointerEvents: "none" } : undefined}
className={classes.iframe}
src={embedUrl}
title="widget iframe"
allow={allowedPermissions}
scrolling={allowScrolling ? "yes" : "no"}
sandbox={sandboxFlags.join(" ")}
>
<Text>{t("widget.iframe.error.noBrowerSupport")}</Text>
</iframe>
</Box>
);
}
const supportedProtocols = ["http", "https"];
const isSupportedProtocol = (url: string) => {
try {
const parsedUrl = new URL(url);
return supportedProtocols.map((protocol) => `${protocol}:`).includes(`${parsedUrl.protocol}`);
} catch {
return false;
}
};
const NoUrl = () => {
const t = useI18n();
return (
<Stack align="center" justify="center" h="100%">
<IconBrowserOff />
<Title order={4}>{t("widget.iframe.error.noUrl")}</Title>
</Stack>
);
};
const UnsupportedProtocol = () => {
const t = useI18n();
return (
<Stack align="center" justify="center" h="100%">
<IconProtocol />
<Title order={4} ta="center">
{t("widget.iframe.error.unsupportedProtocol", {
supportedProtocols: supportedProtocols.map((protocol) => protocol).join(", "),
})}
</Title>
</Stack>
);
};
const getAllowedPermissions = (
permissions: Omit<WidgetComponentProps<"iframe">["options"], "embedUrl" | "allowScrolling">,
) => {
return (
objectEntries(permissions)
.filter(([_key, value]) => value)
// * means it applies to all origins
.map(([key]) => `${permissionMapping[key]} *`)
.join("; ")
);
};
const getSandboxFlags = (
permissions: Omit<WidgetComponentProps<"iframe">["options"], "embedUrl" | "allowScrolling">,
) => {
const baseSandbox = [
"allow-scripts",
"allow-same-origin",
"allow-forms",
"allow-popups",
"allow-top-navigation-by-user-activation",
];
if (permissions.allowFullScreen) {
baseSandbox.push("allow-presentation");
}
if (permissions.allowPayment) {
baseSandbox.push("allow-popups-to-escape-sandbox");
}
return baseSandbox;
};
const permissionMapping = {
allowAutoPlay: "autoplay",
allowCamera: "camera",
allowFullScreen: "fullscreen",
allowGeolocation: "geolocation",
allowMicrophone: "microphone",
allowPayment: "payment",
} satisfies Record<keyof Omit<WidgetComponentProps<"iframe">["options"], "embedUrl" | "allowScrolling">, string>;

View File

@@ -0,0 +1,22 @@
import { IconBrowser } from "@tabler/icons-react";
import { createWidgetDefinition } from "../definition";
import { optionsBuilder } from "../options";
export const { definition, componentLoader } = createWidgetDefinition("iframe", {
icon: IconBrowser,
createOptions() {
return optionsBuilder.from((factory) => ({
embedUrl: factory.text(),
allowFullScreen: factory.switch(),
allowScrolling: factory.switch({
defaultValue: true,
}),
allowPayment: factory.switch(),
allowAutoPlay: factory.switch(),
allowMicrophone: factory.switch(),
allowCamera: factory.switch(),
allowGeolocation: factory.switch(),
}));
},
}).withDynamicImport(() => import("./component"));