Skip to content

Commit

Permalink
feat(desktop): Display template name in workspace card. Shows template (
Browse files Browse the repository at this point in the history
loft-sh#1236)

options on hover
  • Loading branch information
pascalbreuninger authored Sep 2, 2024
1 parent cc1dbc9 commit 744d20d
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 26 deletions.
6 changes: 3 additions & 3 deletions desktop/src/components/Layout/Pro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ function ProInstanceRow({
}
label=""
paddingInlineStart="0"
infoText={authenticated ? "Authenticated" : "Not Authenticated"}
info={authenticated ? "Authenticated" : "Not Authenticated"}
{...(authenticated ? {} : { onClick: onLoginClicked, cursor: "pointer" })}
/>
)}
Expand All @@ -209,14 +209,14 @@ function ProInstanceRow({
icon={<Briefcase />}
paddingInlineStart="0"
label={proInstanceWorkspaces.length.toString(10)}
infoText={`${proInstanceWorkspaces.length} workspaces`}
info={`${proInstanceWorkspaces.length} workspaces`}
/>
{exists(creationTimestamp) && (
<IconTag
variant="ghost"
icon={<Icon as={HiClock} />}
label={dayjs(new Date(creationTimestamp)).format("MMM D, YY")}
infoText={`Created ${dayjs(new Date(creationTimestamp)).fromNow()}`}
info={`Created ${dayjs(new Date(creationTimestamp)).fromNow()}`}
/>
)}
</HStack>
Expand Down
8 changes: 4 additions & 4 deletions desktop/src/components/Tag/IconTag.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { ButtonProps, Tag, TagLabel, TagProps, Tooltip, useColorModeValue } from "@chakra-ui/react"
import { ReactElement, cloneElement } from "react"
import { ReactElement, ReactNode, cloneElement } from "react"

type TIconTagProps = Readonly<{
icon: ReactElement
label: string
infoText?: string
info?: ReactNode
}> &
Pick<ButtonProps, "onClick"> &
TagProps

export function IconTag({ icon: iconProps, label, infoText, onClick, ...tagProps }: TIconTagProps) {
export function IconTag({ icon: iconProps, label, info, onClick, ...tagProps }: TIconTagProps) {
const tagColor = useColorModeValue("gray.700", "gray.300")
const icon = cloneElement(iconProps, { boxSize: 4 })

return (
<Tooltip label={infoText}>
<Tooltip label={info}>
<Tag
borderRadius="full"
color={tagColor}
Expand Down
11 changes: 11 additions & 0 deletions desktop/src/icons/Template.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { createIcon } from "@chakra-ui/react"
import { defaultProps } from "./defaultProps"

export const Template = createIcon({
displayName: "Template",
viewBox: "0 0 24 25",
defaultProps,
path: (
<path d="M6.9375 6.60461C6.83437 6.60461 6.75 6.68899 6.75 6.79211V7.91711C6.75 8.02024 6.83437 8.10461 6.9375 8.10461H15.9375C16.0406 8.10461 16.125 8.02024 16.125 7.91711V6.79211C16.125 6.68899 16.0406 6.60461 15.9375 6.60461H6.9375ZM11.4375 11.2921V10.1671C11.4375 10.064 11.3531 9.97961 11.25 9.97961H6.9375C6.83437 9.97961 6.75 10.064 6.75 10.1671V11.2921C6.75 11.3952 6.83437 11.4796 6.9375 11.4796H11.25C11.3531 11.4796 11.4375 11.3952 11.4375 11.2921ZM10.3125 20.5734H4.875V4.07336H18V12.1359C18 12.239 18.0844 12.3234 18.1875 12.3234H19.5C19.6031 12.3234 19.6875 12.239 19.6875 12.1359V3.13586C19.6875 2.72102 19.3523 2.38586 18.9375 2.38586H3.9375C3.52266 2.38586 3.1875 2.72102 3.1875 3.13586V21.5109C3.1875 21.9257 3.52266 22.2609 3.9375 22.2609H10.3125C10.4156 22.2609 10.5 22.1765 10.5 22.0734V20.7609C10.5 20.6577 10.4156 20.5734 10.3125 20.5734ZM12.7523 17.864C12.7945 17.0554 13.132 16.2984 13.7086 15.7218C14.3227 15.1077 15.1617 14.7609 16.0312 14.7609C16.9078 14.7609 17.7328 15.1031 18.3539 15.7218C18.4289 15.7968 18.5016 15.8765 18.5695 15.9585L18.0281 16.3781C18.0002 16.3996 17.979 16.4287 17.9668 16.4618C17.9547 16.495 17.9521 16.5309 17.9595 16.5654C17.9668 16.5999 17.9837 16.6316 18.0083 16.657C18.0329 16.6823 18.0641 16.7002 18.0984 16.7085L20.2852 17.2359C20.4023 17.264 20.5148 17.1749 20.5172 17.0554L20.5312 14.8195C20.5316 14.7842 20.522 14.7496 20.5036 14.7196C20.4852 14.6896 20.4586 14.6654 20.4271 14.6497C20.3955 14.634 20.3602 14.6276 20.3252 14.6311C20.2901 14.6346 20.2567 14.6479 20.2289 14.6695L19.7531 15.0398C18.8766 13.9546 17.5336 13.2609 16.0312 13.2609C13.4555 13.2609 11.3531 15.2976 11.25 17.8476C11.2453 17.9531 11.332 18.0421 11.4375 18.0421H12.5648C12.6656 18.0421 12.7477 17.9648 12.7523 17.864ZM20.625 18.0421H19.4977C19.3969 18.0421 19.3148 18.1195 19.3102 18.2202C19.268 19.0288 18.9305 19.7859 18.3539 20.3624C17.7398 20.9765 16.9008 21.3234 16.0312 21.3234C15.1547 21.3234 14.3297 20.9812 13.7086 20.3624C13.6336 20.2874 13.5609 20.2077 13.493 20.1257L14.0344 19.7062C14.0623 19.6846 14.0835 19.6555 14.0957 19.6224C14.1078 19.5893 14.1104 19.5534 14.103 19.5189C14.0957 19.4843 14.0788 19.4526 14.0542 19.4273C14.0296 19.4019 13.9984 19.3841 13.9641 19.3757L11.7773 18.8484C11.6602 18.8202 11.5477 18.9093 11.5453 19.0288L11.5312 21.2648C11.5309 21.3 11.5405 21.3346 11.5589 21.3646C11.5773 21.3946 11.6039 21.4189 11.6354 21.4345C11.667 21.4502 11.7023 21.4566 11.7373 21.4531C11.7724 21.4496 11.8058 21.4363 11.8336 21.4148L12.3094 21.0445C13.1859 22.1296 14.5289 22.8234 16.0312 22.8234C18.607 22.8234 20.7094 20.7866 20.8125 18.2366C20.8172 18.1312 20.7305 18.0421 20.625 18.0421Z" />
),
})
1 change: 1 addition & 0 deletions desktop/src/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ export { ProviderPlaceholder } from "./ProviderPlaceholder"
export { Stack3D } from "./Stack3D"
export { Status } from "./Status"
export { Trash } from "./Trash"
export { Template } from "./Template"
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function useProviderDisplayOptions(
// processProviderOptions reads, parses and groups all options into a displayable format.
// Option groups can contain wildcard options `MY_PREFIX_*`. All options matching the wildcard will be added to this group.
// The first group to claim a wildcard takes precendence.
function processDisplayOptions(
export function processDisplayOptions(
options: TProviderOptions | undefined,
optionGroups: TProviderOptionGroup[],
edit?: boolean
Expand Down
2 changes: 1 addition & 1 deletion desktop/src/views/Providers/ProviderCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export function ProviderCard({ id, provider, remove }: TProviderCardProps) {
? providerWorkspaces.length + " workspaces"
: "No workspaces"
}
infoText={`This provider is used by ${providerWorkspaces.length} ${
info={`This provider is used by ${providerWorkspaces.length} ${
providerWorkspaces.length === 1 ? "workspace" : "workspaces"
}`}
/>
Expand Down
109 changes: 92 additions & 17 deletions desktop/src/views/Workspaces/WorkspaceCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
HStack,
Icon,
IconButton,
List,
ListItem,
Menu,
MenuButton,
MenuItem,
Expand Down Expand Up @@ -57,16 +59,19 @@ import {
Pause,
Play,
Stack3D,
Template,
Trash,
} from "../../icons"
import { getIDEDisplayName, useHover } from "../../lib"
import { QueryKeys } from "../../queryKeys"
import { Routes } from "../../routes"
import { TIDE, TIDEs, TProInstance, TWorkspace, TWorkspaceID } from "../../types"
import { TIDE, TIDEs, TProInstance, TProvider, TWorkspace, TWorkspaceID } from "../../types"
import { useIDEs } from "../../useIDEs"
import { ConfigureProviderOptionsForm } from "../Providers"
import { getIDEName, getSourceName } from "./helpers"
import { WorkspaceStatusBadge } from "./WorkspaceStatusBadge"
import { processDisplayOptions } from "../Providers/AddProvider/useProviderOptions"
import { mergeOptionDefinitions, TOptionWithID } from "../Providers/helpers"

type TWorkspaceCardProps = Readonly<{
workspaceID: TWorkspaceID
Expand Down Expand Up @@ -99,6 +104,7 @@ export function WorkspaceCard({ workspaceID, isSelected, onSelectionChange }: TW
} = useDisclosure()

const workspace = useWorkspace(workspaceID)
const [provider] = useProvider(workspace.data?.provider?.name)
const [ideName, setIdeName] = useState<string | undefined>(() => {
if (settings.fixedIDE && defaultIDE?.name) {
return defaultIDE.name
Expand Down Expand Up @@ -164,6 +170,7 @@ export function WorkspaceCard({ workspaceID, isSelected, onSelectionChange }: TW
marginBottom="3">
<WorkspaceCardHeader
workspace={workspace.data}
provider={provider}
isLoading={isLoading}
currentAction={workspace.current}
ideName={ideName}
Expand All @@ -177,6 +184,7 @@ export function WorkspaceCard({ workspaceID, isSelected, onSelectionChange }: TW
<WorkspaceControls
id={workspace.data.id}
workspace={workspace}
provider={provider}
isLoading={isLoading}
isIDEFixed={settings.fixedIDE}
ides={ides}
Expand Down Expand Up @@ -341,6 +349,7 @@ export function WorkspaceCard({ workspaceID, isSelected, onSelectionChange }: TW

type TWorkspaceCardHeaderProps = Readonly<{
workspace: TWorkspace
provider: TProvider | undefined
isLoading: boolean
currentAction: TActionObj | undefined
ideName: string | undefined
Expand All @@ -352,6 +361,7 @@ type TWorkspaceCardHeaderProps = Readonly<{
}>
function WorkspaceCardHeader({
workspace,
provider,
isLoading,
currentAction,
ideName,
Expand All @@ -363,7 +373,7 @@ function WorkspaceCardHeader({
}: TWorkspaceCardHeaderProps) {
const navigate = useNavigate()
const checkboxID = useId()
const { id, status, provider, ide, lastUsed, source } = workspace
const { id, status, provider: providerState, ide, lastUsed, source } = workspace
const workspaceActions = useWorkspaceActions(id)

const idesQuery = useQuery({
Expand Down Expand Up @@ -401,7 +411,9 @@ function WorkspaceCardHeader({
? getIDEName({ name: ideName }, idesQuery.data)
: getIDEName(ide, idesQuery.data)

const maybeRunnerName = getRunnerName(workspace)
const maybeRunnerName = getRunnerName(workspace, provider)
const maybeTemplate = getTemplate(workspace, provider)
const maybeTemplateOptions = getTemplateOptions(workspace, provider)

return (
<CardHeader overflow="hidden" w="full">
Expand Down Expand Up @@ -458,33 +470,61 @@ function WorkspaceCardHeader({
<HStack rowGap={2} marginTop={4} flexWrap="wrap" alignItems="center" paddingLeft="8">
<IconTag
icon={<Stack3D />}
label={provider?.name ?? "No provider"}
infoText={provider?.name ? `Uses provider ${provider.name}` : undefined}
label={providerState?.name ?? "No provider"}
info={providerState?.name ? `Uses provider ${providerState.name}` : undefined}
onClick={() => {
if (!provider?.name) {
if (!providerState?.name) {
return
}

navigate(Routes.toProvider(provider.name))
navigate(Routes.toProvider(providerState.name))
}}
/>
<IconTag
icon={<Icon as={HiOutlineCode} />}
label={ideDisplayName}
infoText={`Will be opened in ${ideDisplayName}`}
/>
<IconTag
icon={<Icon as={HiClock} />}
label={dayjs(new Date(lastUsed)).fromNow()}
infoText={`Last used ${dayjs(new Date(lastUsed)).fromNow()}`}
info={`Will be opened in ${ideDisplayName}`}
/>
{maybeTemplate && (
<IconTag
icon={<Template />}
label={maybeTemplate}
info={
<Box width="full">
Using {maybeTemplate} template with options: <br />
{maybeTemplateOptions.length > 0 ? (
<List mt="2" width="full">
{maybeTemplateOptions.map((opt) => (
<ListItem
key={opt.id}
width="full"
display="flex"
flexFlow="row nowrap"
alignItems="space-between">
<Text fontWeight="bold">{opt.value}</Text>
<Text ml="4">({opt.displayName || opt.id})</Text>
</ListItem>
))}
</List>
) : (
"No options configured"
)}
</Box>
}
/>
)}
{maybeRunnerName && (
<IconTag
icon={<Icon as={HiServerStack} />}
label={maybeRunnerName}
infoText={`Running on ${maybeRunnerName}`}
info={`Running on ${maybeRunnerName}`}
/>
)}
<IconTag
icon={<Icon as={HiClock} />}
label={dayjs(new Date(lastUsed)).fromNow()}
info={`Last used ${dayjs(new Date(lastUsed)).fromNow()}`}
/>
</HStack>
</CardHeader>
)
Expand All @@ -493,6 +533,7 @@ function WorkspaceCardHeader({
type TWorkspaceControlsProps = Readonly<{
id: TWorkspaceID
workspace: TWorkspaceResult
provider: TProvider | undefined
isIDEFixed: boolean
isLoading: boolean
ides: TIDEs | undefined
Expand All @@ -513,6 +554,7 @@ function WorkspaceControls({
ides,
ideName,
isIDEFixed,
provider,
setIdeName,
navigateToAction,
onRebuildClicked,
Expand All @@ -522,7 +564,6 @@ function WorkspaceControls({
onLogsClicked,
onChangeOptionsClicked,
}: TWorkspaceControlsProps) {
const [provider] = useProvider(workspace.data?.provider?.name)
const [[proInstances]] = useProInstances()
const proInstance = useMemo<TProInstance | undefined>(() => {
if (!provider?.isProxyProvider) {
Expand Down Expand Up @@ -723,12 +764,46 @@ function useShareWorkspace(
}
}

function getRunnerName(workspace: TWorkspace): string | undefined {
const maybeRunnerOption = workspace.provider?.options?.["LOFT_RUNNER"]
function getRunnerName(workspace: TWorkspace, provider: TProvider | undefined): string | undefined {
const options = mergeOptionDefinitions(
workspace.provider?.options ?? {},
provider?.config?.options ?? {}
)
const maybeRunnerOption = options["LOFT_RUNNER"]
if (!maybeRunnerOption) {
return undefined
}
const value = maybeRunnerOption.value

return maybeRunnerOption.enum?.find((e) => e.value === value)?.displayName ?? value ?? undefined
}

function getTemplate(workspace: TWorkspace, provider: TProvider | undefined): string | undefined {
const options = mergeOptionDefinitions(
workspace.provider?.options ?? {},
provider?.config?.options ?? {}
)
const maybeTemplateOption = options["LOFT_TEMPLATE"]
if (!maybeTemplateOption) {
return undefined
}
const value = maybeTemplateOption.value

return maybeTemplateOption.enum?.find((e) => e.value === value)?.displayName ?? value ?? undefined
}

function getTemplateOptions(
workspace: TWorkspace,
provider: TProvider | undefined
): readonly TOptionWithID[] {
const options = mergeOptionDefinitions(
workspace.provider?.options ?? {},
provider?.config?.options ?? {}
)
const displayOptions = processDisplayOptions(options, [], true)

// shouldn't have groups here as we passed in empty array earlier
return [...displayOptions.required, ...displayOptions.other].filter(
(opt) => opt.id !== "LOFT_TEMPLATE"
)
}

0 comments on commit 744d20d

Please sign in to comment.