Skip to content

impl: confirmation dialog for workspace deletion #179

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

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Changed

- workspaces status is now refresh every time Coder Toolbox becomes visible
- workspaces can no longer be removed by accident - users are now required to input the workspace name.

### Fixed

Expand Down
78 changes: 45 additions & 33 deletions src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -78,7 +79,7 @@ class CoderRemoteEnvironment(
fun asPairOfWorkspaceAndAgent(): Pair<Workspace, WorkspaceAgent> = Pair(workspace, agent)

private fun getAvailableActions(): List<ActionDescription> {
val actions = mutableListOf<Action>()
val actions = mutableListOf<ActionDescription>()
if (wsRawStatus.canStop()) {
actions.add(Action(context.i18n.ptrl("Open web terminal")) {
context.cs.launch {
Expand Down Expand Up @@ -143,6 +144,28 @@ 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?"),
context.i18n.pnotr(dialogText),
context.i18n.ptrl("Workspace name"),
TextType.General,
context.i18n.ptrl("OK"),
context.i18n.ptrl("Cancel")
)
if (confirmation != workspace.name) {
return@launch
}
deleteWorkspace()
}
})
return actions
}

Expand Down Expand Up @@ -272,43 +295,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()) "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."
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)
}
}

Expand Down
6 changes: 2 additions & 4 deletions src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -416,6 +416,4 @@ class CoderRemoteProvider(
LoadableState.Loading
}
}
}

private class CoderDelimiter(override val label: LocalizableString) : ActionDelimiter
}
5 changes: 5 additions & 0 deletions src/main/kotlin/com/coder/toolbox/views/CoderPage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -57,13 +58,17 @@ 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()
}
}

class CoderDelimiter(override val label: LocalizableString) : ActionDelimiter
11 changes: 10 additions & 1 deletion src/main/resources/localization/defaultMessages.po
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,13 @@ msgid "Headers"
msgstr ""

msgid "Body"
msgstr ""
msgstr ""

msgid "Delete workspace"
msgstr ""

msgid "Delete running workspace?"
msgstr ""

msgid "Workspace name"
msgstr ""
Loading
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