From a51beea09be2aef40a47071c2daca2cd2d925816 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Wed, 14 Aug 2024 12:13:31 -0500 Subject: [PATCH 01/11] revise recent projects flow to be less confusing --- ...erGatewayRecentWorkspaceConnectionsView.kt | 75 ++++++++++--------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt index 252c51c2..2ff9671f 100644 --- a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt +++ b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt @@ -7,6 +7,7 @@ import com.coder.gateway.CoderGatewayConstants import com.coder.gateway.CoderRemoteConnectionHandle import com.coder.gateway.icons.CoderIcons import com.coder.gateway.models.WorkspaceAgentListModel +import com.coder.gateway.models.WorkspaceAndAgentStatus import com.coder.gateway.models.WorkspaceProjectIDE import com.coder.gateway.models.toWorkspaceProjectIDE import com.coder.gateway.sdk.CoderRestClient @@ -193,11 +194,11 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback: TopGap.MEDIUM } row { - icon(status.first).applyToComponent { - foreground = status.second - }.align(AlignX.LEFT).gap(RightGap.SMALL).applyToComponent { - size = Dimension(JBUI.scale(16), JBUI.scale(16)) - } +// icon(status.first).applyToComponent { +// foreground = status.second +// }.align(AlignX.LEFT).gap(RightGap.SMALL).applyToComponent { +// size = Dimension(JBUI.scale(16), JBUI.scale(16)) +// } label(workspaceName).applyToComponent { font = JBFont.h3().asBold() }.align(AlignX.LEFT).gap(RightGap.SMALL) @@ -280,41 +281,47 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback: if (deploymentError == null || showError) { row { // There must be a way to make this properly wrap? + if (status.first == CoderIcons.PENDING) { + icon(status.first) + } label("" + status.third + "").applyToComponent { foreground = status.second } } } - connections.forEach { workspaceProjectIDE -> - row { - icon(workspaceProjectIDE.ideProduct.icon) - cell( - ActionLink(workspaceProjectIDE.projectPathDisplay) { - CoderRemoteConnectionHandle().connect { workspaceProjectIDE } - GatewayUI.getInstance().reset() - }, - ) - label("").resizableColumn().align(AlignX.FILL) - label(workspaceProjectIDE.ideName).applyToComponent { - foreground = JBUI.CurrentTheme.ContextHelp.FOREGROUND - font = ComponentPanelBuilder.getCommentFont(font) - } - label(workspaceProjectIDE.lastOpened.toString()).applyToComponent { - foreground = JBUI.CurrentTheme.ContextHelp.FOREGROUND - font = ComponentPanelBuilder.getCommentFont(font) + if (workspaceWithAgent?.workspace?.latestBuild?.status == WorkspaceStatus.RUNNING && workspaceWithAgent.status == WorkspaceAndAgentStatus.READY) { + row { label("Select a project to launch.") } + connections.forEach { workspaceProjectIDE -> + row { + icon(workspaceProjectIDE.ideProduct.icon) + cell( + ActionLink(workspaceProjectIDE.projectPathDisplay) { + CoderRemoteConnectionHandle().connect { workspaceProjectIDE } + GatewayUI.getInstance().reset() + }, + ) + label("").resizableColumn().align(AlignX.FILL) + label(workspaceProjectIDE.ideName).applyToComponent { + foreground = JBUI.CurrentTheme.ContextHelp.FOREGROUND + font = ComponentPanelBuilder.getCommentFont(font) + } + label(workspaceProjectIDE.lastOpened.toString()).applyToComponent { + foreground = JBUI.CurrentTheme.ContextHelp.FOREGROUND + font = ComponentPanelBuilder.getCommentFont(font) + } + actionButton( + object : DumbAwareAction( + CoderGatewayBundle.message("gateway.connector.recent-connections.remove.button.tooltip"), + "", + CoderIcons.DELETE, + ) { + override fun actionPerformed(e: AnActionEvent) { + recentConnectionsService.removeConnection(workspaceProjectIDE.toRecentWorkspaceConnection()) + updateRecentView() + } + }, + ) } - actionButton( - object : DumbAwareAction( - CoderGatewayBundle.message("gateway.connector.recent-connections.remove.button.tooltip"), - "", - CoderIcons.DELETE, - ) { - override fun actionPerformed(e: AnActionEvent) { - recentConnectionsService.removeConnection(workspaceProjectIDE.toRecentWorkspaceConnection()) - updateRecentView() - } - }, - ) } } } From c704fa55d1c0ad64203010005d3958c0a5cc1cbc Mon Sep 17 00:00:00 2001 From: Benjamin Date: Wed, 14 Aug 2024 14:45:27 -0500 Subject: [PATCH 02/11] swap hourglass for spinner --- .../gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt index 2ff9671f..6d0904e4 100644 --- a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt +++ b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt @@ -282,7 +282,7 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback: row { // There must be a way to make this properly wrap? if (status.first == CoderIcons.PENDING) { - icon(status.first) + icon(AnimatedIcon.Default()) } label("" + status.third + "").applyToComponent { foreground = status.second From 6aeb88d2a035714265abe3a1fc7f146da59b4dc7 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Thu, 15 Aug 2024 12:08:55 -0500 Subject: [PATCH 03/11] make the link just start the workspace --- ...erGatewayRecentWorkspaceConnectionsView.kt | 102 ++++-------------- 1 file changed, 22 insertions(+), 80 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt index 6d0904e4..4799fcee 100644 --- a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt +++ b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt @@ -33,6 +33,7 @@ import com.intellij.openapi.ui.panel.ComponentPanelBuilder import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeScreenUIManager import com.intellij.ui.AnimatedIcon import com.intellij.ui.DocumentAdapter +import com.intellij.ui.JBColor import com.intellij.ui.SearchTextField import com.intellij.ui.components.ActionLink import com.intellij.ui.components.JBScrollPane @@ -57,6 +58,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.awt.Color import java.awt.Component import java.awt.Dimension import java.util.Locale @@ -194,11 +196,6 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback: TopGap.MEDIUM } row { -// icon(status.first).applyToComponent { -// foreground = status.second -// }.align(AlignX.LEFT).gap(RightGap.SMALL).applyToComponent { -// size = Dimension(JBUI.scale(16), JBUI.scale(16)) -// } label(workspaceName).applyToComponent { font = JBFont.h3().asBold() }.align(AlignX.LEFT).gap(RightGap.SMALL) @@ -206,14 +203,17 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback: foreground = UIUtil.getContextHelpForeground() font = ComponentPanelBuilder.getCommentFont(font) } - label("").resizableColumn().align(AlignX.FILL) - actionButton( - object : DumbAwareAction( - CoderGatewayBundle.message("gateway.connector.recent-connections.start.button.tooltip"), - "", - CoderIcons.RUN, - ) { - override fun actionPerformed(e: AnActionEvent) { + label("").resizableColumn().align(AlignX.FILL) }.topGap(gap) + + row { label("Select a project to launch.") } + connections.forEach { workspaceProjectIDE -> + + val actionLink = ActionLink(workspaceProjectIDE.projectPathDisplay) { + if (workspaceWithAgent?.workspace?.latestBuild?.status == WorkspaceStatus.RUNNING && workspaceWithAgent.status == WorkspaceAndAgentStatus.READY) { + CoderRemoteConnectionHandle().connect { workspaceProjectIDE } + GatewayUI.getInstance().reset() + } else { + // Start the workspace withoutNull(workspaceWithAgent?.workspace, deployment?.client) { workspace, client -> jobs[workspace.id]?.cancel() jobs[workspace.id] = @@ -227,78 +227,20 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback: } } } + cs.launch { + jobs[workspace.id]?.join() + CoderRemoteConnectionHandle().connect { workspaceProjectIDE } + GatewayUI.getInstance().reset() + } } + } - }, - ).applyToComponent { - isEnabled = - listOf( - WorkspaceStatus.STOPPED, - WorkspaceStatus.FAILED, - ).contains(workspaceWithAgent?.workspace?.latestBuild?.status) - } - .gap(RightGap.SMALL) - actionButton( - object : DumbAwareAction( - CoderGatewayBundle.message("gateway.connector.recent-connections.stop.button.tooltip"), - "", - CoderIcons.STOP, - ) { - override fun actionPerformed(e: AnActionEvent) { - withoutNull(workspaceWithAgent?.workspace, deployment?.client) { workspace, client -> - jobs[workspace.id]?.cancel() - jobs[workspace.id] = - cs.launch(ModalityState.current().asContextElement()) { - withContext(Dispatchers.IO) { - try { - client.stopWorkspace(workspace) - fetchWorkspaces() - } catch (e: Exception) { - logger.error("Could not stop workspace ${workspace.name}", e) - } - } - } - } - } - }, - ).applyToComponent { isEnabled = workspaceWithAgent?.workspace?.latestBuild?.status == WorkspaceStatus.RUNNING } - .gap(RightGap.SMALL) - actionButton( - object : DumbAwareAction( - CoderGatewayBundle.message("gateway.connector.recent-connections.terminal.button.tooltip"), - "", - CoderIcons.OPEN_TERMINAL, - ) { - override fun actionPerformed(e: AnActionEvent) { - withoutNull(workspaceWithAgent, deployment?.client) { ws, client -> - val link = client.url.withPath("/me/${ws.name}/terminal") - BrowserUtil.browse(link.toString()) - } - } - }, - ) - }.topGap(gap) - if (deploymentError == null || showError) { - row { - // There must be a way to make this properly wrap? - if (status.first == CoderIcons.PENDING) { - icon(AnimatedIcon.Default()) } - label("" + status.third + "").applyToComponent { - foreground = status.second - } - } - } - if (workspaceWithAgent?.workspace?.latestBuild?.status == WorkspaceStatus.RUNNING && workspaceWithAgent.status == WorkspaceAndAgentStatus.READY) { - row { label("Select a project to launch.") } - connections.forEach { workspaceProjectIDE -> + row { icon(workspaceProjectIDE.ideProduct.icon) cell( - ActionLink(workspaceProjectIDE.projectPathDisplay) { - CoderRemoteConnectionHandle().connect { workspaceProjectIDE } - GatewayUI.getInstance().reset() - }, + actionLink, ) label("").resizableColumn().align(AlignX.FILL) label(workspaceProjectIDE.ideName).applyToComponent { @@ -323,7 +265,7 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback: ) } } - } + } } }.apply { From 7cd80fb0aac118dff8574eed1a5bc67b3f0e135a Mon Sep 17 00:00:00 2001 From: Benjamin Date: Fri, 16 Aug 2024 11:47:08 -0500 Subject: [PATCH 04/11] disable link when appropriate and show loading states --- .../gateway/CoderRemoteConnectionHandle.kt | 4 ++ ...erGatewayRecentWorkspaceConnectionsView.kt | 45 +++++++++---------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt b/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt index 10c5d0ec..66b9bffc 100644 --- a/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt +++ b/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt @@ -7,6 +7,9 @@ import com.coder.gateway.models.WorkspaceProjectIDE import com.coder.gateway.models.toIdeWithStatus import com.coder.gateway.models.toRawString import com.coder.gateway.models.withWorkspaceProject +import com.coder.gateway.sdk.CoderRestClient +import com.coder.gateway.sdk.v2.models.Workspace +import com.coder.gateway.sdk.v2.models.WorkspaceStatus import com.coder.gateway.services.CoderRecentWorkspaceConnectionsService import com.coder.gateway.services.CoderSettingsService import com.coder.gateway.util.SemVer @@ -65,6 +68,7 @@ class CoderRemoteConnectionHandle { private val localTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm") fun connect(getParameters: (indicator: ProgressIndicator) -> WorkspaceProjectIDE) { + val clientLifetime = LifetimeDefinition() clientLifetime.launchUnderBackgroundProgress(CoderGatewayBundle.message("gateway.connector.coder.connection.provider.title")) { try { diff --git a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt index 4799fcee..880933f4 100644 --- a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt +++ b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt @@ -203,38 +203,35 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback: foreground = UIUtil.getContextHelpForeground() font = ComponentPanelBuilder.getCommentFont(font) } - label("").resizableColumn().align(AlignX.FILL) }.topGap(gap) + label("").resizableColumn().align(AlignX.FILL) + }.topGap(gap) - row { label("Select a project to launch.") } connections.forEach { workspaceProjectIDE -> + val enableLinks = listOf(WorkspaceStatus.STOPPED, WorkspaceStatus.CANCELED, WorkspaceStatus.FAILED, WorkspaceStatus.STARTING, WorkspaceStatus.RUNNING).contains(workspaceWithAgent?.workspace?.latestBuild?.status) + val inLoadingState = listOf(WorkspaceStatus.STARTING, WorkspaceStatus.CANCELING, WorkspaceStatus.DELETING, WorkspaceStatus.STOPPING).contains(workspaceWithAgent?.workspace?.latestBuild?.status) val actionLink = ActionLink(workspaceProjectIDE.projectPathDisplay) { - if (workspaceWithAgent?.workspace?.latestBuild?.status == WorkspaceStatus.RUNNING && workspaceWithAgent.status == WorkspaceAndAgentStatus.READY) { - CoderRemoteConnectionHandle().connect { workspaceProjectIDE } - GatewayUI.getInstance().reset() - } else { - // Start the workspace - withoutNull(workspaceWithAgent?.workspace, deployment?.client) { workspace, client -> - jobs[workspace.id]?.cancel() - jobs[workspace.id] = - cs.launch(ModalityState.current().asContextElement()) { - withContext(Dispatchers.IO) { - try { - client.startWorkspace(workspace) - fetchWorkspaces() - } catch (e: Exception) { - logger.error("Could not start workspace ${workspace.name}", e) - } - } - } - cs.launch { - jobs[workspace.id]?.join() - CoderRemoteConnectionHandle().connect { workspaceProjectIDE } - GatewayUI.getInstance().reset() + withoutNull(deployment?.client, workspaceWithAgent?.workspace) { client, workspace -> + CoderRemoteConnectionHandle().connect { + if (listOf(WorkspaceStatus.STOPPED, WorkspaceStatus.CANCELED, WorkspaceStatus.FAILED).contains(workspace.latestBuild.status)) { + client.startWorkspace(workspace) } + workspaceProjectIDE } + GatewayUI.getInstance().reset() + } + } + if (!enableLinks) { + actionLink.foreground = Color.GRAY + actionLink.actionListeners.forEach { actionLink.removeActionListener(it) } + } + + row { + if (inLoadingState) { + icon(AnimatedIcon.Default()) } + label(workspaceWithAgent?.status?.description.orEmpty()) } row { From 9c876af87df1413af7700c0d81b4eb44430ab04a Mon Sep 17 00:00:00 2001 From: Benjamin Date: Fri, 16 Aug 2024 14:03:29 -0500 Subject: [PATCH 05/11] remove underline for disabled link --- .../gateway/CoderRemoteConnectionHandle.kt | 4 - ...erGatewayRecentWorkspaceConnectionsView.kt | 98 +++++++++---------- 2 files changed, 46 insertions(+), 56 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt b/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt index 66b9bffc..10c5d0ec 100644 --- a/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt +++ b/src/main/kotlin/com/coder/gateway/CoderRemoteConnectionHandle.kt @@ -7,9 +7,6 @@ import com.coder.gateway.models.WorkspaceProjectIDE import com.coder.gateway.models.toIdeWithStatus import com.coder.gateway.models.toRawString import com.coder.gateway.models.withWorkspaceProject -import com.coder.gateway.sdk.CoderRestClient -import com.coder.gateway.sdk.v2.models.Workspace -import com.coder.gateway.sdk.v2.models.WorkspaceStatus import com.coder.gateway.services.CoderRecentWorkspaceConnectionsService import com.coder.gateway.services.CoderSettingsService import com.coder.gateway.util.SemVer @@ -68,7 +65,6 @@ class CoderRemoteConnectionHandle { private val localTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm") fun connect(getParameters: (indicator: ProgressIndicator) -> WorkspaceProjectIDE) { - val clientLifetime = LifetimeDefinition() clientLifetime.launchUnderBackgroundProgress(CoderGatewayBundle.message("gateway.connector.coder.connection.provider.title")) { try { diff --git a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt index 880933f4..200cfe79 100644 --- a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt +++ b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt @@ -7,7 +7,6 @@ import com.coder.gateway.CoderGatewayConstants import com.coder.gateway.CoderRemoteConnectionHandle import com.coder.gateway.icons.CoderIcons import com.coder.gateway.models.WorkspaceAgentListModel -import com.coder.gateway.models.WorkspaceAndAgentStatus import com.coder.gateway.models.WorkspaceProjectIDE import com.coder.gateway.models.toWorkspaceProjectIDE import com.coder.gateway.sdk.CoderRestClient @@ -18,10 +17,8 @@ import com.coder.gateway.services.CoderRestClientService import com.coder.gateway.services.CoderSettingsService import com.coder.gateway.util.humanizeConnectionError import com.coder.gateway.util.toURL -import com.coder.gateway.util.withPath import com.coder.gateway.util.withoutNull import com.intellij.icons.AllIcons -import com.intellij.ide.BrowserUtil import com.intellij.openapi.Disposable import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.application.ModalityState @@ -33,7 +30,6 @@ import com.intellij.openapi.ui.panel.ComponentPanelBuilder import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeScreenUIManager import com.intellij.ui.AnimatedIcon import com.intellij.ui.DocumentAdapter -import com.intellij.ui.JBColor import com.intellij.ui.SearchTextField import com.intellij.ui.components.ActionLink import com.intellij.ui.components.JBScrollPane @@ -206,63 +202,61 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback: label("").resizableColumn().align(AlignX.FILL) }.topGap(gap) - connections.forEach { workspaceProjectIDE -> - val enableLinks = listOf(WorkspaceStatus.STOPPED, WorkspaceStatus.CANCELED, WorkspaceStatus.FAILED, WorkspaceStatus.STARTING, WorkspaceStatus.RUNNING).contains(workspaceWithAgent?.workspace?.latestBuild?.status) - val inLoadingState = listOf(WorkspaceStatus.STARTING, WorkspaceStatus.CANCELING, WorkspaceStatus.DELETING, WorkspaceStatus.STOPPING).contains(workspaceWithAgent?.workspace?.latestBuild?.status) + connections.forEach { workspaceProjectIDE -> + val enableLinks = listOf(WorkspaceStatus.STOPPED, WorkspaceStatus.CANCELED, WorkspaceStatus.FAILED, WorkspaceStatus.STARTING, WorkspaceStatus.RUNNING).contains(workspaceWithAgent?.workspace?.latestBuild?.status) + val inLoadingState = listOf(WorkspaceStatus.STARTING, WorkspaceStatus.CANCELING, WorkspaceStatus.DELETING, WorkspaceStatus.STOPPING).contains(workspaceWithAgent?.workspace?.latestBuild?.status) - val actionLink = ActionLink(workspaceProjectIDE.projectPathDisplay) { - withoutNull(deployment?.client, workspaceWithAgent?.workspace) { client, workspace -> - CoderRemoteConnectionHandle().connect { - if (listOf(WorkspaceStatus.STOPPED, WorkspaceStatus.CANCELED, WorkspaceStatus.FAILED).contains(workspace.latestBuild.status)) { - client.startWorkspace(workspace) - } - workspaceProjectIDE - } - GatewayUI.getInstance().reset() - } - } - - if (!enableLinks) { - actionLink.foreground = Color.GRAY - actionLink.actionListeners.forEach { actionLink.removeActionListener(it) } - } - - row { - if (inLoadingState) { - icon(AnimatedIcon.Default()) - } - label(workspaceWithAgent?.status?.description.orEmpty()) + row { + if (inLoadingState) { + icon(AnimatedIcon.Default()) } + label(workspaceWithAgent?.status?.description.orEmpty()) + } - row { - icon(workspaceProjectIDE.ideProduct.icon) + row { + icon(workspaceProjectIDE.ideProduct.icon) + if (enableLinks) { cell( - actionLink, - ) - label("").resizableColumn().align(AlignX.FILL) - label(workspaceProjectIDE.ideName).applyToComponent { - foreground = JBUI.CurrentTheme.ContextHelp.FOREGROUND - font = ComponentPanelBuilder.getCommentFont(font) - } - label(workspaceProjectIDE.lastOpened.toString()).applyToComponent { - foreground = JBUI.CurrentTheme.ContextHelp.FOREGROUND - font = ComponentPanelBuilder.getCommentFont(font) - } - actionButton( - object : DumbAwareAction( - CoderGatewayBundle.message("gateway.connector.recent-connections.remove.button.tooltip"), - "", - CoderIcons.DELETE, - ) { - override fun actionPerformed(e: AnActionEvent) { - recentConnectionsService.removeConnection(workspaceProjectIDE.toRecentWorkspaceConnection()) - updateRecentView() + ActionLink(workspaceProjectIDE.projectPathDisplay) { + withoutNull(deployment?.client, workspaceWithAgent?.workspace) { client, workspace -> + CoderRemoteConnectionHandle().connect { + if (listOf(WorkspaceStatus.STOPPED, WorkspaceStatus.CANCELED, WorkspaceStatus.FAILED).contains(workspace.latestBuild.status)) { + client.startWorkspace(workspace) + } + workspaceProjectIDE + } + GatewayUI.getInstance().reset() } }, ) + } else { + label(workspaceProjectIDE.projectPathDisplay).applyToComponent { + foreground = Color.GRAY + } + } + label("").resizableColumn().align(AlignX.FILL) + label(workspaceProjectIDE.ideName).applyToComponent { + foreground = JBUI.CurrentTheme.ContextHelp.FOREGROUND + font = ComponentPanelBuilder.getCommentFont(font) + } + label(workspaceProjectIDE.lastOpened.toString()).applyToComponent { + foreground = JBUI.CurrentTheme.ContextHelp.FOREGROUND + font = ComponentPanelBuilder.getCommentFont(font) } + actionButton( + object : DumbAwareAction( + CoderGatewayBundle.message("gateway.connector.recent-connections.remove.button.tooltip"), + "", + CoderIcons.DELETE, + ) { + override fun actionPerformed(e: AnActionEvent) { + recentConnectionsService.removeConnection(workspaceProjectIDE.toRecentWorkspaceConnection()) + updateRecentView() + } + }, + ) } - + } } } }.apply { From cc07facc8e84d27bcfd4d4e7674fe4a6e141fbe8 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Fri, 16 Aug 2024 14:30:38 -0500 Subject: [PATCH 06/11] re-add wrapping to description label --- .../CoderGatewayRecentWorkspaceConnectionsView.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt index 200cfe79..dc58a699 100644 --- a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt +++ b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt @@ -174,15 +174,14 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback: val workspaceWithAgent = deployment?.items?.firstOrNull { it.workspace.name == workspaceName } val status = if (deploymentError != null) { - Triple(UIUtil.getBalloonErrorIcon(), UIUtil.getErrorForeground(), deploymentError) + Pair(UIUtil.getErrorForeground(), deploymentError) } else if (workspaceWithAgent != null) { - Triple( - workspaceWithAgent.status.icon, + Pair( workspaceWithAgent.status.statusColor(), workspaceWithAgent.status.description, ) } else { - Triple(AnimatedIcon.Default.INSTANCE, UIUtil.getContextHelpForeground(), "Querying workspace status...") + Pair(UIUtil.getContextHelpForeground(), "Querying workspace status...") } val gap = if (top) { @@ -210,7 +209,9 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback: if (inLoadingState) { icon(AnimatedIcon.Default()) } - label(workspaceWithAgent?.status?.description.orEmpty()) + label("" + status.second + "").applyToComponent { + foreground = status.first + } } row { From 93101b397e78fe81846f8fa48729b62eeae32033 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Mon, 19 Aug 2024 11:59:19 -0500 Subject: [PATCH 07/11] add back status icon in error case --- ...erGatewayRecentWorkspaceConnectionsView.kt | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt index dc58a699..3cf9db60 100644 --- a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt +++ b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt @@ -174,14 +174,15 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback: val workspaceWithAgent = deployment?.items?.firstOrNull { it.workspace.name == workspaceName } val status = if (deploymentError != null) { - Pair(UIUtil.getErrorForeground(), deploymentError) + Triple(UIUtil.getErrorForeground(), deploymentError, UIUtil.getBalloonErrorIcon()) } else if (workspaceWithAgent != null) { - Pair( + Triple( workspaceWithAgent.status.statusColor(), workspaceWithAgent.status.description, + null ) } else { - Pair(UIUtil.getContextHelpForeground(), "Querying workspace status...") + Triple(UIUtil.getContextHelpForeground(), "Querying workspace status...", AnimatedIcon.Default()) } val gap = if (top) { @@ -201,19 +202,25 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback: label("").resizableColumn().align(AlignX.FILL) }.topGap(gap) - connections.forEach { workspaceProjectIDE -> - val enableLinks = listOf(WorkspaceStatus.STOPPED, WorkspaceStatus.CANCELED, WorkspaceStatus.FAILED, WorkspaceStatus.STARTING, WorkspaceStatus.RUNNING).contains(workspaceWithAgent?.workspace?.latestBuild?.status) - val inLoadingState = listOf(WorkspaceStatus.STARTING, WorkspaceStatus.CANCELING, WorkspaceStatus.DELETING, WorkspaceStatus.STOPPING).contains(workspaceWithAgent?.workspace?.latestBuild?.status) + val enableLinks = listOf(WorkspaceStatus.STOPPED, WorkspaceStatus.CANCELED, WorkspaceStatus.FAILED, WorkspaceStatus.STARTING, WorkspaceStatus.RUNNING).contains(workspaceWithAgent?.workspace?.latestBuild?.status) + val inLoadingState = listOf(WorkspaceStatus.STARTING, WorkspaceStatus.CANCELING, WorkspaceStatus.DELETING, WorkspaceStatus.STOPPING).contains(workspaceWithAgent?.workspace?.latestBuild?.status) + // We only display an API error on the first workspace rather than duplicating it on each workspace. + if (deploymentError == null || showError) { row { if (inLoadingState) { icon(AnimatedIcon.Default()) } + if (status.third != null) { + icon(status.third!!) + } label("" + status.second + "").applyToComponent { foreground = status.first } } + } + connections.forEach { workspaceProjectIDE -> row { icon(workspaceProjectIDE.ideProduct.icon) if (enableLinks) { From 901c2c301692f98c9f0936c0f884f2c4ba50a4be Mon Sep 17 00:00:00 2001 From: Benjamin Date: Mon, 19 Aug 2024 11:59:50 -0500 Subject: [PATCH 08/11] run linter --- .../gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt index 3cf9db60..582a5262 100644 --- a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt +++ b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt @@ -179,7 +179,7 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback: Triple( workspaceWithAgent.status.statusColor(), workspaceWithAgent.status.description, - null + null, ) } else { Triple(UIUtil.getContextHelpForeground(), "Querying workspace status...", AnimatedIcon.Default()) From a6b196b0060ab24d1da0b13174f68530a30c5fde Mon Sep 17 00:00:00 2001 From: Benjamin Date: Mon, 19 Aug 2024 12:09:08 -0500 Subject: [PATCH 09/11] update changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 716d5757..f41b236f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ ## Unreleased +- The "Recents" view has been updated to have a new flow. + Before, there were separate controls for managing the workspace and then you + could click a link to launch a project (clicking a link would also start a stopped workspace automatically). + Now, there are no workspace controls, just links which start the workspace automatically when needed. + The links are enabled when the workspace is STOPPED, CANCELED, FAILED, STARTING, RUNNING. These states represent + valid times to start a workspace and connect, or to simply connect to a running one or one that's already starting. + We also use a spinner icon when workspaces are in a transition state (STARTING, CANCELING, DELETING, STOPPING) + to give context for why a link might be disabled or a connection might take longer than usual to establish. + ## 2.13.1 - 2024-07-19 ### Changed From 3202bb83269b508b63300e279b54b1d129f5170c Mon Sep 17 00:00:00 2001 From: Benjamin Date: Mon, 19 Aug 2024 16:37:02 -0500 Subject: [PATCH 10/11] decide icon in one place --- .../CoderGatewayRecentWorkspaceConnectionsView.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt index 582a5262..47d6edfd 100644 --- a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt +++ b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt @@ -176,10 +176,16 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback: if (deploymentError != null) { Triple(UIUtil.getErrorForeground(), deploymentError, UIUtil.getBalloonErrorIcon()) } else if (workspaceWithAgent != null) { + val inLoadingState = listOf(WorkspaceStatus.STARTING, WorkspaceStatus.CANCELING, WorkspaceStatus.DELETING, WorkspaceStatus.STOPPING).contains(workspaceWithAgent?.workspace?.latestBuild?.status) + Triple( workspaceWithAgent.status.statusColor(), workspaceWithAgent.status.description, - null, + if (inLoadingState) { + AnimatedIcon.Default() + } else { + null + }, ) } else { Triple(UIUtil.getContextHelpForeground(), "Querying workspace status...", AnimatedIcon.Default()) @@ -203,14 +209,10 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback: }.topGap(gap) val enableLinks = listOf(WorkspaceStatus.STOPPED, WorkspaceStatus.CANCELED, WorkspaceStatus.FAILED, WorkspaceStatus.STARTING, WorkspaceStatus.RUNNING).contains(workspaceWithAgent?.workspace?.latestBuild?.status) - val inLoadingState = listOf(WorkspaceStatus.STARTING, WorkspaceStatus.CANCELING, WorkspaceStatus.DELETING, WorkspaceStatus.STOPPING).contains(workspaceWithAgent?.workspace?.latestBuild?.status) // We only display an API error on the first workspace rather than duplicating it on each workspace. if (deploymentError == null || showError) { row { - if (inLoadingState) { - icon(AnimatedIcon.Default()) - } if (status.third != null) { icon(status.third!!) } From 30204730ea2fbdabcbf74a7ab2e9f049aa613bd7 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Mon, 19 Aug 2024 16:57:01 -0500 Subject: [PATCH 11/11] use let instead of git add . --- .../views/CoderGatewayRecentWorkspaceConnectionsView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt index 47d6edfd..8abe6a8d 100644 --- a/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt +++ b/src/main/kotlin/com/coder/gateway/views/CoderGatewayRecentWorkspaceConnectionsView.kt @@ -213,8 +213,8 @@ class CoderGatewayRecentWorkspaceConnectionsView(private val setContentCallback: // We only display an API error on the first workspace rather than duplicating it on each workspace. if (deploymentError == null || showError) { row { - if (status.third != null) { - icon(status.third!!) + status.third?.let { + icon(it) } label("" + status.second + "").applyToComponent { foreground = status.first