Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement command palette #353

Merged
merged 3 commits into from
Jun 2, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Implement command palette
Currently supports basic app nav, but will also integrate the ai endpoint functionality too
  • Loading branch information
michaeljguarino committed Jun 1, 2023
commit d619a8310553a2d4ff097c2046f0ab276d6400ce
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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you change to 0.1.0-beta.40?

"kubernetes-resource-parser": "0.1.0",
"lodash": "4.17.21",
"moment": "2.29.4",
Expand Down
338 changes: 338 additions & 0 deletions assets/src/components/CommandPalette.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,338 @@
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) {
console.log(applications)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove it?

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}-launch`,
name: `launch ${app.name}`,
shortcut: [],
parent: app.name,
section: app.name,
perform: () => {
window.location.href = `https://${app.spec.descriptor.links[0].url}`
},
},
{
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}/config`),
},
{
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'),
// }),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it stay commented out?

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>
)
}
17 changes: 10 additions & 7 deletions assets/src/components/layout/Console.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ContentOverlay } from './Overlay'
import Sidebar from './Sidebar'
import Subheader from './Subheader'
import WithApplicationUpdate from './WithApplicationUpdate'
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() {
<EnsureLogin>
<InstallationsProvider>
<PluralProvider>
<BillingSubscriptionProvider>
<BreadcrumbsProvider>
<TerminalThemeProvider>
<ConsoleContent />
</TerminalThemeProvider>
</BreadcrumbsProvider>
</BillingSubscriptionProvider>
<CommandPalette>
<BillingSubscriptionProvider>
<BreadcrumbsProvider>
<TerminalThemeProvider>
<ConsoleContent />
</TerminalThemeProvider>
</BreadcrumbsProvider>
</BillingSubscriptionProvider>
</CommandPalette>
</PluralProvider>
</InstallationsProvider>
</EnsureLogin>
Expand Down
Loading