mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-21 20:22:22 +02:00
[eric] update: an accent breadcrumb walks gear tile to Advanced row to a relaunch pill the moment an update is downloaded
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import { useAppSelector } from '@/shared/hooks';
|
||||
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
|
||||
import type { SxProps } from '@mui/material/styles';
|
||||
|
||||
/** The breadcrumb dot for a downloaded update: gear tile -> Advanced rail row -> the relaunch row. Renders nothing until the update is actually sitting on disk, so it can never nag about something a click cannot deliver. */
|
||||
const UpdateReadyDot: React.FC<{ size?: number; sx?: SxProps }> = ({ size = 8, sx }) => {
|
||||
const c = useClaudeTokens();
|
||||
const ready = useAppSelector((s) => s.update.status === 'downloaded' && !s.update.installing);
|
||||
if (!ready) return null;
|
||||
return (
|
||||
<Box
|
||||
aria-label="Update ready"
|
||||
sx={{
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: '50%',
|
||||
bgcolor: c.accent.primary,
|
||||
boxShadow: `0 0 0 2px ${c.bg.surface}, 0 0 6px ${c.accent.primary}80`,
|
||||
flexShrink: 0,
|
||||
pointerEvents: 'none',
|
||||
...sx,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateReadyDot;
|
||||
@@ -5,6 +5,7 @@ import { Globe, CalendarClock, Settings, LayoutGrid, Store } from 'lucide-react'
|
||||
import { useAppDispatch } from '@/shared/hooks';
|
||||
import { openSettingsCard, openWorkflowsApp } from '@/shared/state/dashboardLayoutSlice';
|
||||
import { openMarketplace } from '@/app/pages/Directory/openMarketplace';
|
||||
import UpdateReadyDot from '@/app/components/UpdateReadyDot';
|
||||
|
||||
// The dock reserves room for these before it knows what they are, so the count lives with the list.
|
||||
export const DOCK_ACTION_COUNT = 5;
|
||||
@@ -19,11 +20,11 @@ interface DockActionTilesProps {
|
||||
/** The dock's fixed group: browser, workflows, then settings + applications under their own divider. Action buttons are flat hairline glyphs resting on the rail material (running dock tiles above keep their colored tiles), so actions and running things read as different species. */
|
||||
function DockActionTiles({ tile, onAddBrowser, onApplications, onHoverAway }: DockActionTilesProps): React.ReactElement {
|
||||
const dispatch = useAppDispatch();
|
||||
const actions: { label: string; Icon: typeof Globe; act: () => void; divider?: boolean }[] = [
|
||||
const actions: { label: string; Icon: typeof Globe; act: () => void; divider?: boolean; updateDot?: boolean }[] = [
|
||||
{ label: 'Browsers', Icon: Globe, act: onAddBrowser },
|
||||
{ label: 'Workflows', Icon: CalendarClock, act: () => dispatch(openWorkflowsApp()) },
|
||||
{ label: 'Marketplace', Icon: Store, act: () => openMarketplace() },
|
||||
{ label: 'Settings', Icon: Settings, act: () => dispatch(openSettingsCard()), divider: true },
|
||||
{ label: 'Settings', Icon: Settings, act: () => dispatch(openSettingsCard()), divider: true, updateDot: true },
|
||||
{ label: 'Applications', Icon: LayoutGrid, act: onApplications },
|
||||
];
|
||||
|
||||
@@ -38,6 +39,7 @@ function DockActionTiles({ tile, onAddBrowser, onApplications, onHoverAway }: Do
|
||||
onClick={a.act}
|
||||
onMouseEnter={onHoverAway}
|
||||
sx={{
|
||||
position: 'relative',
|
||||
width: tile,
|
||||
height: tile,
|
||||
borderRadius: '10px',
|
||||
@@ -53,6 +55,7 @@ function DockActionTiles({ tile, onAddBrowser, onApplications, onHoverAway }: Do
|
||||
}}
|
||||
>
|
||||
<a.Icon size={19} strokeWidth={1.5} />
|
||||
{a.updateDot && <UpdateReadyDot sx={{ position: 'absolute', top: 6, right: 6 }} />}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</React.Fragment>
|
||||
|
||||
@@ -4,6 +4,7 @@ import Typography from '@mui/material/Typography';
|
||||
import { User, Settings2, Palette, ShieldCheck, Wrench, Boxes, SquareSlash, BarChart3, Bell, Mic, LayoutGrid, Bot, Brain } from 'lucide-react';
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
|
||||
import UpdateReadyDot from '@/app/components/UpdateReadyDot';
|
||||
|
||||
// ChatGPT/Apple-style settings nav: a left rail of short, focused sections instead of one giant
|
||||
// General scroll. Grouped so the eye lands (account, then app-wide, then capabilities).
|
||||
@@ -89,6 +90,7 @@ const SettingsRail: React.FC<{
|
||||
>
|
||||
<s.Icon size={16} style={{ flexShrink: 0, color: c.text.secondary }} />
|
||||
{s.label}
|
||||
{s.value === 'advanced' && <UpdateReadyDot size={7} sx={{ ml: 'auto' }} />}
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { onboardingBus } from '@/app/components/Onboarding/eventBus';
|
||||
import { resetTour } from '@/shared/state/onboardingProgressSlice';
|
||||
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
|
||||
import TrustedFilePatterns from '@/app/components/overlays/TrustedFilePatterns';
|
||||
import SoftwareUpdateRow from './SoftwareUpdateRow';
|
||||
import SoftwareUpdateRow, { UpdateReadyBanner } from './SoftwareUpdateRow';
|
||||
import type { SettingsStyles } from '../settingsStyles';
|
||||
import { settingSelectAttrs } from '../settingSelect';
|
||||
|
||||
@@ -36,6 +36,7 @@ const GeneralAdvanced: React.FC<{
|
||||
|
||||
return (
|
||||
<>
|
||||
<UpdateReadyBanner />
|
||||
|
||||
<Box sx={inlineRowSx} {...settingSelectAttrs('dev_mode', 'Developer mode', 'Advanced', 'Show transport details, env vars, and technical metadata throughout the app.')}>
|
||||
<Box sx={{ mr: 3 }}>
|
||||
|
||||
@@ -14,6 +14,49 @@ import { setChecking, setUpdateError, setInstalling } from '@/shared/state/updat
|
||||
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
|
||||
import type { SettingsStyles } from '../settingsStyles';
|
||||
|
||||
/** The breadcrumb's last stop: a pill-shaped relaunch row pinned to the top of Advanced while an update sits downloaded. */
|
||||
export const UpdateReadyBanner: React.FC = () => {
|
||||
const c = useClaudeTokens();
|
||||
const dispatch = useAppDispatch();
|
||||
const updateStatus = useAppSelector((s) => s.update.status);
|
||||
const availableVersion = useAppSelector((s) => s.update.availableVersion);
|
||||
const installing = useAppSelector((s) => s.update.installing);
|
||||
if (updateStatus !== 'downloaded') return null;
|
||||
const install = () => {
|
||||
if (installing) return;
|
||||
dispatch(setInstalling());
|
||||
(window as any).openswarm?.installUpdate();
|
||||
};
|
||||
return (
|
||||
<Box
|
||||
onClick={install}
|
||||
role="button"
|
||||
aria-label="Relaunch to update"
|
||||
sx={{
|
||||
display: 'flex', alignItems: 'center', gap: 1.25,
|
||||
px: 1.75, py: 1.25, mb: 2,
|
||||
borderRadius: '999px',
|
||||
border: `1px solid ${c.accent.primary}`,
|
||||
bgcolor: `${c.accent.primary}14`,
|
||||
cursor: installing ? 'default' : 'pointer',
|
||||
transition: 'background-color 0.15s ease',
|
||||
'&:hover': { bgcolor: `${c.accent.primary}22` },
|
||||
}}
|
||||
>
|
||||
<RestartAltIcon sx={{ fontSize: 18, color: c.accent.primary, flexShrink: 0 }} />
|
||||
<Box sx={{ minWidth: 0 }}>
|
||||
<Typography sx={{ fontSize: '0.875rem', fontWeight: 600, color: c.text.primary, lineHeight: 1.25 }}>
|
||||
{installing ? 'Relaunching…' : 'Relaunch to update'}
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: '0.75rem', color: c.text.tertiary }}>
|
||||
{availableVersion ? `Version ${availableVersion} is downloaded and ready.` : 'The update is downloaded and ready.'}
|
||||
</Typography>
|
||||
</Box>
|
||||
{installing && <CircularProgress size={16} sx={{ color: c.accent.primary, ml: 'auto', flexShrink: 0 }} />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const SoftwareUpdateRow: React.FC<{ styles: SettingsStyles }> = ({ styles }) => {
|
||||
const c = useClaudeTokens();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
Reference in New Issue
Block a user