diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json
index 0e20d20b0..4c68d23da 100644
--- a/packages/translation/src/lang/en.json
+++ b/packages/translation/src/lang/en.json
@@ -889,12 +889,17 @@
"list": {
"title": "Custom Widgets",
"noResults": "No custom widgets yet. Create your first one!",
+ "noDescription": "No description",
"loading": "Loading...",
+ "enabled": "Enabled",
+ "disabled": "Disabled",
+ "disabledDescription": "This widget stays configured but will not load on any board.",
+ "requestCount": "{count, plural, one {# request} other {# requests}}",
"invalidDefinition": "Invalid definition",
"invalidDefinitionDescription": "This stored widget no longer matches the current Custom Widget schema. Delete it and recreate or import a valid definition.",
"migrationRequired": "Migration required",
"migrationDescription": "This v1 custom widget is preserved but cannot run until it is migrated to Custom JSX v2.",
- "migrationInstructions": "Open Actions, copy the redacted migration prompt into an LLM, then copy its single JSON response and choose Paste migrated widget. Existing board placements keep working after migration.",
+ "migrationInstructions": "Open Actions to copy or export the original v1 definition for manual recreation. You can also use the migration prompt with an LLM. Existing board placements keep working after migration.",
"missingCredentials": "{count, plural, one {Credential missing} other {# credentials missing}}",
"missingCredentialsDescription": "{count, plural, one {This widget still needs one credential.} other {This widget still needs # credentials.}} Edit it to configure the encrypted values."
},
@@ -904,6 +909,11 @@
},
"edit": {
"title": "Edit Custom Widget"
+ },
+ "tabs": {
+ "ariaLabel": "Custom widget sections",
+ "installed": "Installed",
+ "workshop": "Workshop"
}
},
"action": {
@@ -924,6 +934,8 @@
"downloadSkill": "View Custom Widget Skill",
"publishWorkshop": "Publish to Workshop",
"migrate": "Migrate Custom Widget",
+ "copyLegacyDefinition": "Copy original widget content",
+ "exportLegacyDefinition": "Export original widget content",
"copyMigrationPrompt": "Copy LLM migration prompt",
"pasteMigration": "Paste migrated widget",
"importMigration": "Import migrated JSON file",
@@ -1282,6 +1294,8 @@
"imported": "Widget imported successfully.",
"migrated": "Legacy widget migrated successfully. Existing board placements now use the v2 definition.",
"migrationError": "Failed to migrate the legacy widget. Ensure the file uses the homarr-custom-widget-v2 schema.",
+ "legacyDefinitionCopied": "The original widget content was copied. Stored credential values are not included.",
+ "legacyDefinitionCopyError": "Failed to copy the original widget content.",
"migrationPromptCopied": "A redacted migration prompt was copied. Paste it into an LLM, copy its single JSON response, then choose Paste migrated widget here.",
"migrationPromptCopyError": "Failed to copy the migration prompt.",
"aiPromptCopied": "AI prompt copied to clipboard",
@@ -1436,64 +1450,68 @@
}
},
"workshop": {
- "installSuccess": "Custom widget installed",
- "installSuccessDescription": "Add its credentials, test the requests, and save when it is ready.",
- "installError": "Could not install widget",
- "installErrorDescription": "The Workshop submission is not a valid Custom JSX v2 widget.",
"title": "Workshop",
- "importFromWorkshop": "Import from Workshop",
+ "browseWorkshop": "Browse Workshop",
"openCommunity": "Open Workshop",
- "manageSubmissionsTitle": "Manage submissions on Workshop",
- "manageSubmissionsDescription": "Publishing starts in Homarr, but updates, reports, outdated status, and deletion are managed directly on the Workshop website. Sign in there and select Yours to find your submissions.",
- "description": "Community Custom JSX v2 widgets. Inspect the source and network capabilities before installing.",
- "descriptionCss": "Community Custom CSS themes. Preview every rule before importing it into a board.",
- "securityNotice": "Workshop widgets can call their declared API sources and perform declared actions with credentials you configure locally. Secrets are never included.",
- "securityNoticeCss": "Community CSS can change the appearance and usability of a board. Review it before importing; nothing is saved until you save board settings.",
- "signIn": "Sign in with GitHub",
- "signInError": "GitHub sign-in failed. Check that the Workshop OAuth provider is configured and that popups are allowed.",
- "signOut": "Sign out",
+ "openOnWorkshop": "Open {title} on the Workshop website",
+ "back": "Back to Workshop",
+ "cancel": "Cancel",
"search": "Search widgets",
"searchCss": "Search CSS themes",
"sort": "Sort",
"sortTop": "Top rated",
"sortNewest": "Newest",
+ "listAriaLabel": "Workshop widgets",
+ "listAriaLabelCss": "Workshop CSS themes",
+ "empty": "No widgets match this search.",
+ "emptyDescription": "Community widgets published to the Workshop show up here.",
+ "emptyFilteredTitle": "No widgets match this search",
+ "emptyFilteredDescription": "No Workshop widget matches \"{search}\".",
+ "clearSearch": "Clear search",
+ "emptyCss": "No CSS themes match this search.",
"unavailableTitle": "Homarr Workshop seems to be unavailable",
"unavailable": "The community service may be temporarily down. Try again in a moment. Your installed widgets are unaffected.",
"retry": "Try again",
- "empty": "No widgets match this search.",
- "emptyCss": "No CSS themes match this search.",
+ "loadError": "This submission could not be loaded.",
"noDescription": "No description",
"author": "by {name}",
"communityMember": "Community member",
"outdated": "Outdated",
"outdatedWarning": "The author marked this submission as outdated. Review it carefully before importing.",
- "reportCount": "{count, plural, one {# report} other {# reports}}",
- "reportWarning": "{count, plural, one {This submission has # open community report.} other {This submission has # open community reports.}} It remains installable, but you should review its content first.",
- "reportVisibility": "Report details are visible only to this submission's author and Workshop admins.",
+ "score": "Score: {count, number}",
+ "upvote": "Upvote",
+ "downvote": "Downvote",
+ "voteError": "The vote could not be saved.",
+ "signIn": "Sign in with GitHub",
+ "signInError": "GitHub sign-in failed. Check that the Workshop OAuth provider is configured and that popups are allowed.",
+ "signOut": "Sign out",
+ "signInHint": "Sign in to vote or report. Installing does not require a Workshop account.",
+ "securityNotice": "Workshop widgets can call their declared API sources and perform declared actions with credentials you configure locally. Secrets are never included.",
+ "securityNoticeCss": "Community CSS can change the appearance and usability of a board. Review it before importing; nothing is saved until you save board settings.",
+ "install": "Install",
+ "installReviewTitle": "Review before installing",
+ "installError": "This widget cannot be installed",
+ "installErrorDescription": "The Workshop submission is not a valid Custom JSX v2 widget.",
+ "useCss": "Import CSS",
+ "importCss": "Import CSS from Workshop",
+ "importCssConfirm": "Replace the current custom CSS with \"{title}\"? Nothing is saved until you save the board settings.",
"screenshotAlt": "{title} screenshot {count, number}",
- "upvote": "Upvote, {count, plural, one {# vote} other {# votes}}",
- "downvote": "Downvote, {count, plural, one {# vote} other {# votes}}",
- "inspectCss": "Inspect CSS",
+ "zoomScreenshot": "View full size",
+ "previousScreenshot": "Previous screenshot",
+ "nextScreenshot": "Next screenshot",
+ "technicalDetails": "Technical details",
"sourceCode": "Workshop entry",
- "loadError": "This submission could not be loaded.",
- "sources": "Declared API sources",
- "requests": "Declared queries and actions",
- "noRequests": "This widget does not declare network requests.",
"export": "Export",
"report": "Report",
- "install": "Install widget",
- "continueInstall": "Continue installation",
- "confirmInstallTitle": "Confirm widget installation",
- "confirmInstallDescription": "Confirm where this widget connects and add any credentials required by your Homarr installation.",
- "backToWidget": "Back to widget",
- "useCss": "Import CSS",
- "signInHint": "Sign in to vote or report. Installing does not require a Workshop account.",
"reportTitle": "Report submission",
"reportReason": "Reason",
"reportExplanation": "What should the Workshop moderators know?",
"reportSend": "Send report",
"reportSent": "Report sent",
"reportSentDescription": "Workshop moderators can now review this submission.",
+ "reportCount": "{count, plural, one {# report} other {# reports}}",
+ "reportWarning": "{count, plural, one {This submission has # open community report.} other {This submission has # open community reports.}} It remains installable, but you should review its content first.",
+ "reportVisibility": "Report details are visible only to this submission's author and Workshop admins.",
"reportCategory": {
"outdated": "Outdated or no longer working",
"malicious": "Malicious",
@@ -1502,7 +1520,6 @@
"inappropriate": "Inappropriate",
"other": "Other"
},
- "voteError": "The vote could not be saved.",
"publish": {
"title": "Publish to Workshop",
"introTitle": "Share this widget with the community",
@@ -1511,7 +1528,6 @@
"listingDetailsDescription": "These details appear publicly alongside the exported widget definition.",
"success": "Published immediately. Community members can now install and report this widget.",
"successTitle": "Your widget is live",
- "manageTitle": "Continue on Workshop",
"manageDescription": "Open the submission to update its listing, review reports, mark it outdated, or delete it. Submission management is handled directly on the Workshop website.",
"viewSubmission": "View and manage submission",
"done": "Done",
@@ -1530,10 +1546,7 @@
"publicationNote": "Publishing is immediate. You can manage the submission afterward on Workshop.",
"definitionChanged": "The widget changed while publication was being prepared. Review its private source URLs again, then retry.",
"error": "Publication failed"
- },
- "installDialog": "Install from Workshop",
- "installed": "Widget installed",
- "installedDescription": "{name} is ready to configure."
+ }
},
"integration": {
"completion": {
@@ -6128,7 +6141,6 @@
"apps": "Apps",
"integrations": "Integrations",
"customWidgets": "Custom Widgets",
- "workshop": "Workshop",
"searchEngies": "Search engines",
"medias": "Medias",
"users": {
@@ -7614,11 +7626,14 @@
"flow": {
"label": "Flow"
}
+ },
+ "workshop": {
+ "label": "Workshop"
+ },
+ "publish": {
+ "label": "Publish to Workshop"
}
},
- "workshop": {
- "label": "Workshop"
- },
"users": {
"label": "Users",
"create": {
diff --git a/packages/ui/src/components/integration-marquee.module.css b/packages/ui/src/components/integration-marquee.module.css
index a3cb0daa2..81095fac0 100644
--- a/packages/ui/src/components/integration-marquee.module.css
+++ b/packages/ui/src/components/integration-marquee.module.css
@@ -1,41 +1,9 @@
-.root {
- --integration-marquee-accent: var(--mantine-primary-color-filled);
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: var(--mantine-spacing-xl);
- height: 100%;
- overflow: hidden;
- transform: rotate(10deg);
-}
-
-.track {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: var(--mantine-spacing-xl);
- animation: integrationMarquee var(--marquee-duration) linear infinite;
-}
-
-.iconFrame {
- display: grid;
- flex: 0 0 4.5rem;
- width: 4.5rem;
- height: 4.5rem;
- place-items: center;
- border: 1px solid color-mix(in srgb, var(--integration-marquee-accent) 24%, transparent);
- border-radius: var(--mantine-radius-lg);
- background: color-mix(in srgb, var(--integration-marquee-accent) 8%, var(--mantine-color-body));
- box-shadow: var(--mantine-shadow-sm);
-}
-
-@keyframes integrationMarquee {
- to {
- transform: translateY(-50%);
- }
+.marquee {
+ mask-image: linear-gradient(to bottom, transparent, black 14%, black 86%, transparent);
}
@media (prefers-reduced-motion: reduce) {
- .track {
+ .content {
animation: none;
}
}
diff --git a/packages/ui/src/components/integration-marquee.tsx b/packages/ui/src/components/integration-marquee.tsx
index e29ce87ef..f413b298a 100644
--- a/packages/ui/src/components/integration-marquee.tsx
+++ b/packages/ui/src/components/integration-marquee.tsx
@@ -1,5 +1,4 @@
-import type { CSSProperties } from "react";
-import { Image } from "@mantine/core";
+import { Image, Marquee, SimpleGrid } from "@mantine/core";
import { splitToNChunks } from "@homarr/common";
import { integrationDefs } from "@homarr/definitions";
@@ -15,19 +14,31 @@ const iconGroups = splitToNChunks(
const animationDuration = iconGroups.flat().length * 2;
export const IntegrationMarquee = ({ className }: { className?: string }) => (
-