From 05be524e75c1edad42ff83759753bd127f38e569 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Wed, 20 Aug 2025 00:09:12 +0300 Subject: [PATCH 1/4] chore: improve delete confirmation dialog The message is now english friendly and more clear. See issue #178 --- CHANGELOG.md | 1 + src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e87ca97..dc0da95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Changed - workspaces status is now refresh every time Coder Toolbox becomes visible +- improved workspace delete confirmation dialog ### Fixed diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt index f8b3a17..8dbfb11 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt @@ -277,8 +277,8 @@ class CoderRemoteEnvironment( override val cancelButtonText: String = "Cancel" override val confirmButtonText: String = "Delete" override val message: String = - if (wsRawStatus.canStop()) "Workspace will be closed and all the information will be lost, including all files, unsaved changes, historical info and usage data." - else "All the information in this workspace will be lost, including all files, unsaved changes, historical info and usage data." + if (wsRawStatus.canStop()) "This will close the workspace and remove all its information, including files, unsaved changes, history, and usage data." + else "This will remove all information from the workspace, including files, unsaved changes, history, and usage data." override val title: String = if (wsRawStatus.canStop()) "Delete running workspace?" else "Delete workspace?" } } From e63fd0784ee952359decc1f06d3e0af82a3546c8 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Thu, 21 Aug 2025 21:54:12 +0300 Subject: [PATCH 2/4] impl: confirmation dialog for workspace deletion Users are now required to confirm the workspace name if they want to delete a workspace. This is in order to avoid any accidental removals. --- CHANGELOG.md | 2 +- .../coder/toolbox/CoderRemoteEnvironment.kt | 74 ++++++++++--------- .../com/coder/toolbox/CoderRemoteProvider.kt | 6 +- .../com/coder/toolbox/views/CoderPage.kt | 3 + .../resources/localization/defaultMessages.po | 17 ++++- 5 files changed, 63 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc0da95..982be3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ### Changed - workspaces status is now refresh every time Coder Toolbox becomes visible -- improved workspace delete confirmation dialog +- workspaces can no longer be removed by accident - users are now required to input the workspace name. ### Fixed diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt index 8dbfb11..5e69faf 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt @@ -12,17 +12,18 @@ import com.coder.toolbox.sdk.v2.models.WorkspaceAgent import com.coder.toolbox.util.waitForFalseWithTimeout import com.coder.toolbox.util.withPath import com.coder.toolbox.views.Action +import com.coder.toolbox.views.CoderDelimiter import com.coder.toolbox.views.EnvironmentView import com.jetbrains.toolbox.api.localization.LocalizableString import com.jetbrains.toolbox.api.remoteDev.AfterDisconnectHook import com.jetbrains.toolbox.api.remoteDev.BeforeConnectionHook -import com.jetbrains.toolbox.api.remoteDev.DeleteEnvironmentConfirmationParams import com.jetbrains.toolbox.api.remoteDev.EnvironmentVisibilityState import com.jetbrains.toolbox.api.remoteDev.RemoteProviderEnvironment import com.jetbrains.toolbox.api.remoteDev.environments.EnvironmentContentsView import com.jetbrains.toolbox.api.remoteDev.states.EnvironmentDescription import com.jetbrains.toolbox.api.remoteDev.states.RemoteEnvironmentState import com.jetbrains.toolbox.api.ui.actions.ActionDescription +import com.jetbrains.toolbox.api.ui.components.TextType import com.squareup.moshi.Moshi import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -78,7 +79,7 @@ class CoderRemoteEnvironment( fun asPairOfWorkspaceAndAgent(): Pair = Pair(workspace, agent) private fun getAvailableActions(): List { - val actions = mutableListOf() + val actions = mutableListOf() if (wsRawStatus.canStop()) { actions.add(Action(context.i18n.ptrl("Open web terminal")) { context.cs.launch { @@ -143,6 +144,24 @@ class CoderRemoteEnvironment( } }) } + actions.add(CoderDelimiter(context.i18n.pnotr(""))) + actions.add(Action(context.i18n.ptrl("Delete workspace")) { + context.cs.launch { + val confirmation = context.ui.showTextInputPopup( + if (wsRawStatus.canStop()) context.i18n.ptrl("Delete running workspace?") else context.i18n.ptrl("Delete workspace?"), + if (wsRawStatus.canStop()) context.i18n.ptrl("This will close the workspace and remove all its information, including files, unsaved changes, history, and usage data.") + else context.i18n.ptrl("This will remove all information from the workspace, including files, unsaved changes, history, and usage data."), + context.i18n.ptrl("Workspace name"), + TextType.General, + context.i18n.ptrl("OK"), + context.i18n.ptrl("Cancel") + ) + if (confirmation != workspace.name) { + return@launch + } + deleteWorkspace() + } + }) return actions } @@ -272,43 +291,32 @@ class CoderRemoteEnvironment( return false } - override fun getDeleteEnvironmentConfirmationParams(): DeleteEnvironmentConfirmationParams? { - return object : DeleteEnvironmentConfirmationParams { - override val cancelButtonText: String = "Cancel" - override val confirmButtonText: String = "Delete" - override val message: String = - if (wsRawStatus.canStop()) "This will close the workspace and remove all its information, including files, unsaved changes, history, and usage data." - else "This will remove all information from the workspace, including files, unsaved changes, history, and usage data." - override val title: String = if (wsRawStatus.canStop()) "Delete running workspace?" else "Delete workspace?" - } - } + override val deleteActionFlow: StateFlow<(() -> Unit)?> = MutableStateFlow(null) - override val deleteActionFlow: StateFlow<(() -> Unit)?> = MutableStateFlow { - context.cs.launch { - try { - client.removeWorkspace(workspace) - // mark the env as deleting otherwise we will have to - // wait for the poller to update the status in the next 5 seconds - state.update { - WorkspaceAndAgentStatus.DELETING.toRemoteEnvironmentState(context) - } + suspend fun deleteWorkspace() { + try { + client.removeWorkspace(workspace) + // mark the env as deleting otherwise we will have to + // wait for the poller to update the status in the next 5 seconds + state.update { + WorkspaceAndAgentStatus.DELETING.toRemoteEnvironmentState(context) + } - context.cs.launch { - withTimeout(5.minutes) { - var workspaceStillExists = true - while (context.cs.isActive && workspaceStillExists) { - if (wsRawStatus == WorkspaceAndAgentStatus.DELETING || wsRawStatus == WorkspaceAndAgentStatus.DELETED) { - workspaceStillExists = false - context.envPageManager.showPluginEnvironmentsPage() - } else { - delay(1.seconds) - } + context.cs.launch { + withTimeout(5.minutes) { + var workspaceStillExists = true + while (context.cs.isActive && workspaceStillExists) { + if (wsRawStatus == WorkspaceAndAgentStatus.DELETING || wsRawStatus == WorkspaceAndAgentStatus.DELETED) { + workspaceStillExists = false + context.envPageManager.showPluginEnvironmentsPage() + } else { + delay(1.seconds) } } } - } catch (e: APIResponseException) { - context.ui.showErrorInfoPopup(e) } + } catch (e: APIResponseException) { + context.ui.showErrorInfoPopup(e) } } diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt index 596255e..2584117 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt @@ -11,6 +11,7 @@ import com.coder.toolbox.util.waitForTrue import com.coder.toolbox.util.withPath import com.coder.toolbox.views.Action import com.coder.toolbox.views.CoderCliSetupWizardPage +import com.coder.toolbox.views.CoderDelimiter import com.coder.toolbox.views.CoderSettingsPage import com.coder.toolbox.views.NewEnvironmentPage import com.coder.toolbox.views.state.CoderCliSetupWizardState @@ -21,7 +22,6 @@ import com.jetbrains.toolbox.api.core.util.LoadableState import com.jetbrains.toolbox.api.localization.LocalizableString import com.jetbrains.toolbox.api.remoteDev.ProviderVisibilityState import com.jetbrains.toolbox.api.remoteDev.RemoteProvider -import com.jetbrains.toolbox.api.ui.actions.ActionDelimiter import com.jetbrains.toolbox.api.ui.actions.ActionDescription import com.jetbrains.toolbox.api.ui.components.UiPage import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -416,6 +416,4 @@ class CoderRemoteProvider( LoadableState.Loading } } -} - -private class CoderDelimiter(override val label: LocalizableString) : ActionDelimiter \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt b/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt index 363d618..b65013f 100644 --- a/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt +++ b/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt @@ -4,6 +4,7 @@ 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.ActionDelimiter import com.jetbrains.toolbox.api.ui.actions.RunnableActionDescription import com.jetbrains.toolbox.api.ui.components.UiPage import kotlinx.coroutines.flow.MutableStateFlow @@ -67,3 +68,5 @@ class Action( actionBlock() } } + +class CoderDelimiter(override val label: LocalizableString) : ActionDelimiter \ No newline at end of file diff --git a/src/main/resources/localization/defaultMessages.po b/src/main/resources/localization/defaultMessages.po index 8aabe3f..3ef44a6 100644 --- a/src/main/resources/localization/defaultMessages.po +++ b/src/main/resources/localization/defaultMessages.po @@ -179,4 +179,19 @@ msgid "Headers" msgstr "" msgid "Body" -msgstr "" \ No newline at end of file +msgstr "" + +msgid "Delete workspace" +msgstr "" + +msgid "Delete running workspace?" +msgstr "" + +msgid "This will close the workspace and remove all its information, including files, unsaved changes, history, and usage data." +msgstr "" + +msgid "This will remove all information from the workspace, including files, unsaved changes, history, and usage data." +msgstr "" + +msgid "Workspace name" +msgstr "" From ad053d1d12375740b1c3c38bb118d8d4fccb3f7f Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Thu, 21 Aug 2025 22:48:29 +0300 Subject: [PATCH 3/4] impl: highlight in red the "Delete workspace" action Similar to the original "Delete" button provided by JetBrains. --- src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt | 2 +- src/main/kotlin/com/coder/toolbox/views/CoderPage.kt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt index 5e69faf..552a3fa 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt @@ -145,7 +145,7 @@ class CoderRemoteEnvironment( }) } actions.add(CoderDelimiter(context.i18n.pnotr(""))) - actions.add(Action(context.i18n.ptrl("Delete workspace")) { + actions.add(Action(context.i18n.ptrl("Delete workspace"), highlightInRed = true) { context.cs.launch { val confirmation = context.ui.showTextInputPopup( if (wsRawStatus.canStop()) context.i18n.ptrl("Delete running workspace?") else context.i18n.ptrl("Delete workspace?"), diff --git a/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt b/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt index b65013f..1fd4cc3 100644 --- a/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt +++ b/src/main/kotlin/com/coder/toolbox/views/CoderPage.kt @@ -58,12 +58,14 @@ abstract class CoderPage( class Action( description: LocalizableString, closesPage: Boolean = false, + highlightInRed: Boolean = false, enabled: () -> Boolean = { true }, private val actionBlock: () -> Unit, ) : RunnableActionDescription { override val label: LocalizableString = description override val shouldClosePage: Boolean = closesPage override val isEnabled: Boolean = enabled() + override val isDangerous: Boolean = highlightInRed override fun run() { actionBlock() } From 3df7c526b161643456c279b240167d889f6f0ccc Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Thu, 21 Aug 2025 23:01:11 +0300 Subject: [PATCH 4/4] chore: improve delete dialog message Make it clear to the user what does he need to type. --- .../kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt | 8 ++++++-- src/main/resources/localization/defaultMessages.po | 6 ------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt index 552a3fa..91553e0 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt @@ -147,10 +147,14 @@ class CoderRemoteEnvironment( actions.add(CoderDelimiter(context.i18n.pnotr(""))) actions.add(Action(context.i18n.ptrl("Delete workspace"), highlightInRed = true) { context.cs.launch { + var dialogText = + if (wsRawStatus.canStop()) "This will close the workspace and remove all its information, including files, unsaved changes, history, and usage data." + else "This will remove all information from the workspace, including files, unsaved changes, history, and usage data." + dialogText += "\n\nType \"${workspace.name}\" below to confirm:" + val confirmation = context.ui.showTextInputPopup( if (wsRawStatus.canStop()) context.i18n.ptrl("Delete running workspace?") else context.i18n.ptrl("Delete workspace?"), - if (wsRawStatus.canStop()) context.i18n.ptrl("This will close the workspace and remove all its information, including files, unsaved changes, history, and usage data.") - else context.i18n.ptrl("This will remove all information from the workspace, including files, unsaved changes, history, and usage data."), + context.i18n.pnotr(dialogText), context.i18n.ptrl("Workspace name"), TextType.General, context.i18n.ptrl("OK"), diff --git a/src/main/resources/localization/defaultMessages.po b/src/main/resources/localization/defaultMessages.po index 3ef44a6..29351e3 100644 --- a/src/main/resources/localization/defaultMessages.po +++ b/src/main/resources/localization/defaultMessages.po @@ -187,11 +187,5 @@ msgstr "" msgid "Delete running workspace?" msgstr "" -msgid "This will close the workspace and remove all its information, including files, unsaved changes, history, and usage data." -msgstr "" - -msgid "This will remove all information from the workspace, including files, unsaved changes, history, and usage data." -msgstr "" - msgid "Workspace name" msgstr "" pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy