This commit is contained in:
2026-02-23 05:49:33 +00:00
parent a601b8e078
commit 46c3b6b71d
17 changed files with 98 additions and 27 deletions
+2 -1
View File
@@ -1,7 +1,7 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './App';
import { AppRegistry } from 'officerdev';
import { AppRegistry, WidgetRegistry } from 'officerdev';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ColorModeProvider } from '@/components/ui/ThemeProvider';
import { I18nBridge } from '@/lib/I18nBridge';
@@ -41,6 +41,7 @@ const app = (
<Toaster />
<Sonner position="top-center" toastOptions={{ style: { padding: '16px 20px', fontSize: '16px' } }} />
<AppRegistry />
<WidgetRegistry />
<App />
</TooltipProvider>
</I18nBridge>
@@ -7,9 +7,10 @@ import { appRegistryMetas as workspaceMetas } from '../apps/Workspaces';
import { appRegistryMetas as projectMetas } from '../apps/Projects';
import { appRegistryMetas as chatHistoryMetas } from '../apps/ChatHistory';
import { appRegistryMetas as previewMetas } from '../apps/Preview';
import { appRegistryMetas as widgetMetas } from '../apps/Widgets';
import { useAppRegistry } from './useAppRegistry';
const apps = [...fileBrowserMetas, ...terminalMetas, ...codeEditorMetas, ...chatMetas, ...fileViewerMetas, ...workspaceMetas, ...projectMetas, ...chatHistoryMetas, ...previewMetas];
const apps = [...fileBrowserMetas, ...terminalMetas, ...codeEditorMetas, ...chatMetas, ...fileViewerMetas, ...workspaceMetas, ...projectMetas, ...chatHistoryMetas, ...previewMetas, ...widgetMetas];
export const AppRegistry = () => {
useAppRegistry(apps);
@@ -0,0 +1,13 @@
import { widgetRegistryMetas as clockMetas } from 'widgets/Clock';
import { widgetRegistryMetas as weatherMetas } from 'widgets/Weather';
import { widgetRegistryMetas as pomodoroMetas } from 'widgets/Pomodoro';
import { widgetRegistryMetas as dailyGoalsMetas } from 'widgets/DailyGoals';
import { widgetRegistryMetas as quickNotesMetas } from 'widgets/QuickNotes';
import { useWidgetRegistry } from './useWidgetRegistry';
const widgets = [...clockMetas, ...weatherMetas, ...pomodoroMetas, ...dailyGoalsMetas, ...quickNotesMetas];
export const WidgetRegistry = () => {
useWidgetRegistry(widgets);
return null;
};
@@ -0,0 +1,3 @@
export * from './WidgetRegistry';
export * from './useWidgetRegistry';
export type { WidgetRegistryEntry } from './types';
@@ -0,0 +1,10 @@
import type { ComponentType } from 'react';
import type { LucideIcon } from 'lucide-react';
export type WidgetRegistryEntry = {
name: string;
icon: LucideIcon;
component: ComponentType;
};
export type WidgetRegistry = Record<string, WidgetRegistryEntry>;
@@ -0,0 +1,17 @@
import type { WidgetRegistry, WidgetRegistryEntry } from './types';
import { useGlobal } from 'hooks/useGlobal';
export type WidgetRegistryMeta = { key: string } & WidgetRegistryEntry;
export function useWidgetRegistry(initialWidgets: WidgetRegistryMeta[] = []) {
const [registry, setRegistry] = useGlobal<WidgetRegistry>('WIDGET_REGISTRY', () => metasToRegistry(initialWidgets));
const registerWidget = (key: string, entry: WidgetRegistryEntry) => {
setRegistry((prev) => ({ ...prev, [key]: entry }));
};
return { registry, registerWidget };
}
const metasToRegistry = (metas: WidgetRegistryMeta[]): WidgetRegistry =>
Object.fromEntries(metas.map(({ key, ...entry }) => [key, entry]));
@@ -4,7 +4,7 @@ import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useClient } from 'hooks/useClient';
import { useAuth } from 'hooks/useAuth';
import { Plus, X } from 'lucide-react';
import { widgetRegistry } from '../widget-registry';
import { useWidgetRegistry } from '../../WidgetRegistry';
import { WidgetPicker } from './WidgetPicker';
type Position = { x: number; y: number }; // percentages (0100)
@@ -78,6 +78,7 @@ type DragState = {
const DraggableWidget = ({ instance, panelRef, onMove, onRemove }: DraggableWidgetProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const dragRef = useRef<DragState | null>(null);
const { registry } = useWidgetRegistry();
const onPointerDown = useCallback(
(ev: ReactPointerEvent) => {
@@ -136,7 +137,7 @@ const DraggableWidget = ({ instance, panelRef, onMove, onRemove }: DraggableWidg
[endDrag],
);
const entry = widgetRegistry[instance.widgetType];
const entry = registry[instance.widgetType];
if (!entry) return null;
const WidgetComponent = entry.component;
@@ -156,7 +157,7 @@ const DraggableWidget = ({ instance, panelRef, onMove, onRemove }: DraggableWidg
>
<X size={12} />
</button>
<WidgetComponent panelId={instance.id} />
<WidgetComponent />
</div>
);
};
@@ -1,11 +1,12 @@
import { widgetRegistry } from '../widget-registry';
import { useWidgetRegistry } from '../../WidgetRegistry';
type WidgetPickerProps = {
onSelect: (widgetType: string) => void;
};
export const WidgetPicker = ({ onSelect }: WidgetPickerProps) => {
const entries = Object.entries(widgetRegistry);
const { registry } = useWidgetRegistry();
const entries = Object.entries(registry);
return (
<div className="grid grid-cols-3 gap-2 max-w-xs">
@@ -0,0 +1,13 @@
import type { AppRegistryMeta } from '../../AppRegistry';
import { LayoutGrid } from 'lucide-react';
import { WidgetPanel } from './WidgetPanel';
export const appRegistryMetas: AppRegistryMeta[] = [
{
key: 'officerdev/widgets',
name: 'Widgets',
icon: LayoutGrid,
component: WidgetPanel,
transparent: true,
},
];
+1
View File
@@ -1,5 +1,6 @@
export * from './hooks';
export * from './AppRegistry';
export * from './WidgetRegistry';
// Re-export app modules (excluding appRegistryMetas to avoid name collisions)
export { MessageList, MessageBubble, StreamingBubble, ToolActivity, QuestionActivity, ModelSelector, InputArea, ChatLauncher, AttachmentList, AttachButton, WebpageDialog, EmbeddableChat, usePiChat, ChatList, useSlashCommands, useChatSessions, useChatSession, useAttachments, useAudioRecording } from './apps/Chat';
+6
View File
@@ -1,4 +1,6 @@
import type { WidgetRegistryMeta } from 'officerdev';
import { useState, useEffect } from 'react';
import { Clock as ClockIcon } from 'lucide-react';
import { Widget } from '../Widget';
export const Clock = () => {
@@ -21,3 +23,7 @@ export const Clock = () => {
</Widget>
);
};
export const widgetRegistryMetas: WidgetRegistryMeta[] = [
{ key: 'clock', name: 'Clock', icon: ClockIcon, component: Clock },
];
+6 -1
View File
@@ -1,8 +1,9 @@
import type { WidgetRegistryMeta } from 'officerdev';
import { useCallback, useRef, useState } from 'react';
import { useQueryClient, useQuery } from '@tanstack/react-query';
import { useClient } from 'hooks/useClient';
import { useAuth } from 'hooks/useAuth';
import { Plus, Trash2, Flame } from 'lucide-react';
import { Plus, Target, Trash2, Flame } from 'lucide-react';
import { Widget } from '../Widget';
type Goal = {
@@ -188,3 +189,7 @@ export const DailyGoals = () => {
</Widget>
);
};
export const widgetRegistryMetas: WidgetRegistryMeta[] = [
{ key: 'daily-goals', name: 'Daily Goals', icon: Target, component: DailyGoals },
];
+6 -1
View File
@@ -1,8 +1,9 @@
import type { WidgetRegistryMeta } from 'officerdev';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useQueryClient, useQuery } from '@tanstack/react-query';
import { useClient } from 'hooks/useClient';
import { useAuth } from 'hooks/useAuth';
import { Pause, Play, RotateCcw, Settings, Check, Volume2, Square } from 'lucide-react';
import { Pause, Play, RotateCcw, Settings, Check, Timer, Volume2, Square } from 'lucide-react';
import { allSounds, categories } from 'sounds';
import type { SoundAsset } from 'sounds';
import { Widget } from '../Widget';
@@ -412,3 +413,7 @@ export const Pomodoro = () => {
</Widget>
);
};
export const widgetRegistryMetas: WidgetRegistryMeta[] = [
{ key: 'pomodoro', name: 'Pomodoro', icon: Timer, component: Pomodoro },
];
@@ -1,7 +1,9 @@
import type { WidgetRegistryMeta } from 'officerdev';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useQueryClient, useQuery } from '@tanstack/react-query';
import { useClient } from 'hooks/useClient';
import { useAuth } from 'hooks/useAuth';
import { StickyNote } from 'lucide-react';
import { Widget } from '../Widget';
const USER_STATE_KEY = ['USER_STATE'];
@@ -76,3 +78,7 @@ export const QuickNotes = () => {
</Widget>
);
};
export const widgetRegistryMetas: WidgetRegistryMeta[] = [
{ key: 'quick-notes', name: 'Quick Notes', icon: StickyNote, component: QuickNotes },
];
+6 -1
View File
@@ -1,8 +1,9 @@
import type { WidgetRegistryMeta } from 'officerdev';
import { useCallback, useRef, useState } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useClient } from 'hooks/useClient';
import { useAuth } from 'hooks/useAuth';
import { MapPin, Settings } from 'lucide-react';
import { CloudSun, MapPin, Settings } from 'lucide-react';
import { Widget } from '../Widget';
import { getWeatherInfo } from './weather-codes';
@@ -266,3 +267,7 @@ export const Weather = () => {
</Widget>
);
};
export const widgetRegistryMetas: WidgetRegistryMeta[] = [
{ key: 'weather', name: 'Weather', icon: CloudSun, component: Weather },
];
-2
View File
@@ -8,8 +8,6 @@
"./Pomodoro": "./Pomodoro/index.tsx",
"./DailyGoals": "./DailyGoals/index.tsx",
"./QuickNotes": "./QuickNotes/index.tsx",
"./widget-registry": "./widget-registry.tsx",
"./WidgetPanel": "./WidgetPanel/index.tsx",
"./Workspaces": "./Workspaces/index.tsx"
}
}
@@ -1,15 +0,0 @@
import { Clock as ClockIcon, CloudSun, Timer, Target, StickyNote } from 'lucide-react';
import type { AppRegistryEntry } from 'officerdev';
import { Clock } from './Clock/index';
import { Weather } from './Weather/index';
import { Pomodoro } from './Pomodoro/index';
import { DailyGoals } from './DailyGoals/index';
import { QuickNotes } from './QuickNotes/index';
export const widgetRegistry: Record<string, AppRegistryEntry> = {
'clock': { name: 'Clock', icon: ClockIcon, component: () => <Clock />, availableOnPanel: false },
'weather': { name: 'Weather', icon: CloudSun, component: () => <Weather />, availableOnPanel: false },
'pomodoro': { name: 'Pomodoro', icon: Timer, component: () => <Pomodoro />, availableOnPanel: false },
'daily-goals': { name: 'Daily Goals', icon: Target, component: () => <DailyGoals />, availableOnPanel: false },
'quick-notes': { name: 'Quick Notes', icon: StickyNote, component: () => <QuickNotes />, availableOnPanel: false },
};