feat: finish the homepage plugin, move service links and canvas out of core (B19)

This commit is contained in:
LukeGus committed 2026-09-24 15:07:25 -05:00
1 parent 21937a6b02
commit d4ec444558
256 files changed
+27365 -9077

No files matched your search

+6 -4
View File
@@ -23,7 +23,7 @@ import {
registerDashboardCard,
type DashboardCardRenderProps,
} from "@/dashboard/dashboard-cards-registry";
import { registerWidget } from "@/features/homepage/widgets/WidgetRegistry";
import { registerHomepageWidgetType } from "./homepage-widget-registry";
import { registerSettingsComponent } from "@/settings/settings-components";
import {
declareActionSlot,
@@ -33,7 +33,7 @@ import {
} from "@/shell/action-registry";
import { pluginApiFor, pluginFetch, pluginWsUrl } from "@/lib/plugin-transport";
import { registerPluginComponent } from "./component-registry";
import type { WidgetTypeDefinition } from "@/types/homepage-types";
import type { RegisteredHomepageWidget } from "./homepage-widget-registry";
import type { LucideIcon } from "lucide-react";
import {
registerLoginMethod,
@@ -265,6 +265,7 @@ export function createPluginApp(
pluginId,
titleKey: key(card.titleKey),
defaultHeight: card.defaultHeight,
defaultPanel: card.defaultPanel,
component: scoped(
card.component as unknown as ComponentType<DashboardCardRenderProps>,
),
@@ -274,13 +275,14 @@ export function createPluginApp(
registerHomepageWidget(widget) {
return track(
registerWidget({
registerHomepageWidgetType({
...widget,
pluginId,
component: scoped(widget.component),
editFormComponent: widget.editFormComponent
? scoped(widget.editFormComponent)
: undefined,
} as unknown as WidgetTypeDefinition),
} as unknown as RegisteredHomepageWidget),
);
},
+31
View File
@@ -1,9 +1,17 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import { enabledHostProtocols } from "@/sidebar/host-protocols";
import { useHostActions } from "@/sidebar/host-contributions";
import { tabTypeForActivity, useTabTypes } from "@/shell/tab-registry";
import {
getHomepageWidgetType,
useHomepageWidgetTypes,
} from "./homepage-widget-registry";
import { useTranslation as useI18nTranslation } from "react-i18next";
import { toast } from "sonner";
import {
__setPluginHost,
type HomepageWidgetContribution,
type HostActionContribution,
type PluginHostBridge,
type PluginHostRecord,
type SettingsScope,
@@ -294,6 +302,29 @@ export const pluginHostBridge: PluginHostBridge = {
...(entry.reason ? { reason: entry.reason } : {}),
};
},
useHostActions: () => useHostActions() as unknown as HostActionContribution[],
useActivityTypes: () => {
const defs = useTabTypes();
const types = new Set(["file_manager"]);
for (const def of defs) {
for (const type of def.activityTypes ?? []) types.add(type);
}
return [...types];
},
activityTarget: (type) => {
const def = tabTypeForActivity(type);
if (!def) return undefined;
return { icon: def.icon, tab: def.id, titleKey: def.titleKey };
},
useHomepageWidgetTypes: () =>
useHomepageWidgetTypes() as unknown as HomepageWidgetContribution[],
homepageWidgetType: (id) =>
getHomepageWidgetType(id) as unknown as
HomepageWidgetContribution | undefined,
};
/** Installs the bridge. Idempotent. */
@@ -0,0 +1,33 @@
import type { ComponentType } from "react";
import { createRegistry } from "@/lib/registry";
/**
* A homepage widget type any plugin contributes through
* app.registerHomepageWidget. Core owns this registry so the homepage
* plugin's canvas can read it without every widget-contributing plugin
* depending on homepage being enabled.
*/
export interface RegisteredHomepageWidget {
id: string;
pluginId?: string;
name: string;
description: string;
category: "links" | "info" | "system" | "monitoring";
icon: React.ReactNode;
defaultConfig: Record<string, unknown>;
defaultSize: { w: number; h: number };
minSize: { w: number; h: number };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
component: ComponentType<any>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
editFormComponent?: ComponentType<any>;
}
const registry = createRegistry<RegisteredHomepageWidget>();
export const registerHomepageWidgetType = registry.register;
export const unregisterHomepageWidgetType = registry.unregister;
export const getHomepageWidgetType = registry.get;
export const listHomepageWidgetTypes = registry.list;
export const useHomepageWidgetTypes = registry.useList;
export const resetHomepageWidgetTypes = registry.reset;
+15 -3
View File
@@ -74,6 +74,7 @@ export { ActionSlot, ComponentSlot } from "@/shell/ActionSlot";
// Components other plugins offer by id (app.registerComponent), rendered with
// a fallback while their plugin is off.
export { PluginComponent } from "@/plugin-host/component-registry";
export { PluginViewPlaceholder } from "@/plugin-host/PluginViewPlaceholder";
// More primitives and hooks.
export { cn } from "@/lib/utils";
@@ -90,8 +91,8 @@ export {
useUiPreferencesContext,
} from "@/contexts/UiPreferencesContext";
// Homepage widget pieces, for plugins that register a widget.
export { WidgetTitle } from "@/features/homepage/widgets/WidgetTitle";
export { runVisibleInterval } from "@/features/homepage/use-visible-interval";
export { WidgetTitle } from "@/lib/widget-title";
export { runVisibleInterval } from "@/lib/visible-interval";
export { useIsMobile } from "@/hooks/use-mobile";
export { PassphraseDialog } from "@/ssh/dialogs/PassphraseDialog";
export { OPKSSHDialog } from "@/ssh/dialogs/OPKSSHDialog";
@@ -138,7 +139,18 @@ export { useConnectionDefaults } from "@/contexts/ConnectionDefaultsContext";
// password for sudo autofill, the open tab record, the user's keybindings and
// host terminal options.
export { getCookie } from "@/main-axios";
export { logActivity } from "@/api/dashboard-api";
export {
logActivity,
getRecentActivity,
getUptime,
type RecentActivityItem,
type UptimeInfo,
} from "@/api/dashboard-api";
export {
getVersionInfo,
getDatabaseHealth,
type VersionInfo,
} from "@/api/system-status-api";
export { getHostPassword } from "@/api/credentials-api";
export {
patchOpenTab,