From 5a9c93f7ee32f3e334562baf94717ce5ea748b8b Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Tue, 11 Mar 2025 22:46:43 +0200 Subject: [PATCH 1/7] fix: Start button is active when template is outdated - Start button is active even though it doesn't do anything when clicked when the workspace template has updates. - resolves #31 --- src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt index 82256be..f39fc18 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt @@ -68,7 +68,7 @@ class CoderRemoteEnvironment( } } }, - Action(context.i18n.ptrl("Start"), enabled = { wsRawStatus.canStart() }) { + Action(context.i18n.ptrl("Start"), enabled = { wsRawStatus.canStart() && !workspace.outdated }) { val build = client.startWorkspace(workspace) workspace = workspace.copy(latestBuild = build) update(workspace, agent) From 7559cbb8bbe309d8625f9a034a31dad1763f9573 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Tue, 11 Mar 2025 22:48:19 +0200 Subject: [PATCH 2/7] fix: rename `Update` button - to `Update and start` to reflect that the it also starts the workspace - more consistent with the web client --- src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt index f39fc18..776cb14 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt @@ -78,7 +78,7 @@ class CoderRemoteEnvironment( workspace = workspace.copy(latestBuild = build) update(workspace, agent) }, - Action(context.i18n.ptrl("Update"), enabled = { workspace.outdated }) { + Action(context.i18n.ptrl("Update and start"), enabled = { workspace.outdated }) { val build = client.updateWorkspace(workspace) workspace = workspace.copy(latestBuild = build) update(workspace, agent) From 38bcbba769259e2a7814e7f67664ed9153ec3f6a Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Tue, 11 Mar 2025 23:40:55 +0200 Subject: [PATCH 3/7] impl: icon support for light & dark theme - LAF support in Toolbox is quite primitive, it turns out icon support for light and dark themes is enabled by a masked flag on the icons - the mask flag controls whether the svg colors are inverted in light&dark themes. --- CHANGELOG.md | 3 ++- .../kotlin/com/coder/toolbox/CoderRemoteProvider.kt | 11 +++++++++-- src/main/kotlin/com/coder/toolbox/views/CoderPage.kt | 8 ++++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6867b6f..0bb2520 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,4 +5,5 @@ ### Added - initial support for JetBrains Toolbox 2.6.0.38311 with the possibility to manage the workspaces - i.e. start, stop, - update and delete actions and also quick shortcuts to templates, web terminal and dashboard. \ No newline at end of file + update and delete actions and also quick shortcuts to templates, web terminal and dashboard. +- support for light & dark themes \ No newline at end of file diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt index a449c39..a30d3fb 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt @@ -17,6 +17,7 @@ import com.coder.toolbox.views.NewEnvironmentPage import com.coder.toolbox.views.SignInPage import com.coder.toolbox.views.TokenPage import com.jetbrains.toolbox.api.core.ui.icons.SvgIcon +import com.jetbrains.toolbox.api.core.ui.icons.SvgIcon.IconType import com.jetbrains.toolbox.api.core.util.LoadableState import com.jetbrains.toolbox.api.remoteDev.ProviderVisibilityState import com.jetbrains.toolbox.api.remoteDev.RemoteProvider @@ -181,10 +182,16 @@ class CoderRemoteProvider( } override val svgIcon: SvgIcon = - SvgIcon(this::class.java.getResourceAsStream("/icon.svg")?.readAllBytes() ?: byteArrayOf()) + SvgIcon( + this::class.java.getResourceAsStream("/icon.svg")?.readAllBytes() ?: byteArrayOf(), + type = IconType.Masked + ) override val noEnvironmentsSvgIcon: SvgIcon? = - SvgIcon(this::class.java.getResourceAsStream("/icon.svg")?.readAllBytes() ?: byteArrayOf()) + SvgIcon( + this::class.java.getResourceAsStream("/icon.svg")?.readAllBytes() ?: byteArrayOf(), + type = IconType.Masked + ) /** * TODO@JB: It would be nice to show "loading workspaces" at first but it diff --git a/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt b/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt index 6a1c4e3..53b55ea 100644 --- a/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt +++ b/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt @@ -2,6 +2,7 @@ package com.coder.toolbox.views import com.coder.toolbox.CoderToolboxContext import com.jetbrains.toolbox.api.core.ui.icons.SvgIcon +import com.jetbrains.toolbox.api.core.ui.icons.SvgIcon.IconType import com.jetbrains.toolbox.api.localization.LocalizableString import com.jetbrains.toolbox.api.ui.actions.RunnableActionDescription import com.jetbrains.toolbox.api.ui.components.UiField @@ -46,9 +47,12 @@ abstract class CoderPage( * This seems to only work on the first page. */ override val svgIcon: SvgIcon? = if (showIcon) { - SvgIcon(this::class.java.getResourceAsStream("/icon.svg")?.readAllBytes() ?: byteArrayOf()) + SvgIcon( + this::class.java.getResourceAsStream("/icon.svg")?.readAllBytes() ?: byteArrayOf(), + type = IconType.Masked + ) } else { - SvgIcon(byteArrayOf()) + SvgIcon(byteArrayOf(), type = IconType.Masked) } /** From efea6671259d2a0f52d0bb57401f119a7ef4caa0 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Tue, 11 Mar 2025 23:53:04 +0200 Subject: [PATCH 4/7] fix: update localization bundle --- src/main/resources/localization/defaultMessages.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/localization/defaultMessages.po b/src/main/resources/localization/defaultMessages.po index 837e2a0..5c86a06 100644 --- a/src/main/resources/localization/defaultMessages.po +++ b/src/main/resources/localization/defaultMessages.po @@ -73,7 +73,7 @@ msgstr "" msgid "Stop" msgstr "" -msgid "Update" +msgid "Update and start" msgstr "" msgid "Settings" From 5f1756211c76ebf09c18f8a5621ee0946cf34c4a Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Wed, 12 Mar 2025 22:10:29 +0200 Subject: [PATCH 5/7] impl: hide unavailable actions - instead of disabling the env actions when they are not available we should hide them instead. - resolves #31 --- .../coder/toolbox/CoderRemoteEnvironment.kt | 78 +++++++++++-------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt index 776cb14..d824898 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt @@ -45,44 +45,54 @@ class CoderRemoteEnvironment( override val actionsList: MutableStateFlow> = MutableStateFlow(getAvailableActions()) - private fun getAvailableActions(): List = listOf( - Action(context.i18n.ptrl("Open web terminal")) { - context.cs.launch { - BrowserUtil.browse(client.url.withPath("/${workspace.ownerName}/$name/terminal").toString()) { - context.ui.showErrorInfoPopup(it) + private fun getAvailableActions(): List { + val actions = mutableListOf( + Action(context.i18n.ptrl("Open web terminal")) { + context.cs.launch { + BrowserUtil.browse(client.url.withPath("/${workspace.ownerName}/$name/terminal").toString()) { + context.ui.showErrorInfoPopup(it) + } } - } - }, - Action(context.i18n.ptrl("Open in dashboard")) { - context.cs.launch { - BrowserUtil.browse(client.url.withPath("/@${workspace.ownerName}/${workspace.name}").toString()) { - context.ui.showErrorInfoPopup(it) + }, + Action(context.i18n.ptrl("Open in dashboard")) { + context.cs.launch { + BrowserUtil.browse(client.url.withPath("/@${workspace.ownerName}/${workspace.name}").toString()) { + context.ui.showErrorInfoPopup(it) + } } - } - }, + }, - Action(context.i18n.ptrl("View template")) { - context.cs.launch { - BrowserUtil.browse(client.url.withPath("/templates/${workspace.templateName}").toString()) { - context.ui.showErrorInfoPopup(it) + Action(context.i18n.ptrl("View template")) { + context.cs.launch { + BrowserUtil.browse(client.url.withPath("/templates/${workspace.templateName}").toString()) { + context.ui.showErrorInfoPopup(it) + } } - } - }, - Action(context.i18n.ptrl("Start"), enabled = { wsRawStatus.canStart() && !workspace.outdated }) { - val build = client.startWorkspace(workspace) - workspace = workspace.copy(latestBuild = build) - update(workspace, agent) - }, - Action(context.i18n.ptrl("Stop"), enabled = { wsRawStatus.canStop() }) { - val build = client.stopWorkspace(workspace) - workspace = workspace.copy(latestBuild = build) - update(workspace, agent) - }, - Action(context.i18n.ptrl("Update and start"), enabled = { workspace.outdated }) { - val build = client.updateWorkspace(workspace) - workspace = workspace.copy(latestBuild = build) - update(workspace, agent) - }) + }) + if (wsRawStatus.canStart() && !workspace.outdated) { + actions.add(Action(context.i18n.ptrl("Start")) { + val build = client.startWorkspace(workspace) + workspace = workspace.copy(latestBuild = build) + update(workspace, agent) + }) + } + if (wsRawStatus.canStop()) { + actions.add(Action(context.i18n.ptrl("Stop")) { + val build = client.stopWorkspace(workspace) + workspace = workspace.copy(latestBuild = build) + update(workspace, agent) + }) + } + if (workspace.outdated) { + actions.add(Action(context.i18n.ptrl("Update and start")) { + val build = client.updateWorkspace(workspace) + workspace = workspace.copy(latestBuild = build) + update(workspace, agent) + }) + } + + return actions + } /** * Update the workspace/agent status to the listeners, if it has changed. From 55446410c11a95e8e61d1d4e6e0c40dfd185b4f7 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Wed, 12 Mar 2025 22:32:46 +0200 Subject: [PATCH 6/7] refactor: update already assigns the workspace reference --- .../kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt index d824898..810a273 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt @@ -72,22 +72,19 @@ class CoderRemoteEnvironment( if (wsRawStatus.canStart() && !workspace.outdated) { actions.add(Action(context.i18n.ptrl("Start")) { val build = client.startWorkspace(workspace) - workspace = workspace.copy(latestBuild = build) - update(workspace, agent) + update(workspace.copy(latestBuild = build), agent) }) } if (wsRawStatus.canStop()) { actions.add(Action(context.i18n.ptrl("Stop")) { val build = client.stopWorkspace(workspace) - workspace = workspace.copy(latestBuild = build) - update(workspace, agent) + update(workspace.copy(latestBuild = build), agent) }) } if (workspace.outdated) { actions.add(Action(context.i18n.ptrl("Update and start")) { val build = client.updateWorkspace(workspace) - workspace = workspace.copy(latestBuild = build) - update(workspace, agent) + update(workspace.copy(latestBuild = build), agent) }) } From 3285347b2d6c7258c38ba6533ecae0b6ec021d20 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Wed, 12 Mar 2025 23:58:25 +0200 Subject: [PATCH 7/7] impl: support for `Update and restart` action - when workspace is running and is outdated - resolves #33 --- .../coder/toolbox/CoderRemoteEnvironment.kt | 40 +++++++++++-------- .../resources/localization/defaultMessages.po | 3 ++ 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt index 810a273..d9e7d95 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt @@ -69,25 +69,33 @@ class CoderRemoteEnvironment( } } }) - if (wsRawStatus.canStart() && !workspace.outdated) { - actions.add(Action(context.i18n.ptrl("Start")) { - val build = client.startWorkspace(workspace) - update(workspace.copy(latestBuild = build), agent) - }) + + if (wsRawStatus.canStart()) { + if (workspace.outdated) { + actions.add(Action(context.i18n.ptrl("Update and start")) { + val build = client.updateWorkspace(workspace) + update(workspace.copy(latestBuild = build), agent) + }) + } else { + actions.add(Action(context.i18n.ptrl("Start")) { + val build = client.startWorkspace(workspace) + update(workspace.copy(latestBuild = build), agent) + }) + } } if (wsRawStatus.canStop()) { - actions.add(Action(context.i18n.ptrl("Stop")) { - val build = client.stopWorkspace(workspace) - update(workspace.copy(latestBuild = build), agent) - }) - } - if (workspace.outdated) { - actions.add(Action(context.i18n.ptrl("Update and start")) { - val build = client.updateWorkspace(workspace) - update(workspace.copy(latestBuild = build), agent) - }) + if (workspace.outdated) { + actions.add(Action(context.i18n.ptrl("Update and restart")) { + val build = client.updateWorkspace(workspace) + update(workspace.copy(latestBuild = build), agent) + }) + } else { + actions.add(Action(context.i18n.ptrl("Stop")) { + val build = client.stopWorkspace(workspace) + update(workspace.copy(latestBuild = build), agent) + }) + } } - return actions } diff --git a/src/main/resources/localization/defaultMessages.po b/src/main/resources/localization/defaultMessages.po index 5c86a06..aa96e05 100644 --- a/src/main/resources/localization/defaultMessages.po +++ b/src/main/resources/localization/defaultMessages.po @@ -76,6 +76,9 @@ msgstr "" msgid "Update and start" msgstr "" +msgid "Update and restart" +msgstr "" + msgid "Settings" msgstr ""