Skip to content

Commit

Permalink
Implement command palette
Browse files Browse the repository at this point in the history
Currently supports basic app nav, but will also integrate the ai endpoint functionality too
  • Loading branch information
michaeljguarino committed May 11, 2023
1 parent 99eaa55 commit 74b473c
Show file tree
Hide file tree
Showing 11 changed files with 479 additions and 12 deletions.
1 change: 1 addition & 0 deletions assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"js-file-download": "0.4.12",
"js-yaml": "4.1.0",
"jsonpath": "1.1.1",
"kbar": "^0.1.0-beta.40",
"kubernetes-resource-parser": "0.1.0",
"lodash": "4.17.21",
"moment": "2.29.4",
Expand Down
327 changes: 327 additions & 0 deletions assets/src/components/CommandPalette.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,327 @@
import {
KBarProvider,
KBarPortal,
KBarPositioner,
KBarAnimator,
KBarSearch,
useMatches,
KBarResults,
createAction,
useRegisterActions,
} from 'kbar'
import { Fragment, forwardRef, useContext, useMemo } from 'react'
import { InstallationContext } from './Installations'
import { getIcon, hasIcons } from './apps/misc'
import {
ApiIcon,
AppIcon,
BuildIcon,
ComponentsIcon,
DashboardIcon,
DocumentIcon,
GearTrainIcon,
LogsIcon,
RunBookIcon,
ServersIcon,
} from '@pluralsh/design-system'
import { useNavigate } from 'react-router-dom'

const animatorStyle = {
maxWidth: '600px',
width: '100%',
background: 'rgb(28 28 29)',
color: 'rgba(252 252 252 / 0.9)',
borderRadius: '8px',
overflow: 'hidden',
zIndex: 10000,
boxShadow: 'rgb(0 0 0 / 50%) 0px 16px 70px',
}

const searchStyle = {
padding: '12px 16px',
fontSize: '16px',
width: '100%',
boxSizing: 'border-box',
outline: 'none',
border: 'none',
background: 'rgb(28 28 29)',
color: 'rgba(252 252 252 / 0.9)',
}

const groupNameStyle = {
padding: '8px 16px',
fontSize: '10px',
textTransform: 'uppercase',
opacity: 0.5,
}

function buildActions(applications, nav) {
return applications
.map((app) => [
{
id: app.name,
name: app.name,
icon: hasIcons(app) ? (
<AppIcon
url={getIcon(app)}
size="xsmall"
/>
) : null,
shortcut: [],
section: 'Apps',
},
{
id: `${app.name}-logs`,
name: `${app.name} logs`,
icon: <LogsIcon />,
shortcut: [],
parent: app.name,
section: app.name,
perform: () => nav(`/apps/${app.name}/logs`),
},
{
id: `${app.name}-docs`,
name: `${app.name} docs`,
icon: <DocumentIcon />,
shortcut: [],
parent: app.name,
section: app.name,
perform: () => nav(`/apps/${app.name}/docs`),
},
{
id: `${app.name}-configuration`,
name: `${app.name} configuration`,
icon: <GearTrainIcon />,
shortcut: [],
parent: app.name,
section: app.name,
perform: () => nav(`/apps/${app.name}/configuration`),
},
{
id: `${app.name}-runbooks`,
name: `${app.name} runbooks`,
icon: <RunBookIcon />,
shortcut: [],
parent: app.name,
section: app.name,
perform: () => nav(`/apps/${app.name}/runbooks`),
},
{
id: `${app.name}-components`,
name: `${app.name} components`,
icon: <ComponentsIcon />,
shortcut: [],
parent: app.name,
section: app.name,
perform: () => nav(`/apps/${app.name}/components`),
},
{
id: `${app.name}-dashboards`,
name: `${app.name} dashboards`,
icon: <DashboardIcon />,
shortcut: [],
parent: app.name,
section: app.name,
perform: () => nav(`/apps/${app.name}/dashboards`),
},
])
.flat()
}

const ResultItem = forwardRef(
({ action, active, currentRootActionId }, ref) => {
const ancestors = useMemo(() => {
if (!currentRootActionId) return action.ancestors
const index = action.ancestors.findIndex(
(ancestor) => ancestor.id === currentRootActionId
)
// +1 removes the currentRootAction; e.g.
// if we are on the "Set theme" parent action,
// the UI should not display "Set theme… > Dark"
// but rather just "Dark"
return action.ancestors.slice(index + 1)
}, [action.ancestors, currentRootActionId])

return (
<div
ref={ref}
style={{
padding: '12px 16px',
background: active ? 'rgb(53 53 54)' : 'transparent',
borderLeft: `2px solid ${
active ? 'rgba(252 252 252 / 0.9)' : 'transparent'
}`,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
cursor: 'pointer',
}}
>
<div
style={{
display: 'flex',
gap: '8px',
alignItems: 'center',
fontSize: 14,
}}
>
{action.icon && action.icon}
<div style={{ display: 'flex', flexDirection: 'column' }}>
<div>
{ancestors.length > 0 &&
ancestors.map((ancestor) => (
<Fragment key={ancestor.id}>
<span
style={{
opacity: 0.5,
marginRight: 8,
}}
>
{ancestor.name}
</span>
<span
style={{
marginRight: 8,
}}
>
&rsaquo;
</span>
</Fragment>
))}
<span>{action.name}</span>
</div>
{action.subtitle && (
<span style={{ fontSize: 12 }}>{action.subtitle}</span>
)}
</div>
</div>
{action.shortcut?.length ? (
<div
aria-hidden
style={{ display: 'grid', gridAutoFlow: 'column', gap: '4px' }}
>
{action.shortcut.map((sc) => (
<kbd
key={sc}
style={{
padding: '4px 6px',
background: 'rgba(0 0 0 / .1)',
borderRadius: '4px',
fontSize: 14,
}}
>
{sc}
</kbd>
))}
</div>
) : null}
</div>
)
}
)

function RenderResults() {
const { results, rootActionId } = useMatches()

return (
<KBarResults
items={results}
onRender={({ item, active }) =>
typeof item === 'string' ? (
<div style={groupNameStyle}>{item}</div>
) : (
<ResultItem
action={item}
active={active}
currentRootActionId={rootActionId}
/>
)
}
/>
)
}

function useAppActions() {
const { applications } = useContext(InstallationContext)
const navigate = useNavigate()
const actions = useMemo(
() => buildActions(applications, navigate),
[applications, navigate]
)
useRegisterActions(actions)
}

function Palette() {
useAppActions()
return (
<KBarPortal style={{ zIndex: 10000 }}>
<KBarPositioner style={{ zIndex: 10000 }}>
<KBarAnimator style={animatorStyle}>
<KBarSearch style={searchStyle} />
<RenderResults />
</KBarAnimator>
</KBarPositioner>
</KBarPortal>
)
}

export function CommandPalette({ children }) {
const navigate = useNavigate()
const baseActions = useMemo(
() => [
createAction({
name: 'Nodes',
shortcuts: ['N'],
icon: <ServersIcon />,
section: 'Cluster',
perform: () => navigate('/nodes'),
}),
createAction({
name: 'Pods',
shortcuts: ['P'],
icon: <ApiIcon />,
section: 'Cluster',
perform: () => navigate('/pods'),
}),
// createAction({
// name: 'Builds',
// shortcuts: ['B'],
// icon: <BuildIcon />,
// section: 'Cluster',
// perform: () => navigate('/builds'),
// }),
createAction({
name: 'Temporary Token',
shortcuts: ['N'],
section: 'Security',
perform: () => navigate('/profile/security'),
}),
createAction({
name: 'VPN',
shortcuts: ['V'],
section: 'Security',
perform: () => navigate('/profile/vpn'),
}),
createAction({
name: 'Users',
shortcuts: [],
section: 'Account',
perform: () => navigate('/account/users'),
}),
// createAction({
// name: 'Webhooks',
// shortcuts: [],
// section: 'Account',
// perform: () => navigate('/account/webhooks'),
// }),
],
[navigate]
)

return (
<KBarProvider actions={baseActions}>
<Palette />
{children}
</KBarProvider>
)
}
1 change: 0 additions & 1 deletion assets/src/components/Installations.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { createContext, useEffect } from 'react'
import { useQuery } from '@apollo/client'
import { LoopingLogo } from '@pluralsh/design-system'

import { APPLICATIONS_Q, APPLICATION_SUB } from './graphql/plural'
import ShowAfterDelay from './utils/ShowAfterDelay'
Expand Down
17 changes: 10 additions & 7 deletions assets/src/components/layout/Console.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import Subheader from './Subheader'
import Sidebar from './Sidebar'
import WithApplicationUpdate from './WithApplicationUpdate'
import { ContentOverlay } from './Overlay'
import { CommandPalette } from 'components/CommandPalette'

export const TOOLBAR_HEIGHT = '55px'
export const SIDEBAR_WIDTH = '200px'
Expand All @@ -30,13 +31,15 @@ export default function Console() {
<ConsoleNavContextProvider>
<EnsureLogin>
<InstallationsProvider>
<BillingSubscriptionProvider>
<BreadcrumbsProvider>
<TerminalThemeProvider>
<ConsoleContent />
</TerminalThemeProvider>
</BreadcrumbsProvider>
</BillingSubscriptionProvider>
<CommandPalette>
<BillingSubscriptionProvider>
<BreadcrumbsProvider>
<TerminalThemeProvider>
<ConsoleContent />
</TerminalThemeProvider>
</BreadcrumbsProvider>
</BillingSubscriptionProvider>
</CommandPalette>
</InstallationsProvider>
</EnsureLogin>
</ConsoleNavContextProvider>
Expand Down
Loading

0 comments on commit 74b473c

Please sign in to comment.