This commit is contained in:
Stijnus
2025-01-24 01:08:51 +01:00
parent 4e6f18ea1e
commit 27eab591a9
19 changed files with 1759 additions and 592 deletions

View File

@@ -1,7 +1,7 @@
import * as RadixDialog from '@radix-ui/react-dialog';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import { motion } from 'framer-motion';
import { useState, useEffect } from 'react';
import React, { useState, useEffect, useMemo } from 'react';
import { classNames } from '~/utils/classNames';
import { DialogTitle } from '~/components/ui/Dialog';
import { Switch } from '~/components/ui/Switch';
@@ -9,7 +9,6 @@ import type { TabType, TabVisibilityConfig } from '~/components/settings/setting
import { TAB_LABELS } from '~/components/settings/settings.types';
import { DeveloperWindow } from '~/components/settings/developer/DeveloperWindow';
import { TabTile } from '~/components/settings/shared/TabTile';
import { tabConfigurationStore, updateTabConfiguration } from '~/lib/stores/settings';
import { useStore } from '@nanostores/react';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
@@ -30,6 +29,13 @@ import { useDebugStatus } from '~/lib/hooks/useDebugStatus';
import CloudProvidersTab from '~/components/settings/providers/CloudProvidersTab';
import LocalProvidersTab from '~/components/settings/providers/LocalProvidersTab';
import TaskManagerTab from '~/components/settings/task-manager/TaskManagerTab';
import {
tabConfigurationStore,
resetTabConfiguration,
updateTabConfiguration,
developerModeStore,
setDeveloperMode,
} from '~/lib/stores/settings';
interface DraggableTabTileProps {
tab: TabVisibilityConfig;
@@ -89,8 +95,14 @@ const DraggableTabTile = ({
},
});
const dragDropRef = (node: HTMLDivElement | null) => {
if (node) {
drag(drop(node));
}
};
return (
<div ref={(node) => drag(drop(node))} style={{ opacity: isDragging ? 0.5 : 1 }}>
<div ref={dragDropRef} style={{ opacity: isDragging ? 0.5 : 1 }}>
<TabTile
tab={tab}
onClick={onClick}
@@ -110,10 +122,15 @@ interface UsersWindowProps {
}
export const UsersWindow = ({ open, onClose }: UsersWindowProps) => {
const [developerMode, setDeveloperMode] = useState(false);
const [activeTab, setActiveTab] = useState<TabType | null>(null);
const [loadingTab, setLoadingTab] = useState<TabType | null>(null);
const tabConfiguration = useStore(tabConfigurationStore);
const developerMode = useStore(developerModeStore);
const [showDeveloperWindow, setShowDeveloperWindow] = useState(false);
const [profile, setProfile] = useState(() => {
const saved = localStorage.getItem('bolt_user_profile');
return saved ? JSON.parse(saved) : { avatar: null, notifications: true };
});
// Status hooks
const { hasUpdate, currentVersion, acknowledgeUpdate } = useUpdateCheck();
@@ -122,11 +139,7 @@ export const UsersWindow = ({ open, onClose }: UsersWindowProps) => {
const { hasConnectionIssues, currentIssue, acknowledgeIssue } = useConnectionStatus();
const { hasActiveWarnings, activeIssues, acknowledgeAllIssues } = useDebugStatus();
const [profile, setProfile] = useState(() => {
const saved = localStorage.getItem('bolt_user_profile');
return saved ? JSON.parse(saved) : { avatar: null, notifications: true };
});
// Listen for profile changes
useEffect(() => {
const handleStorageChange = (e: StorageEvent) => {
if (e.key === 'bolt_user_profile') {
@@ -140,8 +153,66 @@ export const UsersWindow = ({ open, onClose }: UsersWindowProps) => {
return () => window.removeEventListener('storage', handleStorageChange);
}, []);
// Listen for settings toggle event
useEffect(() => {
const handleToggleSettings = () => {
if (!open) {
// Open settings panel
setActiveTab('settings');
onClose(); // Close any other open panels
}
};
document.addEventListener('toggle-settings', handleToggleSettings);
return () => document.removeEventListener('toggle-settings', handleToggleSettings);
}, [open, onClose]);
// Ensure tab configuration is properly initialized
useEffect(() => {
if (!tabConfiguration || !tabConfiguration.userTabs || !tabConfiguration.developerTabs) {
console.warn('Tab configuration is invalid, resetting to defaults');
resetTabConfiguration();
} else {
// Validate tab configuration structure
const isValid =
tabConfiguration.userTabs.every(
(tab) =>
tab &&
typeof tab.id === 'string' &&
typeof tab.visible === 'boolean' &&
typeof tab.window === 'string' &&
typeof tab.order === 'number',
) &&
tabConfiguration.developerTabs.every(
(tab) =>
tab &&
typeof tab.id === 'string' &&
typeof tab.visible === 'boolean' &&
typeof tab.window === 'string' &&
typeof tab.order === 'number',
);
if (!isValid) {
console.warn('Tab configuration is malformed, resetting to defaults');
resetTabConfiguration();
}
}
}, [tabConfiguration]);
// Handle developer mode changes
const handleDeveloperModeChange = (checked: boolean) => {
setDeveloperMode(checked);
if (checked) {
setShowDeveloperWindow(true);
}
};
// Handle developer window close
const handleDeveloperWindowClose = () => {
setShowDeveloperWindow(false);
setDeveloperMode(false);
};
const handleBack = () => {
@@ -149,21 +220,55 @@ export const UsersWindow = ({ open, onClose }: UsersWindowProps) => {
};
// Only show tabs that are assigned to the user window AND are visible
const visibleUserTabs = tabConfiguration.userTabs
.filter((tab) => {
// Hide notifications tab if notifications are disabled
if (tab.id === 'notifications' && !profile.notifications) {
return false;
}
const visibleUserTabs = useMemo(() => {
console.log('Filtering user tabs with configuration:', tabConfiguration);
return tab.visible;
})
.sort((a: TabVisibilityConfig, b: TabVisibilityConfig) => (a.order || 0) - (b.order || 0));
if (!tabConfiguration?.userTabs || !Array.isArray(tabConfiguration.userTabs)) {
console.warn('Invalid tab configuration, using empty array');
return [];
}
return tabConfiguration.userTabs
.filter((tab) => {
if (!tab || typeof tab.id !== 'string') {
console.warn('Invalid tab entry:', tab);
return false;
}
// Hide notifications tab if notifications are disabled
if (tab.id === 'notifications' && !profile.notifications) {
console.log('Hiding notifications tab due to disabled notifications');
return false;
}
// Ensure the tab has the required properties
if (typeof tab.visible !== 'boolean' || typeof tab.window !== 'string' || typeof tab.order !== 'number') {
console.warn('Tab missing required properties:', tab);
return false;
}
// Only show tabs that are explicitly visible and assigned to the user window
const isVisible = tab.visible && tab.window === 'user';
console.log(`Tab ${tab.id} visibility:`, isVisible);
return isVisible;
})
.sort((a: TabVisibilityConfig, b: TabVisibilityConfig) => {
const orderA = typeof a.order === 'number' ? a.order : 0;
const orderB = typeof b.order === 'number' ? b.order : 0;
return orderA - orderB;
});
}, [tabConfiguration, profile.notifications]);
console.log('Filtered visible user tabs:', visibleUserTabs);
const moveTab = (dragIndex: number, hoverIndex: number) => {
const draggedTab = visibleUserTabs[dragIndex];
const targetTab = visibleUserTabs[hoverIndex];
console.log('Moving tab:', { draggedTab, targetTab });
// Update the order of the dragged and target tabs
const updatedDraggedTab = { ...draggedTab, order: targetTab.order };
const updatedTargetTab = { ...targetTab, order: draggedTab.order };
@@ -310,7 +415,7 @@ export const UsersWindow = ({ open, onClose }: UsersWindowProps) => {
className="data-[state=checked]:bg-purple-500"
aria-label="Toggle developer mode"
/>
<label className="text-sm text-gray-500 dark:text-gray-400">Developer Mode</label>
<label className="text-sm text-gray-500 dark:text-gray-400">Switch to Developer Mode</label>
</div>
<DropdownMenu.Root>
@@ -412,9 +517,9 @@ export const UsersWindow = ({ open, onClose }: UsersWindowProps) => {
return (
<>
<DeveloperWindow open={developerMode} onClose={() => setDeveloperMode(false)} />
<DeveloperWindow open={showDeveloperWindow} onClose={handleDeveloperWindowClose} />
<DndProvider backend={HTML5Backend}>
<RadixDialog.Root open={open}>
<RadixDialog.Root open={open && !showDeveloperWindow}>
<RadixDialog.Portal>
<div className="fixed inset-0 flex items-center justify-center z-[50]">
<RadixDialog.Overlay asChild>