From 673589f6c833a3822e27524599ce88daab32d025 Mon Sep 17 00:00:00 2001 From: ajnart Date: Thu, 13 Aug 2026 22:13:08 +0200 Subject: [PATCH] fix(docs): restore homepage integration flow --- .../pages/home/dataflow/dataflow.tsx | 207 ++++++----------- apps/docs/src/pages/index.module.css | 219 +++++++++++++++++- apps/docs/src/pages/index.tsx | 10 +- 3 files changed, 291 insertions(+), 145 deletions(-) diff --git a/apps/docs/src/components/pages/home/dataflow/dataflow.tsx b/apps/docs/src/components/pages/home/dataflow/dataflow.tsx index 0c04cb279..b491dc5a5 100644 --- a/apps/docs/src/components/pages/home/dataflow/dataflow.tsx +++ b/apps/docs/src/components/pages/home/dataflow/dataflow.tsx @@ -1,147 +1,86 @@ -import React, { useRef } from "react"; +import styles from "../../../../pages/index.module.css"; +import { supportedIntegrations } from "../../../../constants/supported-integrations"; -import LeaderLine, { SocketType } from "leader-line-new"; +const featuredIntegrations = [ + { name: "Radarr", position: styles.dataflowNodeTopLeft }, + { name: "Sonarr", position: styles.dataflowNodeMiddleLeft }, + { name: "Lidarr", position: styles.dataflowNodeBottomLeft }, + { name: "SABnzbd", position: styles.dataflowNodeTopRight }, + { name: "Jellyfin", position: styles.dataflowNodeMiddleRight }, +].map((node) => ({ + ...node, + image: supportedIntegrations.find((integration) => integration.name === node.name)?.iconUrl ?? "", +})); -export const DataflowVisualizationComponent = () => { - const homarrRef = useRef(null); - const sonarrRef = useRef(null); - const lidarrRef = useRef(null); - const radarrRef = useRef(null); - const jellyfinRef = useRef(null); - const plexRef = useRef(null); - const sabnzbdRef = useRef(null); +const featuredNames = new Set(["Homarr", ...featuredIntegrations.map((integration) => integration.name)]); +const otherIntegrations = supportedIntegrations.filter((integration) => !featuredNames.has(integration.name)); +const iconCycleStepInSeconds = 1.4; - return ( -
-

+const paths = [ + "M145 82 C330 82 330 235 480 260", + "M145 260 C300 260 360 260 480 260", + "M145 438 C330 438 330 285 480 260", + "M480 260 C630 235 630 82 815 82", + "M480 260 C600 260 660 260 815 260", + "M480 260 C630 285 630 438 815 438", +]; + +export const DataflowVisualizationComponent = () => ( +
+
+

No YAML configurations.
Easy and quick to manage integrations.

+

Homarr brings status, controls, and updates together in one dashboard.

+
-
- {"Homarr +
+ - {"Radarr"} - {"Sonarr"} - {"Lidarr"} + {featuredIntegrations.map((integration) => ( +
+ + {integration.name} +
+ ))} - {"Jellyfin"} +
+ + +{otherIntegrations.length} more +
- {"Plex"} - - {"Sabnzbd"} - - - - - - - +
+ + Homarr
- ); -}; - -export const LineTree = ({ - start, - end, - startSocket, - endSocket, - x = 0, -}: { - start: any; - end: any; - startSocket: SocketType; - endSocket: SocketType; - x?: number; -}) => { - const line = useRef(null); - let leaderLine: LeaderLine; - - React.useEffect(() => { - const drawLine = () => { - leaderLine = new LeaderLine(start.current, LeaderLine.pointAnchor(end.current, { x: `${x}%` }), { - startSocket: startSocket, - endSocket: endSocket, - color: "var(--ifm-color-primary)", - size: 3, - dash: { - animation: true, - }, - }); - }; - const timer = setInterval(() => { - if (start.current) { - clearInterval(timer); - drawLine(); - } - }, 5); - return () => { - timer && clearInterval(timer); - leaderLine.remove(); - }; - }, []); - - React.useEffect(() => { - // scroll and resize listeners could be assigned here - setTimeout(() => { - // skip current even loop and wait - // the end of parent's render call - if (line.current && end?.current) { - (line.current as any).position(); - } - }, 0); - }); - - return null; -}; +
+); diff --git a/apps/docs/src/pages/index.module.css b/apps/docs/src/pages/index.module.css index 36af84e40..4c17f58aa 100644 --- a/apps/docs/src/pages/index.module.css +++ b/apps/docs/src/pages/index.module.css @@ -230,6 +230,188 @@ section { font-weight: 700; } +.dataflowSection { + margin-bottom: 0; + padding: clamp(4rem, 8vw, 7rem) clamp(1rem, 4vw, 3rem); + overflow: hidden; + background: color-mix(in srgb, var(--ifm-background-color) 88%, var(--ifm-color-primary)); +} + +.dataflowHeading { + max-width: 58rem; + margin: 0 auto clamp(2rem, 5vw, 4rem); + text-align: center; +} + +.dataflowHeading h2 { + margin-bottom: 1rem; + font-size: clamp(2rem, 5vw, 4rem); + line-height: 1.08; + letter-spacing: -0.03em; + text-wrap: balance; +} + +.dataflowHeading p { + margin: 0 auto; + max-width: 42rem; + color: color-mix(in srgb, var(--ifm-font-color-base) 76%, transparent); + font-size: clamp(1rem, 2vw, 1.25rem); + text-wrap: pretty; +} + +.dataflowDiagram { + position: relative; + width: min(100%, 70rem); + aspect-ratio: 960 / 520; + margin-inline: auto; +} + +.dataflowLines { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + overflow: visible; +} + +.dataflowLine, +.dataflowSignal, +.dataflowSignalReverse { + fill: none; + stroke-linecap: round; +} + +.dataflowLine { + stroke: color-mix(in srgb, var(--ifm-color-primary) 30%, transparent); + stroke-width: 3; +} + +.dataflowSignal, +.dataflowSignalReverse { + stroke: var(--ifm-color-primary); + stroke-width: 7; + stroke-dasharray: 0.035 0.965; + animation: dataflow-signal 2.8s linear infinite; +} + +.dataflowSignalReverse { + animation-direction: reverse; + animation-delay: -1.4s; +} + +.dataflowNode { + position: absolute; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + width: clamp(4.5rem, 10vw, 7.5rem); + padding: clamp(0.6rem, 1.5vw, 1rem); + border-radius: 0.875rem; + color: var(--ifm-font-color-base); + background: var(--ifm-background-surface-color); + box-shadow: 0 6px 12px rgb(31 41 55 / 14%); + transform: translate(-50%, -50%); +} + +.dataflowNode img { + width: clamp(2rem, 4.5vw, 3.4rem); + height: clamp(2rem, 4.5vw, 3.4rem); + object-fit: contain; +} + +.dataflowMoreIcons { + position: relative; + width: clamp(2rem, 4.5vw, 3.4rem); + aspect-ratio: 1; +} + +.dataflowMoreIcons img { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + opacity: 0; + animation: dataflow-icon-cycle linear infinite; +} + +.dataflowNode span { + overflow: hidden; + width: 100%; + font-size: clamp(0.68rem, 1.4vw, 0.875rem); + font-weight: 700; + line-height: 1.2; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; +} + +.dataflowNodeTopLeft, +.dataflowNodeMiddleLeft, +.dataflowNodeBottomLeft { + left: 15%; +} + +.dataflowNodeTopRight, +.dataflowNodeMiddleRight, +.dataflowNodeBottomRight { + left: 85%; +} + +.dataflowNodeTopLeft, +.dataflowNodeTopRight { + top: 16%; +} + +.dataflowNodeMiddleLeft, +.dataflowNodeMiddleRight, +.dataflowHub { + top: 50%; +} + +.dataflowNodeBottomLeft, +.dataflowNodeBottomRight { + top: 84%; +} + +.dataflowHub { + left: 50%; + width: clamp(6rem, 13vw, 9rem); + background: var(--ifm-color-primary); + box-shadow: 0 8px 16px color-mix(in srgb, var(--ifm-color-primary-darkest) 35%, transparent); +} + +.dataflowHub img { + width: clamp(3rem, 7vw, 5.4rem); + height: clamp(3rem, 7vw, 5.4rem); + filter: brightness(0) invert(1); +} + +.dataflowHub span { + color: #171717; + font-size: clamp(0.8rem, 1.8vw, 1rem); +} + +@keyframes dataflow-signal { + to { + stroke-dashoffset: -1; + } +} + +@keyframes dataflow-icon-cycle { + 0%, + 4.8% { + opacity: 1; + transform: scale(1); + } + + 5.3%, + 99% { + opacity: 0; + transform: scale(0.82); + } +} + :global([data-theme="dark"]) .resourceMeterCritical .resourceLabel, :global([data-theme="dark"]) .containerRowFailed { color: #ffe3e3; @@ -280,8 +462,39 @@ section { } } -@media (prefers-reduced-motion: reduce) { - .statusDotFailed { - animation: none; +@media screen and (max-width: 640px) { + .dataflowSection { + padding-block: 4rem; + } + + .dataflowHeading { + margin-bottom: 2rem; + } + + .dataflowDiagram { + aspect-ratio: 4 / 5; + } + + .dataflowNode { + width: 4.5rem; + padding: 0.6rem; + } +} + +@media (prefers-reduced-motion: reduce) { + .statusDotFailed, + .dataflowSignal, + .dataflowSignalReverse, + .dataflowMoreIcons img { + animation: none; + } + + .dataflowSignal, + .dataflowSignalReverse { + display: none; + } + + .dataflowMoreIcons img:first-child { + opacity: 1; } } diff --git a/apps/docs/src/pages/index.tsx b/apps/docs/src/pages/index.tsx index 7f5426c10..06b85d6ce 100644 --- a/apps/docs/src/pages/index.tsx +++ b/apps/docs/src/pages/index.tsx @@ -5,9 +5,9 @@ import Layout from "@theme/Layout"; import HomepageFeatures from "../components/pages/home/features/features"; import HomepageUserReviews from "../components/pages/home/review-list/review-list"; import HomeHero from "../components/pages/home/hero/hero"; +import { DataflowVisualizationComponent } from "../components/pages/home/dataflow/dataflow"; import styles from "./index.module.css"; -import BrowserOnly from "@docusaurus/BrowserOnly"; import { SectionContainer } from "@site/src/components/pages/home/container/section-container"; import { DragAndDropShowcase } from "@site/src/components/pages/home/drag-and-drop/drag-and-drop-showcase"; import { AvailableIntegrations } from "@site/src/components/pages/home/integrations/available-integrations"; @@ -32,13 +32,7 @@ export default function Home() { - loading...}> - {() => { - const DataflowVisualizationComponent = - require("../components/pages/home/dataflow/dataflow").DataflowVisualizationComponent; - return ; - }} - +