widgets
This commit is contained in:
@@ -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
-3
@@ -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 (0–100)
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
+3
-2
@@ -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,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';
|
||||
|
||||
@@ -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 },
|
||||
];
|
||||
|
||||
@@ -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 },
|
||||
];
|
||||
|
||||
@@ -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 },
|
||||
];
|
||||
|
||||
@@ -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 },
|
||||
];
|
||||
|
||||
@@ -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 },
|
||||
};
|
||||
Reference in New Issue
Block a user