@@ -26,6 +26,7 @@ import androidx.compose.ui.awt.ComposeDialog
2626import androidx.compose.ui.focus.FocusRequester
2727import androidx.compose.ui.focus.focusRequester
2828import androidx.compose.ui.unit.dp
29+ import com.vaticle.typedb.driver.api.TypeDBCredential
2930import com.vaticle.typedb.studio.framework.common.theme.Theme
3031import com.vaticle.typedb.studio.framework.material.ActionableList
3132import com.vaticle.typedb.studio.framework.material.Dialog
@@ -46,12 +47,13 @@ import com.vaticle.typedb.studio.framework.material.Tooltip
4647import com.vaticle.typedb.studio.service.Service
4748import com.vaticle.typedb.studio.service.common.util.Label
4849import com.vaticle.typedb.studio.service.common.util.Property
49- import com.vaticle.typedb.studio.service.common.util.Property.Server.TYPEDB_CORE
5050import com.vaticle.typedb.studio.service.common.util.Property.Server.TYPEDB_CLOUD
51+ import com.vaticle.typedb.studio.service.common.util.Property.Server.TYPEDB_CORE
5152import com.vaticle.typedb.studio.service.common.util.Sentence
5253import com.vaticle.typedb.studio.service.connection.DriverState.Status.CONNECTED
5354import com.vaticle.typedb.studio.service.connection.DriverState.Status.CONNECTING
5455import com.vaticle.typedb.studio.service.connection.DriverState.Status.DISCONNECTED
56+ import java.nio.file.Path
5557
5658object ServerDialog {
5759
@@ -69,6 +71,10 @@ object ServerDialog {
6971 var cloudAddresses: MutableList <String > = mutableStateListOf<String >().also {
7072 appData.cloudAddresses?.let { saved -> it.addAll(saved) }
7173 }
74+ var cloudAddressTranslation: MutableList <Pair <String , String >> = mutableStateListOf<Pair <String , String >>().also {
75+ appData.cloudAddressTranslation?.let { saved -> it.addAll(saved) }
76+ }
77+ var useCloudAddressTranslation: Boolean by mutableStateOf(appData.useCloudAddressTranslation ? : false )
7278 var username: String by mutableStateOf(appData.username ? : " " )
7379 var password: String by mutableStateOf(" " )
7480 var tlsEnabled: Boolean by mutableStateOf(appData.tlsEnabled ? : true )
@@ -77,37 +83,68 @@ object ServerDialog {
7783 override fun cancel () = Service .driver.connectServerDialog.close()
7884 override fun isValid (): Boolean = when (server) {
7985 TYPEDB_CORE -> coreAddress.isNotBlank() && addressFormatIsValid(coreAddress)
80- TYPEDB_CLOUD -> ! (cloudAddresses.isEmpty() || username.isBlank() || password.isBlank())
86+ TYPEDB_CLOUD -> username.isNotBlank() && password.isNotBlank() && if (useCloudAddressTranslation) {
87+ cloudAddressTranslation.isNotEmpty()
88+ } else {
89+ cloudAddresses.isNotEmpty()
90+ }
8191 }
8292
8393 override fun submit () {
8494 when (server) {
8595 TYPEDB_CORE -> Service .driver.tryConnectToTypeDBCoreAsync(coreAddress) {
8696 Service .driver.connectServerDialog.close()
8797 }
88- TYPEDB_CLOUD -> Service .driver.tryConnectToTypeDBCloudAsync(
89- cloudAddresses.toSet(), username, password, tlsEnabled, caCertificate
90- ) { Service .driver.connectServerDialog.close() }
98+ TYPEDB_CLOUD -> {
99+ val credentials = if (caCertificate.isBlank()) TypeDBCredential (username, password, tlsEnabled)
100+ else TypeDBCredential (username, password, Path .of(caCertificate))
101+ val onSuccess = { Service .driver.connectServerDialog.close() }
102+ if (useCloudAddressTranslation) {
103+ val firstAddress = cloudAddressTranslation.first().first
104+ Service .driver.tryConnectToTypeDBCloudAsync(" $username @$firstAddress " , cloudAddressTranslation.associate { it }, credentials, onSuccess)
105+ } else {
106+ val firstAddress = cloudAddresses.first()
107+ Service .driver.tryConnectToTypeDBCloudAsync(" $username @$firstAddress " , cloudAddresses.toSet(), credentials, onSuccess)
108+ }
109+ }
91110 }
92111 password = " "
93112 appData.server = server
94113 appData.coreAddress = coreAddress
95114 appData.cloudAddresses = cloudAddresses
115+ appData.cloudAddressTranslation = cloudAddressTranslation
116+ appData.useCloudAddressTranslation = useCloudAddressTranslation
96117 appData.username = username
97118 appData.tlsEnabled = tlsEnabled
98119 appData.caCertificate = caCertificate
99120 }
100121 }
101122
102123 private object AddAddressForm : Form.State() {
103- var value: String by mutableStateOf(" " )
124+ var server: String by mutableStateOf(" " )
125+ override fun cancel () = Service .driver.manageAddressesDialog.close()
126+ override fun isValid () = server.isNotBlank() && addressFormatIsValid(server) && ! state.cloudAddresses.contains(server)
127+
128+ override fun submit () {
129+ assert (isValid())
130+ state.cloudAddresses.add(server)
131+ server = " "
132+ }
133+ }
134+
135+ private object AddAddressTranslationForm : Form.State() {
136+ var server: String by mutableStateOf(" " )
137+ var translation: String by mutableStateOf(" " )
104138 override fun cancel () = Service .driver.manageAddressesDialog.close()
105- override fun isValid () = value.isNotBlank() && addressFormatIsValid(value) && ! state.cloudAddresses.contains(value)
139+ override fun isValid () = serverIsValid() && translationIsValid()
140+ fun serverIsValid () = server.isNotBlank() && addressFormatIsValid(server) && ! state.cloudAddressTranslation.any { it.first == server }
141+ fun translationIsValid () = translation.isNotBlank() && addressFormatIsValid(translation) && ! state.cloudAddressTranslation.any { it.second == translation }
106142
107143 override fun submit () {
108144 assert (isValid())
109- state.cloudAddresses.add(value)
110- value = " "
145+ state.cloudAddressTranslation.add(Pair (server, translation))
146+ server = " "
147+ translation = " "
111148 }
112149 }
113150
@@ -187,8 +224,9 @@ object ServerDialog {
187224 private fun ManageCloudAddressesButton (state : ConnectServerForm , shouldFocus : Boolean ) {
188225 val focusReq = if (shouldFocus) remember { FocusRequester () } else null
189226 Field (label = Label .ADDRESSES ) {
227+ val numAddresses = if (state.useCloudAddressTranslation) state.cloudAddressTranslation.size else state.cloudAddresses.size
190228 TextButton (
191- text = Label .MANAGE_CLOUD_ADDRESSES + " (${state.cloudAddresses.size} )" ,
229+ text = Label .MANAGE_CLOUD_ADDRESSES + " ($numAddresses )" ,
192230 focusReq = focusReq, leadingIcon = Form .IconArg (Icon .CONNECT_TO_TYPEDB ),
193231 enabled = Service .driver.isDisconnected
194232 ) {
@@ -205,11 +243,21 @@ object ServerDialog {
205243 Column (Modifier .fillMaxSize()) {
206244 Text (value = Sentence .MANAGE_ADDRESSES_MESSAGE , softWrap = true )
207245 Spacer (Modifier .height(Dialog .DIALOG_SPACING ))
208- CloudAddressList (Modifier .fillMaxWidth().weight(1f ))
209- Spacer (Modifier .height(Dialog .DIALOG_SPACING ))
210- AddCloudAddressForm ()
246+ if (state.useCloudAddressTranslation) {
247+ CloudAddressTranslationList (Modifier .fillMaxWidth().weight(1f ))
248+ Spacer (Modifier .height(Dialog .DIALOG_SPACING ))
249+ AddCloudAddressTranslationForm ()
250+ } else {
251+ CloudAddressList (Modifier .fillMaxWidth().weight(1f ))
252+ Spacer (Modifier .height(Dialog .DIALOG_SPACING ))
253+ AddCloudAddressForm ()
254+ }
211255 Spacer (Modifier .height(Dialog .DIALOG_SPACING * 2 ))
212256 Row (verticalAlignment = Alignment .Bottom ) {
257+ Text (value = Label .TRANSLATE_ADDRESSES )
258+ RowSpacer ()
259+ Checkbox (value = state.useCloudAddressTranslation) { state.useCloudAddressTranslation = it }
260+ RowSpacer ()
213261 Spacer (modifier = Modifier .weight(1f ))
214262 RowSpacer ()
215263 TextButton (text = Label .CLOSE ) { dialogState.close() }
@@ -224,12 +272,12 @@ object ServerDialog {
224272 Submission (AddAddressForm , modifier = Modifier .height(Form .FIELD_HEIGHT ), showButtons = false ) {
225273 Row {
226274 TextInputValidated (
227- value = AddAddressForm .value ,
275+ value = AddAddressForm .server ,
228276 placeholder = Label .DEFAULT_SERVER_ADDRESS ,
229- onValueChange = { AddAddressForm .value = it },
277+ onValueChange = { AddAddressForm .server = it },
230278 modifier = Modifier .weight(1f ).focusRequester(focusReq),
231279 invalidWarning = Label .ADDRESS_PORT_WARNING ,
232- validator = { AddAddressForm .value .isBlank() || addressFormatIsValid(AddAddressForm .value ) }
280+ validator = { AddAddressForm .server .isBlank() || addressFormatIsValid(AddAddressForm .server ) }
233281 )
234282 RowSpacer ()
235283 TextButton (text = Label .ADD , enabled = AddAddressForm .isValid()) { AddAddressForm .submit() }
@@ -238,9 +286,38 @@ object ServerDialog {
238286 LaunchedEffect (focusReq) { focusReq.requestFocus() }
239287 }
240288
289+ @Composable
290+ private fun AddCloudAddressTranslationForm () {
291+ val focusReq = remember { FocusRequester () }
292+ Submission (AddAddressTranslationForm , modifier = Modifier .height(Form .FIELD_HEIGHT ), showButtons = false ) {
293+ Row {
294+ TextInputValidated (
295+ value = AddAddressTranslationForm .server,
296+ placeholder = Label .DEFAULT_SERVER_ADDRESS ,
297+ onValueChange = { AddAddressTranslationForm .server = it },
298+ modifier = Modifier .weight(1f ).focusRequester(focusReq),
299+ invalidWarning = Label .ADDRESS_PORT_WARNING ,
300+ validator = { AddAddressTranslationForm .server.isBlank() || addressFormatIsValid(AddAddressTranslationForm .server) }
301+ )
302+ RowSpacer ()
303+ TextInputValidated (
304+ value = AddAddressTranslationForm .translation,
305+ placeholder = Label .DEFAULT_SERVER_ADDRESS ,
306+ onValueChange = { AddAddressTranslationForm .translation = it },
307+ modifier = Modifier .weight(1f ).focusRequester(focusReq),
308+ invalidWarning = Label .ADDRESS_PORT_WARNING ,
309+ validator = { AddAddressTranslationForm .translation.isBlank() || addressFormatIsValid(AddAddressTranslationForm .translation) }
310+ )
311+ RowSpacer ()
312+ TextButton (text = Label .ADD , enabled = AddAddressTranslationForm .isValid()) { AddAddressTranslationForm .submit() }
313+ }
314+ }
315+ LaunchedEffect (focusReq) { focusReq.requestFocus() }
316+ }
317+
241318 @Composable
242319 private fun CloudAddressList (modifier : Modifier ) = ActionableList .SingleButtonLayout (
243- items = state.cloudAddresses.toMutableList() ,
320+ items = state.cloudAddresses,
244321 modifier = modifier.border(1 .dp, Theme .studio.border),
245322 buttonSide = ActionableList .Side .RIGHT ,
246323 buttonFn = { address ->
@@ -252,6 +329,21 @@ object ServerDialog {
252329 }
253330 )
254331
332+ @Composable
333+ private fun CloudAddressTranslationList (modifier : Modifier ) = ActionableList .SingleButtonLayout (
334+ items = state.cloudAddressTranslation.map { " ${it.first} ⇒ ${it.second} " },
335+ modifier = modifier.border(1 .dp, Theme .studio.border),
336+ buttonSide = ActionableList .Side .RIGHT ,
337+ buttonFn = { address ->
338+ val parts = address.split(" ⇒ " , limit = 2 )
339+ Form .IconButtonArg (
340+ icon = Icon .REMOVE ,
341+ color = { Theme .studio.errorStroke },
342+ onClick = { state.cloudAddressTranslation.remove(parts[0 ] to parts[1 ]) }
343+ )
344+ }
345+ )
346+
255347 @Composable
256348 private fun UsernameFormField (state : ConnectServerForm ) = Field (label = Label .USERNAME ) {
257349 TextInput (
0 commit comments