@@ -21,24 +21,29 @@ import com.jetbrains.toolbox.api.core.PluginSecretStore
2121import com.jetbrains.toolbox.api.core.PluginSettingsStore
2222import com.jetbrains.toolbox.api.core.ServiceLocator
2323import com.jetbrains.toolbox.api.core.ui.icons.SvgIcon
24+ import com.jetbrains.toolbox.api.core.util.LoadableState
25+ import com.jetbrains.toolbox.api.localization.LocalizableStringFactory
2426import com.jetbrains.toolbox.api.remoteDev.ProviderVisibilityState
25- import com.jetbrains.toolbox.api.remoteDev.RemoteEnvironmentConsumer
2627import com.jetbrains.toolbox.api.remoteDev.RemoteProvider
28+ import com.jetbrains.toolbox.api.remoteDev.RemoteProviderEnvironment
2729import com.jetbrains.toolbox.api.remoteDev.ui.EnvironmentUiPageManager
2830import com.jetbrains.toolbox.api.ui.ToolboxUi
29- import com.jetbrains.toolbox.api.ui.actions.RunnableActionDescription
30- import com.jetbrains.toolbox.api.ui.components.AccountDropdownField
31+ import com.jetbrains.toolbox.api.ui.actions.ActionDescription
3132import com.jetbrains.toolbox.api.ui.components.UiPage
3233import kotlinx.coroutines.CoroutineScope
3334import kotlinx.coroutines.Job
3435import kotlinx.coroutines.delay
36+ import kotlinx.coroutines.flow.MutableStateFlow
37+ import kotlinx.coroutines.flow.StateFlow
3538import kotlinx.coroutines.isActive
3639import kotlinx.coroutines.launch
3740import okhttp3.OkHttpClient
3841import java.net.URI
3942import java.net.URL
4043import kotlin.coroutines.cancellation.CancellationException
4144import kotlin.time.Duration.Companion.seconds
45+ import com.jetbrains.toolbox.api.ui.components.AccountDropdownField as DropDownMenu
46+ import com.jetbrains.toolbox.api.ui.components.AccountDropdownField as dropDownFactory
4247
4348class CoderRemoteProvider (
4449 private val serviceLocator : ServiceLocator ,
@@ -47,10 +52,10 @@ class CoderRemoteProvider(
4752 private val logger = CoderLoggerFactory .getLogger(javaClass)
4853
4954 private val ui: ToolboxUi = serviceLocator.getService(ToolboxUi ::class .java)
50- private val consumer: RemoteEnvironmentConsumer = serviceLocator.getService(RemoteEnvironmentConsumer ::class .java)
5155 private val coroutineScope: CoroutineScope = serviceLocator.getService(CoroutineScope ::class .java)
5256 private val settingsStore: PluginSettingsStore = serviceLocator.getService(PluginSettingsStore ::class .java)
5357 private val secretsStore: PluginSecretStore = serviceLocator.getService(PluginSecretStore ::class .java)
58+ private val i18n = serviceLocator.getService(LocalizableStringFactory ::class .java)
5459
5560 // Current polling job.
5661 private var pollJob: Job ? = null
@@ -61,8 +66,8 @@ class CoderRemoteProvider(
6166 private val settings: CoderSettings = CoderSettings (settingsService)
6267 private val secrets: CoderSecretsService = CoderSecretsService (secretsStore)
6368 private val settingsPage: CoderSettingsPage = CoderSettingsPage (settingsService)
64- private val dialogUi = DialogUi (settings, ui )
65- private val linkHandler = LinkHandler (settings, httpClient, dialogUi)
69+ private val dialogUi = DialogUi (serviceLocator, settings )
70+ private val linkHandler = LinkHandler (serviceLocator, settings, httpClient, dialogUi)
6671
6772 // The REST client, if we are signed in
6873 private var client: CoderRestClient ? = null
@@ -75,6 +80,10 @@ class CoderRemoteProvider(
7580 // On the first load, automatically log in if we can.
7681 private var firstRun = true
7782
83+ override val environments: MutableStateFlow <LoadableState <List <RemoteProviderEnvironment >>> = MutableStateFlow (
84+ LoadableState .Loading
85+ )
86+
7887 /* *
7988 * With the provided client, start polling for workspaces. Every time a new
8089 * workspace is added, reconfigure SSH using the provided cli (including the
@@ -84,7 +93,7 @@ class CoderRemoteProvider(
8493 while (isActive) {
8594 try {
8695 logger.debug(" Fetching workspace agents from {}" , client.url)
87- val environments = client.workspaces().flatMap { ws ->
96+ val resolvedEnvironments = client.workspaces().flatMap { ws ->
8897 // Agents are not included in workspaces that are off
8998 // so fetch them separately.
9099 when (ws.latestBuild.status) {
@@ -117,16 +126,16 @@ class CoderRemoteProvider(
117126 // Reconfigure if a new environment is found.
118127 // TODO@JB: Should we use the add/remove listeners instead?
119128 val newEnvironments = lastEnvironments
120- ?.let { environments .subtract(it) }
121- ? : environments
129+ ?.let { resolvedEnvironments .subtract(it) }
130+ ? : resolvedEnvironments
122131 if (newEnvironments.isNotEmpty()) {
123132 logger.info(" Found new environment(s), reconfiguring CLI: {}" , newEnvironments)
124133 cli.configSsh(newEnvironments.map { it.name }.toSet())
125134 }
126135
127- consumer.consumeEnvironments( environments, true )
136+ environments.value = LoadableState . Value (resolvedEnvironments.toList() )
128137
129- lastEnvironments = environments
138+ lastEnvironments = resolvedEnvironments
130139 } catch (_: CancellationException ) {
131140 logger.debug(" {} polling loop canceled" , client.url)
132141 break
@@ -155,21 +164,20 @@ class CoderRemoteProvider(
155164 /* *
156165 * A dropdown that appears at the top of the environment list to the right.
157166 */
158- override fun getAccountDropDown (): AccountDropdownField ? {
167+ override fun getAccountDropDown (): DropDownMenu ? {
159168 val username = client?.me?.username
160169 if (username != null ) {
161- return AccountDropdownField ( username, Runnable { logout() })
170+ return dropDownFactory(i18n.pnotr( username), { logout() })
162171 }
163172 return null
164173 }
165174
166- /* *
167- * List of actions that appear next to the account.
168- */
169- override fun getAdditionalPluginActions (): List <RunnableActionDescription > = listOf (
170- Action (" Settings" , closesPage = false ) {
171- ui.showUiPage(settingsPage)
172- },
175+ override val additionalPluginActions: StateFlow <List <ActionDescription >> = MutableStateFlow (
176+ listOf (
177+ Action (i18n.ptrl(" Settings" )) {
178+ ui.showUiPage(settingsPage)
179+ },
180+ )
173181 )
174182
175183 /* *
@@ -182,7 +190,7 @@ class CoderRemoteProvider(
182190 pollJob?.cancel()
183191 client = null
184192 lastEnvironments = null
185- consumer.consumeEnvironments (emptyList(), true )
193+ environments.value = LoadableState . Value (emptyList())
186194 }
187195
188196 override val svgIcon: SvgIcon =
@@ -226,20 +234,10 @@ class CoderRemoteProvider(
226234 */
227235 override fun setVisible (visibilityState : ProviderVisibilityState ) {}
228236
229- /* *
230- * Ignored; unsure if we should use this over the consumer we get passed in.
231- */
232- override fun addEnvironmentsListener (listener : RemoteEnvironmentConsumer ) {}
233-
234- /* *
235- * Ignored; unsure if we should use this over the consumer we get passed in.
236- */
237- override fun removeEnvironmentsListener (listener : RemoteEnvironmentConsumer ) {}
238-
239237 /* *
240238 * Handle incoming links (like from the dashboard).
241239 */
242- override fun handleUri (uri : URI ) {
240+ override suspend fun handleUri (uri : URI ) {
243241 val params = uri.toQueryParameters()
244242 coroutineScope.launch {
245243 val name = linkHandler.handle(params)
0 commit comments