Compare commits

..

No commits in common. "d29e306e6119adf2c2113161db49520ec3ce60d6" and "45b1903e6b3619b861936795cca75fd5e417ffde" have entirely different histories.

13 changed files with 60 additions and 93 deletions

View File

@ -2,14 +2,13 @@ import { define } from '@because/forge'
import type { App } from '../../shared/types'
import { buildAppUrl } from '../../shared/urls'
import { restartApp, shareApp, startApp, stopApp, unshareApp } from '../api'
import { openDeleteAppModal, openRenameAppModal } from '../modals'
import { apps, getSelectedTab, isNarrow, setMobileSidebar } from '../state'
import { openAppSelectorModal, openDeleteAppModal, openRenameAppModal } from '../modals'
import { apps, getSelectedTab, isNarrow } from '../state'
import {
ActionBar,
AppSelectorChevron,
Button,
ClickableAppName,
HamburgerButton,
HamburgerLine,
HeaderActions,
InfoLabel,
InfoRow,
@ -53,15 +52,14 @@ export function AppDetail({ app, render }: { app: App, render: () => void }) {
<Main>
<MainHeader>
<MainTitle>
{isNarrow && (
<HamburgerButton onClick={() => { setMobileSidebar(true); render() }} title="Show apps">
<HamburgerLine />
<HamburgerLine />
<HamburgerLine />
</HamburgerButton>
)}
<OpenEmojiPicker app={app} render={render}>{app.icon}</OpenEmojiPicker>
&nbsp;
<ClickableAppName onClick={() => openRenameAppModal(app)}>{app.name}</ClickableAppName>
{isNarrow && (
<AppSelectorChevron onClick={() => openAppSelectorModal(render)}>
</AppSelectorChevron>
)}
</MainTitle>
<HeaderActions>
{!app.tool && (

View File

@ -17,12 +17,11 @@ interface AppSelectorProps {
render: () => void
onSelect?: () => void
collapsed?: boolean
large?: boolean
switcherStyle?: CSSProperties
listStyle?: CSSProperties
}
export function AppSelector({ render, onSelect, collapsed, large, switcherStyle, listStyle }: AppSelectorProps) {
export function AppSelector({ render, onSelect, collapsed, switcherStyle, listStyle }: AppSelectorProps) {
const switchSection = (section: 'apps' | 'tools') => {
setSidebarSection(section)
render()
@ -36,10 +35,10 @@ export function AppSelector({ render, onSelect, collapsed, large, switcherStyle,
<>
{!collapsed && toolApps.length > 0 && (
<SectionSwitcher style={switcherStyle}>
<SectionTab active={sidebarSection === 'apps' ? true : undefined} large={large || undefined} onClick={() => switchSection('apps')}>
<SectionTab active={sidebarSection === 'apps' ? true : undefined} onClick={() => switchSection('apps')}>
Apps
</SectionTab>
<SectionTab active={sidebarSection === 'tools' ? true : undefined} large={large || undefined} onClick={() => switchSection('tools')}>
<SectionTab active={sidebarSection === 'tools' ? true : undefined} onClick={() => switchSection('tools')}>
Tools
</SectionTab>
</SectionSwitcher>
@ -60,7 +59,6 @@ export function AppSelector({ render, onSelect, collapsed, large, switcherStyle,
key={app.name}
href={`/app/${app.name}`}
onClick={onSelect}
large={large || undefined}
selected={app.name === selectedApp ? true : undefined}
style={collapsed ? { justifyContent: 'center', padding: '10px 12px' } : undefined}
title={collapsed ? app.name : undefined}
@ -69,7 +67,7 @@ export function AppSelector({ render, onSelect, collapsed, large, switcherStyle,
<span style={{ fontSize: 18 }}>{app.icon}</span>
) : (
<>
<span style={{ fontSize: large ? 20 : 14 }}>{app.icon}</span>
<span style={{ fontSize: 14 }}>{app.icon}</span>
{app.name}
<StatusDot state={app.state} data-app={app.name} data-state={app.state} style={{ marginLeft: 'auto' }} />
</>

View File

@ -1,48 +1,13 @@
import { Styles } from '@because/forge'
import { openNewAppModal } from '../modals'
import { apps, currentView, isNarrow, mobileSidebar, selectedApp, setMobileSidebar } from '../state'
import {
HamburgerButton,
HamburgerLine,
Layout,
Main,
MainContent as MainContentContainer,
MainHeader,
MainTitle,
NewAppButton,
} from '../styles'
import { apps, currentView, isNarrow, selectedApp } from '../state'
import { Layout } from '../styles'
import { AppDetail } from './AppDetail'
import { AppSelector } from './AppSelector'
import { DashboardLanding } from './DashboardLanding'
import { Modal } from './modal'
import { SettingsPage } from './SettingsPage'
import { Sidebar } from './Sidebar'
function MobileSidebar({ render }: { render: () => void }) {
return (
<Main>
<MainHeader>
<MainTitle>
<HamburgerButton onClick={() => { setMobileSidebar(false); render() }} title="Hide apps">
<HamburgerLine />
<HamburgerLine />
<HamburgerLine />
</HamburgerButton>
<a href="/" style={{ textDecoration: 'none', color: 'inherit' }}>🐾 Toes</a>
</MainTitle>
</MainHeader>
<MainContentContainer>
<AppSelector render={render} large />
<div style={{ padding: '12px 16px' }}>
<NewAppButton onClick={openNewAppModal}>+ New App</NewAppButton>
</div>
</MainContentContainer>
</Main>
)
}
function MainContent({ render }: { render: () => void }) {
if (isNarrow && mobileSidebar) return <MobileSidebar render={render} />
const selected = apps.find(a => a.name === selectedApp)
if (selected) return <AppDetail app={selected} render={render} />
if (currentView === 'settings') return <SettingsPage render={render} />

View File

@ -1,9 +1,9 @@
import { useEffect } from 'hono/jsx'
import { openAppSelectorModal } from '../modals'
import { navigate } from '../router'
import { dashboardTab, isNarrow, setMobileSidebar } from '../state'
import { dashboardTab, isNarrow } from '../state'
import {
HamburgerButton,
HamburgerLine,
AppSelectorChevron,
DashboardContainer,
DashboardHeader,
DashboardTitle,
@ -43,20 +43,14 @@ export function DashboardLanding({ render }: { render: () => void }) {
>
</SettingsGear>
{isNarrow && (
<HamburgerButton
onClick={() => { setMobileSidebar(true); render() }}
title="Show apps"
style={{ position: 'absolute', top: 16, left: 16 }}
>
<HamburgerLine />
<HamburgerLine />
<HamburgerLine />
</HamburgerButton>
)}
<DashboardHeader>
<DashboardTitle narrow={narrow}>
🐾 Toes
{isNarrow && (
<AppSelectorChevron onClick={() => openAppSelectorModal(render)}>
</AppSelectorChevron>
)}
</DashboardTitle>
</DashboardHeader>

View File

@ -1,6 +1,6 @@
import { buildAppUrl } from '../../shared/urls'
import { navigate } from '../router'
import { apps, isNarrow } from '../state'
import { apps } from '../state'
import {
EmptyState,
Tile,
@ -19,7 +19,7 @@ export function Urls({ render }: { render: () => void }) {
}
return (
<TileGrid narrow={isNarrow || undefined}>
<TileGrid>
{nonTools.map(app => {
const url = buildAppUrl(app.name, location.origin)
const running = app.state === 'running'
@ -36,7 +36,6 @@ export function Urls({ render }: { render: () => void }) {
key={app.name}
href={running ? url : appPage}
target={running ? '_blank' : undefined}
narrow={isNarrow || undefined}
>
<TileStatus state={app.state} onClick={openAppPage} />
<TileIcon>{app.icon}</TileIcon>

View File

@ -0,0 +1,17 @@
import { closeModal, openModal } from '../components/modal'
import { AppSelector } from '../components/AppSelector'
let renderFn: () => void
export function openAppSelectorModal(render: () => void) {
renderFn = render
openModal('Select App', () => (
<AppSelector
render={renderFn}
onSelect={closeModal}
switcherStyle={{ padding: '0 0 12px', marginLeft: -20, marginRight: -20, paddingLeft: 20, paddingRight: 20, marginBottom: 8 }}
listStyle={{ maxHeight: 300, overflow: 'auto' }}
/>
))
}

View File

@ -1,3 +1,4 @@
export { openAppSelectorModal } from './AppSelector'
export { openDeleteAppModal } from './DeleteApp'
export { openNewAppModal } from './NewApp'
export { openRenameAppModal } from './RenameApp'

View File

@ -1,4 +1,4 @@
import { setCurrentView, setDashboardTab, setMobileSidebar, setSelectedApp, setSelectedTab } from './state'
import { setCurrentView, setDashboardTab, setSelectedApp, setSelectedTab } from './state'
let _render: () => void
@ -28,7 +28,6 @@ export function initRouter(render: () => void) {
}
function route() {
setMobileSidebar(false)
const path = location.pathname
if (path.startsWith('/app/')) {

View File

@ -8,7 +8,6 @@ export let isNarrow: boolean = window.matchMedia('(max-width: 768px)').matches
export let selectedApp: string | null = null
export let sidebarCollapsed: boolean = localStorage.getItem('sidebarCollapsed') === 'true'
export let dashboardTab: DashboardTab = 'urls'
export let mobileSidebar: boolean = false
export let sidebarSection: 'apps' | 'tools' = (localStorage.getItem('sidebarSection') as 'apps' | 'tools') || 'apps'
// Server state (from SSE)
@ -34,10 +33,6 @@ export function setIsNarrow(narrow: boolean) {
isNarrow = narrow
}
export function setMobileSidebar(open: boolean) {
mobileSidebar = open
}
export function setSidebarCollapsed(collapsed: boolean) {
sidebarCollapsed = collapsed
localStorage.setItem('sidebarCollapsed', String(collapsed))

View File

@ -65,6 +65,7 @@ export const GaugeValue = define('GaugeValue', {
// Unified Logs Section
export const LogsSection = define('LogsSection', {
width: '100%',
maxWidth: 800,
flex: 1,
minHeight: 0,
display: 'flex',
@ -209,9 +210,6 @@ export const TileGrid = define('TileGrid', {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))',
gap: 20,
variants: {
narrow: { gridTemplateColumns: '1fr' },
},
})
export const Tile = define('Tile', {
@ -233,14 +231,6 @@ export const Tile = define('Tile', {
borderColor: theme('colors-textFaint'),
},
},
variants: {
narrow: {
flexDirection: 'row',
alignItems: 'center',
padding: '16px 20px',
gap: 16,
},
},
})
export const TileIcon = define('TileIcon', {

View File

@ -29,6 +29,7 @@ export { Form, FormActions, FormCheckbox, FormCheckboxField, FormCheckboxLabel,
export {
AppItem,
AppList,
AppSelectorChevron,
ClickableAppName,
DashboardContainer,
DashboardHeader,

View File

@ -106,7 +106,6 @@ export const SectionTab = define('SectionTab', {
background: theme('colors-bgSelected'),
color: theme('colors-text'),
},
large: { fontSize: 14, padding: '8px 12px' },
},
})
@ -130,7 +129,6 @@ export const AppItem = define('AppItem', {
'&:hover': { background: theme('colors-bgHover'), color: theme('colors-text') },
},
variants: {
large: { fontSize: 18, padding: '12px 16px', gap: 12 },
selected: { background: theme('colors-bgSelected'), color: theme('colors-text'), fontWeight: 500 },
},
})
@ -172,6 +170,20 @@ export const MainTitle = define('MainTitle', {
margin: 0,
})
export const AppSelectorChevron = define('AppSelectorChevron', {
base: 'button',
background: 'none',
border: 'none',
cursor: 'pointer',
padding: '4px 8px',
marginLeft: 4,
fontSize: 14,
color: theme('colors-textMuted'),
selectors: {
'&:hover': { color: theme('colors-text') },
},
})
export const ClickableAppName = define('ClickableAppName', {
cursor: 'pointer',
borderRadius: theme('radius-md'),
@ -225,7 +237,6 @@ export const DashboardContainer = define('DashboardContainer', {
export const DashboardHeader = define('DashboardHeader', {
textAlign: 'center',
width: '100%',
})
export const DashboardTitle = define('DashboardTitle', {

View File

@ -165,7 +165,6 @@ export const TabContent = define('TabContent', {
display: 'flex',
flexDirection: 'column',
flex: 1,
width: '100%',
}
}
})