diff --git a/src/main/kotlin/core/disc_storage.kt b/src/main/kotlin/core/disc_storage.kt index ce304ee..03b434e 100644 --- a/src/main/kotlin/core/disc_storage.kt +++ b/src/main/kotlin/core/disc_storage.kt @@ -8,7 +8,7 @@ import java.util.* // TODO tento file si zasluzi lasku private val folder = "${System.getProperty("user.home")}${File.separatorChar}MockdogMocks".also { File(it).mkdirs() } -private val mockFiles = mutableStateOf(emptyList()) +val mockFiles = mutableStateOf(emptyList()) fun saveFile(path: String, name: String, body: String) = try { // TODO nefunguje ak name v sebe obsahuje '_' diff --git a/src/main/kotlin/core/server.kt b/src/main/kotlin/core/server.kt index fe2a861..6d6aae8 100644 --- a/src/main/kotlin/core/server.kt +++ b/src/main/kotlin/core/server.kt @@ -8,6 +8,7 @@ import okhttp3.mockwebserver.Dispatcher import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.RecordedRequest +import ui.globalResponse import java.net.Inet4Address import java.net.InetAddress import java.net.NetworkInterface @@ -64,18 +65,22 @@ fun startServer(port: Int = 52242, inetAddress: InetAddress = getDefaultIpV4Addr requests.add(request) - val response: SentResponse = if(mockingEnabled.value) { - responses[request.id] = Loading - responses[request.id] = EditResponse(sendRealRequest(request)) - val value = queues.getOrPut(request.id) { ArrayBlockingQueue(1) }.take() - if(value is SentResponse) { - value + val response: SentResponse = if (globalResponse != null) { + SentResponse(status = globalResponse!!.code, body = globalResponse!!.body) + } else { + if(mockingEnabled.value) { + responses[request.id] = Loading + responses[request.id] = EditResponse(sendRealRequest(request)) + val value = queues.getOrPut(request.id) { ArrayBlockingQueue(1) }.take() + if(value is SentResponse) { + value + } else { + responses[request.id] = value + sendRealRequest(request) + } } else { - responses[request.id] = value sendRealRequest(request) } - } else { - sendRealRequest(request) } responses[request.id] = response diff --git a/src/main/kotlin/ui/app.kt b/src/main/kotlin/ui/app.kt index fb14f20..bbedb4e 100644 --- a/src/main/kotlin/ui/app.kt +++ b/src/main/kotlin/ui/app.kt @@ -1,15 +1,35 @@ package ui -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Surface +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.rememberDialogState +import core.mockFiles import core.mutable +import core.readFile import java.util.* +data class GlobalResponse( + val code: Int, + val name: String, + val body: String +) + +var globalResponse by mutableStateOf(null) +var globalResponseDialog by mutableStateOf(false) + @Composable fun App() { MaterialTheme( @@ -17,29 +37,146 @@ fun App() { typography = Typography ) { Surface(M.fillMaxWidth()) { - Row { - val leftPaneWidth = mutable(400.dp) - val selectedRequest = mutable(null) - - // Left pane - history + settings - LeftPane( - paneWidth = leftPaneWidth.value, - selected = selectedRequest.value, - onSelect = { selectedRequest.value = it }) - - // Movable divider - DraggableDivider( - width = leftPaneWidth.value, - onWidthChange = { leftPaneWidth.value = it }) - - // Detail - Surface( - modifier = M.fillMaxHeight().weight(1f), - color = C.background - ) { - selectedRequest.value?.let { Detail(id = it) } + Column { + // Global response status + globalResponse?.let { response -> + ContextMenuArea(items = { listOf(ContextMenuItem("Remove global response") { globalResponse = null }) }) { + Row( + modifier = M.fillMaxWidth().background(color = Orange), + horizontalArrangement = Arrangement.Center + ) { + Text( + text = "☺ Warning ☻ global response is set to ► ${response.code} - ${response.name}", + fontWeight = FontWeight.Bold) + } + } + } + + // App content + Row { + val leftPaneWidth = mutable(400.dp) + val selectedRequest = mutable(null) + + // Left pane - history + settings + LeftPane( + paneWidth = leftPaneWidth.value, + selected = selectedRequest.value, + onSelect = { selectedRequest.value = it }) + + // Movable divider + DraggableDivider( + width = leftPaneWidth.value, + onWidthChange = { leftPaneWidth.value = it }) + + // Detail + Surface( + modifier = M.fillMaxHeight().weight(1f), + color = C.background + ) { + selectedRequest.value?.let { Detail(id = it) } + } } } } + + if (globalResponseDialog) + GlobalResponseDialog() } +} + +@Composable +private fun GlobalResponseDialog() { + val (body, setBody) = mutable("") + val (mockDialog, setMockDialog) = mutable(false) + val lazyState = rememberLazyListState() + + Dialog( + state = rememberDialogState(size = DpSize(600.dp, 900.dp)), + onCloseRequest = { globalResponseDialog = false }, + title = "Global request response", + resizable = true, + content = { + Column ( + modifier = M.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + Text( + modifier = M.align(Alignment.CenterHorizontally), + text = "Set global response for every request.", + style = T.h6) + + // Insert own JSON + OutlinedTextField( + modifier = M.fillMaxWidth().padding(bottom = 16.dp).weight(1f), + label = { Text("JSON response body", style = T.caption) }, + colors = TextFieldDefaults.outlinedTextFieldColors(backgroundColor = C.surface), + value = body, + onValueChange = setBody) + + Button(onClick = { setMockDialog(true) }) { + Text(text = "Load saved mock") + } + + Text(text = "Response codes:") + + Box(modifier = M.fillMaxWidth().weight(1f)) { + LazyColumn(state = lazyState) { + allHttpCodes.forEach { entry -> + item { + Text( + text = "${entry.key} - ${entry.value}", + style = T.subtitle1, + modifier = M.fillMaxWidth().padding(vertical = 4.dp).clickable { + globalResponse = GlobalResponse( + code = entry.key, + name = entry.value, + body = body + ) + + globalResponseDialog = false + }) + } + } + } + + VerticalScrollbar( + modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(), + adapter = rememberScrollbarAdapter(lazyState) + ) + } + } + }) + + if (mockDialog) + Dialog( + state = rememberDialogState(size = DpSize(400.dp, 600.dp)), + onCloseRequest = { setMockDialog(false) }, + title = "Load saved mock", + resizable = true, + content = { + Column ( + modifier = M.fillMaxWidth().padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + Text( + modifier = M.align(Alignment.CenterHorizontally), + text = "Load saved mock from file", + style = T.subtitle1) + + Column(M.verticalScroll(rememberScrollState()).weight(1f)) { + mockFiles.value.filter { + it.contains('_') + }.forEach { + val requestPath = it.substringAfterLast('_') + Text( + modifier = M.fillMaxWidth().padding(vertical = 4.dp).clickable { + readFile(it)?.let(setBody) + setMockDialog(false) + }, + text = requestPath + ) + } + } + } + }) } \ No newline at end of file diff --git a/src/main/kotlin/ui/detail.kt b/src/main/kotlin/ui/detail.kt index 234c3e5..c87a327 100644 --- a/src/main/kotlin/ui/detail.kt +++ b/src/main/kotlin/ui/detail.kt @@ -362,7 +362,7 @@ private val httpCodes = mapOf( 200 to "Ok", 401 to "Unauthorised", 404 to "Not Found", 500 to "Internal Server Error" ) -private val allHttpCodes = mapOf( +val allHttpCodes = mapOf( 100 to "Continue", 101 to "Switching Protocols", 102 to "Processing", 103 to "Early Hints", 200 to "Ok", 201 to "Created", 202 to "Accepted", 203 to "Non-Authoritative Information", 204 to "No Content", 205 to "Reset Content", 206 to "Partial Content", 207 to "Multi Status", 208 to "Already Reported", 226 to "IM Used", 300 to "Multiple Choices", 301 to "Moved Permanently", 302 to "Found", 303 to "See Other", 304 to "Not Modified", 305 to "Use Proxy", 306 to "Unused", 307 to "Temporary Redirect", 308 to "Permanent Redirect", diff --git a/src/main/kotlin/ui/left_pane.kt b/src/main/kotlin/ui/left_pane.kt index f524cb2..8910e22 100644 --- a/src/main/kotlin/ui/left_pane.kt +++ b/src/main/kotlin/ui/left_pane.kt @@ -31,6 +31,9 @@ fun LeftPane( ) { Column(M.fillMaxHeight().width(paneWidth).background(C.surface)) { Column(M.weight(1f)) { + Button(onClick = { globalResponseDialog = true }) { + Text("Set global response TODO(do setting)") + } Row { Text( modifier = M.padding(16.dp).weight(1f),