Added input, hardware fields to data

This commit is contained in:
2026-08-09 17:25:18 +02:00
parent 8f4c6cc630
commit 60b1a69bc6
10 changed files with 184 additions and 36 deletions
@@ -110,7 +110,7 @@ object CLI {
data class FileData<T>(val file: InputStream, val parse: (String) -> T?) : IData<T> { data class FileData<T>(val file: InputStream, val parse: (String) -> T?) : IData<T> {
override fun toList(): List<T> { override fun toList(): List<T> {
val raw = file.bufferedReader().use { it.readText() }.split(',') val raw = file.bufferedReader().use { it.readText() }.split(',')
val parsed = ArrayList<T>(raw.size) val parsed = ArrayList<T>(raw.size)
val errors = mutableListOf<String>() val errors = mutableListOf<String>()
raw.forEach { raw.forEach {
@@ -201,6 +201,8 @@ object CLI {
option("--min", "--m", "--minutes", help = "Set the time unit to minutes").flag().convert { TimeUnit.MINUTES }, option("--min", "--m", "--minutes", help = "Set the time unit to minutes").flag().convert { TimeUnit.MINUTES },
option("--h", "--hour", help = "Set the time unit to hours").flag().convert { TimeUnit.HOURS } option("--h", "--hour", help = "Set the time unit to hours").flag().convert { TimeUnit.HOURS }
).single() ).single()
val input by option("--input", help = "Set the input used to benchmark")
val hardware by option("--hardware", "--hw", help = "Set the hardware on which the benchmark was run")
override fun run() = ProjectHandlers.newData(parent.finder, label, warmup, measurement, unit) override fun run() = ProjectHandlers.newData(parent.finder, label, warmup, measurement, unit)
} }
@@ -76,7 +76,7 @@ class Client private constructor(private val _auth: AuthProvider) {
println("Call to ${route.pattern} [$ctr] was cancelled") println("Call to ${route.pattern} [$ctr] was cancelled")
ErrorResponse(COROUTINE_CANCELLED).error() ErrorResponse(COROUTINE_CANCELLED).error()
} catch (e: Exception) { } catch (e: Exception) {
println("Call to ${route.pattern} [$ctr] ran into an exception") println("Call to ${route.pattern} [$ctr] ran into an exception: ${e.message}")
ErrorResponse(e.message ?: "Unknown error.").error() ErrorResponse(e.message ?: "Unknown error.").error()
} }
} }
@@ -30,7 +30,7 @@ interface IProjectRepo {
suspend fun deleteLabel(id: Uuid): Either<ErrorResponse, Unit> suspend fun deleteLabel(id: Uuid): Either<ErrorResponse, Unit>
suspend fun newEntry(label: Uuid, timestamp: Instant, warmups: List<Float>, measurements: List<Float>, suspend fun newEntry(label: Uuid, timestamp: Instant, warmups: List<Float>, measurements: List<Float>,
unit: TimeUnit): Either<ErrorResponse, EntryResponse> unit: TimeUnit, input: String, hardware: String): Either<ErrorResponse, EntryResponse>
suspend fun deleteEntry(id: Uuid): Either<ErrorResponse, Unit> suspend fun deleteEntry(id: Uuid): Either<ErrorResponse, Unit>
companion object { companion object {
@@ -54,9 +54,9 @@ interface IProjectRepo {
_client.callRoute(Routes.Label.delete, id).ignoreValue() _client.callRoute(Routes.Label.delete, id).ignoreValue()
override suspend fun newEntry(label: Uuid, timestamp: Instant, warmups: List<Float>, override suspend fun newEntry(label: Uuid, timestamp: Instant, warmups: List<Float>,
measurements: List<Float>, unit: TimeUnit measurements: List<Float>, unit: TimeUnit, input: String, hardware: String
): Either<ErrorResponse, EntryResponse> = ): Either<ErrorResponse, EntryResponse> =
_client.callRoute(Routes.Entry.new, EntryRequest(label, timestamp, _projectId, warmups, measurements, unit)) _client.callRoute(Routes.Entry.new, EntryRequest(label, timestamp, _projectId, warmups, measurements, unit, input, hardware))
override suspend fun deleteEntry(id: Uuid): Either<ErrorResponse, Unit> = override suspend fun deleteEntry(id: Uuid): Either<ErrorResponse, Unit> =
_client.callRoute(Routes.Entry.delete, id).ignoreValue() _client.callRoute(Routes.Entry.delete, id).ignoreValue()
@@ -57,11 +57,11 @@ class ProjectVM(
} }
data class Entry(val id: Uuid, val label: Label, val timeStamp: Instant, val warmups: List<Float>, data class Entry(val id: Uuid, val label: Label, val timeStamp: Instant, val warmups: List<Float>,
val measurements: List<Float>, val nativeUnit: TimeUnit) { val measurements: List<Float>, val nativeUnit: TimeUnit, val input: String, val hardware: String) {
companion object { companion object {
fun fromResponse(it: EntryResponse, map: Map<Uuid, Label>) = Entry( fun fromResponse(it: EntryResponse, map: Map<Uuid, Label>) = Entry(
it.id, map[it.labelId] ?: Label.invalid, it.timestamp, it.id, map[it.labelId] ?: Label.invalid, it.timestamp,
it.warmups, it.measurements, it.unit it.warmups, it.measurements, it.unit, it.input, it.hardware
) )
} }
} }
@@ -72,6 +72,8 @@ class ProjectVM(
private val _editable = mutableStateOf(false) private val _editable = mutableStateOf(false)
private val _labels = mutableStateOf(mapOf<Uuid, Label>()) private val _labels = mutableStateOf(mapOf<Uuid, Label>())
private val _entries = mutableStateOf(listOf<Entry>()) private val _entries = mutableStateOf(listOf<Entry>())
private val _inputs = mutableStateOf(setOf<String>())
private val _hardware = mutableStateOf(setOf<String>())
val name = _name.immutable() val name = _name.immutable()
val owner = _owner.immutable() val owner = _owner.immutable()
@@ -79,6 +81,8 @@ class ProjectVM(
val editable = _editable.immutable() val editable = _editable.immutable()
val labels = _labels.immutable() val labels = _labels.immutable()
val entries = _entries.immutable() val entries = _entries.immutable()
val inputs = _inputs.immutable()
val hardware = _hardware.immutable()
private var _job: Job? = null private var _job: Job? = null
@@ -101,7 +105,15 @@ class ProjectVM(
_public.value = it.isPublic _public.value = it.isPublic
_editable.value = it.isEditable _editable.value = it.isEditable
_labels.value = it.usedLabels.associate { l -> l.id to Label.fromResponse(l) } _labels.value = it.usedLabels.associate { l -> l.id to Label.fromResponse(l) }
_entries.value = it.entries.map { e -> Entry.fromResponse(e, _labels.value) } val localInputs = mutableSetOf<String>()
val localHardware = mutableSetOf<String>()
_entries.value = it.entries.map { e ->
localInputs += e.input
localHardware += e.hardware
Entry.fromResponse(e, _labels.value)
}
_inputs.value = localInputs
_hardware.value = localHardware
} }
} }
} }
@@ -112,7 +124,11 @@ class ProjectVM(
is ProjectEvent.EntryDeleted -> _entries.value = _entries.value.filter { it.id != event.id } is ProjectEvent.EntryDeleted -> _entries.value = _entries.value.filter { it.id != event.id }
is ProjectEvent.LabelChanged -> _labels.value += (event.label.id to Label.fromResponse(event.label)) is ProjectEvent.LabelChanged -> _labels.value += (event.label.id to Label.fromResponse(event.label))
is ProjectEvent.LabelDeleted -> _labels.value = _labels.value.filter { it.value.id != event.id } is ProjectEvent.LabelDeleted -> _labels.value = _labels.value.filter { it.value.id != event.id }
is ProjectEvent.NewEntry -> _entries.value = _entries.value.insort(Entry.fromResponse(event.entry, _labels.value), Entry::timeStamp) is ProjectEvent.NewEntry -> {
_entries.value = _entries.value.insort(Entry.fromResponse(event.entry, _labels.value), Entry::timeStamp)
_inputs.value += event.entry.input
_hardware.value += event.entry.hardware
}
is ProjectEvent.NewLabel -> _labels.value += (event.label.id to Label.fromResponse(event.label)) is ProjectEvent.NewLabel -> _labels.value += (event.label.id to Label.fromResponse(event.label))
is ProjectEvent.Updated -> { is ProjectEvent.Updated -> {
_name.value = event.changes.name _name.value = event.changes.name
@@ -124,7 +140,9 @@ class ProjectVM(
fun update(name: String?, isPublic: Boolean?) { fun update(name: String?, isPublic: Boolean?) {
withScope { withScope {
_repo.update(name, isPublic).snackOr { refresh() } _repo.update(name, isPublic).snackOr {
if(_job == null) refresh()
}
} }
} }
@@ -142,7 +160,8 @@ class ProjectVM(
fun mkLabel(name: String, color: Color) { fun mkLabel(name: String, color: Color) {
withScope { withScope {
_repo.newLabel(name, color).snackOr { _repo.newLabel(name, color).snackOr {
_labels.value += it.id to Label(it.id, it.name, it.color) if(_job == null)
_labels.value += it.id to Label(it.id, it.name, it.color)
} }
} }
} }
@@ -151,7 +170,8 @@ class ProjectVM(
withScope { withScope {
val old = _labels.value[id] ?: return@withScope val old = _labels.value[id] ?: return@withScope
_repo.updateLabel(id, name, color).snackOr { _repo.updateLabel(id, name, color).snackOr {
_labels.value += id to Label(id, name ?: old.name, color?.hexString() ?: old.colorStr, color ?: old.uiColor) if(_job == null)
_labels.value += id to Label(id, name ?: old.name, color?.hexString() ?: old.colorStr, color ?: old.uiColor)
} }
} }
} }
@@ -159,15 +179,20 @@ class ProjectVM(
fun deleteLabel(id: Uuid) { fun deleteLabel(id: Uuid) {
withScope { withScope {
_repo.deleteLabel(id).snackOr { _repo.deleteLabel(id).snackOr {
_labels.value -= id if(_job == null)
_labels.value -= id
} }
} }
} }
fun mkEntry(label: Label, warmups: List<Float>, measurements: List<Float>, unit: TimeUnit) { fun mkEntry(label: Label, warmups: List<Float>, measurements: List<Float>, unit: TimeUnit, input: String, hardware: String) {
withScope { withScope {
_repo.newEntry(label.id, Clock.System.now(), warmups, measurements, unit).snackOr { _repo.newEntry(label.id, Clock.System.now(), warmups, measurements, unit, input, hardware).snackOr {
_entries.value += Entry.fromResponse(it, _labels.value) if(_job == null) {
_entries.value += Entry.fromResponse(it, _labels.value)
_inputs.value += input
_hardware.value += hardware
}
} }
} }
} }
@@ -175,7 +200,8 @@ class ProjectVM(
fun deleteEntry(id: Uuid) { fun deleteEntry(id: Uuid) {
withScope { withScope {
_repo.deleteEntry(id).snackOr { _repo.deleteEntry(id).snackOr {
_entries.value = _entries.value.filter { it.id != id } if(_job == null)
_entries.value = _entries.value.filter { it.id != id }
} }
} }
} }
@@ -13,13 +13,16 @@ import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.backhandler.BackHandler import androidx.compose.ui.backhandler.BackHandler
import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.scale
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.toLowerCase
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
@@ -27,6 +30,7 @@ import com.composables.icons.lucide.*
import com.jaytux.phoebench.clients.darken import com.jaytux.phoebench.clients.darken
import com.jaytux.phoebench.clients.data.ProjectVM import com.jaytux.phoebench.clients.data.ProjectVM
import com.jaytux.phoebench.clients.data.mutableStateSetFrom import com.jaytux.phoebench.clients.data.mutableStateSetFrom
import com.jaytux.phoebench.clients.data.mutableStateSetOf
import com.jaytux.phoebench.clients.dualLerp import com.jaytux.phoebench.clients.dualLerp
import com.jaytux.phoebench.clients.fmt import com.jaytux.phoebench.clients.fmt
import com.jaytux.phoebench.clients.fmtRange import com.jaytux.phoebench.clients.fmtRange
@@ -153,11 +157,14 @@ fun ProjectView(id: Uuid, forceBack: () -> Unit) {
addOpen = false addOpen = false
} }
if(addingData) AddEntryDialog(labels, val usedInputs by vm.inputs
val usedHardware by vm.hardware
if(addingData) AddEntryDialog(labels, usedInputs, usedHardware,
onCancel = { addingData = false; addOpen = false }, onCancel = { addingData = false; addOpen = false },
onAddLbl = { name, lbl -> vm.mkLabel(name, lbl) } onAddLbl = { name, lbl -> vm.mkLabel(name, lbl) }
) { label, warmups, measurements, unit -> ) { label, warmups, measurements, unit, input, hardware ->
vm.mkEntry(label, warmups, measurements, unit) vm.mkEntry(label, warmups, measurements, unit, input, hardware)
addOpen = false addOpen = false
} }
} }
@@ -227,26 +234,33 @@ fun AddLabelDialog(onCancel: () -> Unit, onAdd: (name: String, color: Color) ->
} }
@Composable @Composable
fun QuickLabel(lbl: ProjectVM.Label) { fun QuickLabel(lbl: ProjectVM.Label, hollow: Boolean = false) {
Row(Modifier.height(IntrinsicSize.Min).padding(vertical = 3.dp), verticalAlignment = Alignment.Bottom) { Row(Modifier.height(IntrinsicSize.Min).padding(vertical = 3.dp), verticalAlignment = Alignment.Bottom) {
Box(Modifier.fillMaxHeight().aspectRatio(1f).background(lbl.uiColor)) {} Box(Modifier.fillMaxHeight().aspectRatio(1f).background(lbl.uiColor)) {
if(hollow) {
Box(Modifier.fillMaxHeight(0.75f).aspectRatio(1f).align(Alignment.Center).background(Color.Gray.copy(alpha = 0.5f)))
}
}
Spacer(Modifier.width(15.dp)) Spacer(Modifier.width(15.dp))
Text(lbl.name) Text(lbl.name)
Text(" ${lbl.colorStr}", style = MaterialTheme.typography.bodySmall, color = LocalContentColor.current.copy(alpha = 0.75f)) Text(" ${lbl.colorStr}", style = MaterialTheme.typography.bodySmall, color = LocalContentColor.current.copy(alpha = 0.75f))
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AddEntryDialog( fun AddEntryDialog(
labels: Map<Uuid, ProjectVM.Label>, labels: Map<Uuid, ProjectVM.Label>, usedInputs: Set<String>, usedHardware: Set<String>,
onCancel: () -> Unit, onAddLbl: (name: String, color: Color) -> Unit, onCancel: () -> Unit, onAddLbl: (name: String, color: Color) -> Unit,
onAdd: (label: ProjectVM.Label, warmups: List<Float>, measurements: List<Float>, unit: TimeUnit) -> Unit onAdd: (label: ProjectVM.Label, warmups: List<Float>, measurements: List<Float>, unit: TimeUnit, input: String, hardware: String) -> Unit
) { ) {
Dialog(onDismissRequest = onCancel) { Dialog(onDismissRequest = onCancel) {
var label by remember { mutableStateOf<ProjectVM.Label?>(null) } var label by remember { mutableStateOf<ProjectVM.Label?>(null) }
var warmups by remember { mutableStateOf("") } var warmups by remember { mutableStateOf("") }
var measurements by remember { mutableStateOf("") } var measurements by remember { mutableStateOf("") }
var unit by remember { mutableStateOf(TimeUnit.SECONDS) } var unit by remember { mutableStateOf(TimeUnit.SECONDS) }
var input by remember { mutableStateOf("") }
var hardware by remember { mutableStateOf("") }
var addingLabel by remember { mutableStateOf(false) } var addingLabel by remember { mutableStateOf(false) }
var warmupParsed by remember { mutableStateOf<Either<String, List<Float>>>(listOf<Float>().value()) } var warmupParsed by remember { mutableStateOf<Either<String, List<Float>>>(listOf<Float>().value()) }
@@ -308,6 +322,39 @@ fun AddEntryDialog(
} }
} }
Row {
var inputFocused by remember { mutableStateOf(false) }
var hardwareFocused by remember { mutableStateOf(false) }
Box(Modifier.weight(0.5f)) {
ExposedDropdownMenuBox(inputFocused, {}) {
OutlinedTextField(input, { input = it },
Modifier.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable).onFocusChanged { inputFocused = it.isFocused },
label = { Text("Input") }
)
ExposedDropdownMenu(inputFocused, {}) {
usedInputs.filter { input.lowercase() in it.lowercase() }.sorted().forEach { inp ->
DropdownMenuItem(text = { Text(inp) }, onClick = { input = inp })
}
}
}
}
Spacer(Modifier.width(5.dp))
Box(Modifier.weight(0.5f)) {
ExposedDropdownMenuBox(hardwareFocused, { inputFocused = false }) {
OutlinedTextField(hardware, { hardware = it },
Modifier.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable).onFocusChanged { hardwareFocused = it.isFocused },
label = { Text("Hardware") }
)
ExposedDropdownMenu(hardwareFocused, { hardwareFocused = false }) {
usedHardware.filter { hardware.lowercase() in it.lowercase() }.sorted().forEach { hw ->
DropdownMenuItem(text = { Text(hw) }, onClick = { hardware = hw })
}
}
}
}
}
Spacer(Modifier.height(7.dp)) Spacer(Modifier.height(7.dp))
Text("Data (separate data points by commas)") Text("Data (separate data points by commas)")
Spacer(Modifier.height(5.dp)) Spacer(Modifier.height(5.dp))
@@ -326,8 +373,8 @@ fun AddEntryDialog(
Spacer(Modifier.height(5.dp)) Spacer(Modifier.height(5.dp))
CancelConfirmXRow(onCancel, { CancelConfirmXRow(onCancel, {
onAdd(label!!, warmupParsed.asValue()!!, measureParsed.asValue()!!, unit) onAdd(label!!, warmupParsed.asValue()!!, measureParsed.asValue()!!, unit, input, hardware)
}, confirmText = "Create", canConfirm = label != null && warmupParsed.isValue() && measureParsed.isValue()) }, confirmText = "Create", canConfirm = label != null && warmupParsed.isValue() && measureParsed.isValue() && input.isNotBlank() && hardware.isNotBlank())
} }
} }
@@ -356,17 +403,33 @@ fun ProjectPlotArea(vm: ProjectVM){
val labelFilter = remember(labels) { mutableStateSetFrom(labels.values) } val labelFilter = remember(labels) { mutableStateSetFrom(labels.values) }
val labelFilterKey by labelFilter.revision val labelFilterKey by labelFilter.revision
LaunchedEffect(entries, displayWarmups, currentUnit, timeFilter, timeMin, timeMax, labelFilterKey) { val allInputs by vm.inputs
val allHardware by vm.hardware
val enabledInputs = remember { mutableStateSetOf<String>() }
val enabledHardware = remember { mutableStateSetOf<String>() }
var filterInput by remember { mutableStateOf<String?>(null) }
var filterHardware by remember { mutableStateOf<String?>(null) }
LaunchedEffect(entries, displayWarmups, currentUnit, timeFilter, timeMin, timeMax, labelFilterKey, filterInput, filterHardware) {
var maxX = 0f var maxX = 0f
var minY = 0f var minY = 0f
var maxY = 0f var maxY = 0f
val timeRange = dualLerp(timeMin, timeMax, timeFilter.start, timeFilter.endInclusive) val timeRange = dualLerp(timeMin, timeMax, timeFilter.start, timeFilter.endInclusive)
enabledInputs.clear()
enabledHardware.clear()
renderableEntries = entries.mapNotNull { entry -> renderableEntries = entries.mapNotNull { entry ->
val use = if(displayWarmups) entry.warmups else entry.measurements val use = if(displayWarmups) entry.warmups else entry.measurements
maxX = maxOf(maxX, use.size.toFloat())
if(entry.label !in labelFilter) return@mapNotNull null if(entry.label !in labelFilter) return@mapNotNull null
if(!(entry.timeStamp inRange timeRange)) return@mapNotNull null if(!(entry.timeStamp inRange timeRange)) return@mapNotNull null
if(filterInput != null && entry.input != filterInput) return@mapNotNull null
if(filterHardware != null && entry.hardware != filterHardware) return@mapNotNull null
maxX = maxOf(maxX, use.size.toFloat())
enabledInputs.add(entry.input)
enabledHardware.add(entry.hardware)
use.mapIndexed { idx, it -> use.mapIndexed { idx, it ->
val converted = entry.nativeUnit.convertTo(currentUnit, it) val converted = entry.nativeUnit.convertTo(currentUnit, it)
@@ -482,9 +545,11 @@ fun ProjectPlotArea(vm: ProjectVM){
Spacer(Modifier.width(20.dp)) Spacer(Modifier.width(20.dp))
Box(Modifier.weight(0.33f).fillMaxHeight()) { Row(Modifier.weight(0.33f).fillMaxHeight()) {
Column { Column(Modifier.weight(0.5f)) {
Text("Labels", style = MaterialTheme.typography.headlineSmall) Text("Labels", style = MaterialTheme.typography.headlineSmall)
Text("Click a label to toggle its visibility.", fontStyle = FontStyle.Italic,
color = LocalContentColor.current.copy(alpha = 0.5f), style = MaterialTheme.typography.bodySmall)
Spacer(Modifier.height(10.dp)) Spacer(Modifier.height(10.dp))
if(labels.isEmpty()) { if(labels.isEmpty()) {
Box(Modifier.fillMaxWidth().fillMaxHeight(0.25f)) { Box(Modifier.fillMaxWidth().fillMaxHeight(0.25f)) {
@@ -494,9 +559,18 @@ fun ProjectPlotArea(vm: ProjectVM){
else { else {
LazyColumn(Modifier.padding(start = 5.dp)) { LazyColumn(Modifier.padding(start = 5.dp)) {
items(labels.toList().sortedBy { it.second.name }) { (_, lbl) -> items(labels.toList().sortedBy { it.second.name }) { (_, lbl) ->
Box(Modifier.fillMaxWidth().clickable { labelFilter.toggle(lbl) }) { Box(Modifier.fillMaxWidth().clickable {
if(labelFilter.size == labels.size) {
labelFilter.clear()
labelFilter.add(lbl)
}
else {
labelFilter.toggle(lbl)
if(labelFilter.isEmpty()) labelFilter.addAll(labels.values)
}
}) {
Box { Box {
QuickLabel(lbl) QuickLabel(lbl, lbl !in labelFilter)
if(lbl !in labelFilter) { if(lbl !in labelFilter) {
Box(Modifier.matchParentSize()) { Box(Modifier.matchParentSize()) {
@@ -513,6 +587,46 @@ fun ProjectPlotArea(vm: ProjectVM){
} }
} }
} }
Spacer(Modifier.width(15.dp))
LazyColumn(Modifier.weight(0.5f)) {
item { Text("Inputs", style = MaterialTheme.typography.headlineSmall) }
item {
Text("Click an input to set or clear the filter.", fontStyle = FontStyle.Italic,
color = LocalContentColor.current.copy(0.5f), style = MaterialTheme.typography.bodySmall)
}
item { Spacer(Modifier.height(10.dp)) }
items((allInputs intersect enabledInputs.toSet()).toList()) {
Row(Modifier.fillMaxWidth().clickable { filterInput = if(filterInput == null) it else null }) {
Text(it, Modifier.padding(start = 5.dp))
}
}
items((allInputs - enabledInputs.toSet()).toList()) {
Text(it, Modifier.padding(start = 5.dp, top = 2.dp, bottom = 2.dp), color = LocalContentColor.current.copy(0.66f))
}
item { Spacer(Modifier.height(15.dp)) }
item { Text("Hardware", style = MaterialTheme.typography.headlineSmall) }
item {
Text("Click an item to set or clear the filter.", fontStyle = FontStyle.Italic,
color = LocalContentColor.current.copy(0.5f), style = MaterialTheme.typography.bodySmall)
}
item { Spacer(Modifier.height(10.dp)) }
items((allHardware intersect enabledHardware.toSet()).toList()) {
Row(Modifier.fillMaxWidth().clickable { filterHardware = if(filterHardware == null) it else null }) {
Text(it, Modifier.padding(start = 5.dp))
}
}
items((allHardware - enabledHardware.toSet()).toList()) {
Text(it, Modifier.padding(start = 5.dp, top = 2.dp, bottom = 2.dp), color = LocalContentColor.current.copy(0.66f))
}
}
} }
} }
} }
@@ -27,7 +27,7 @@ data class LabelRequest(val name: String, val color: String, val projectId: Uuid
@Serializable @ToPartialize @Serializable @ToPartialize
data class EntryRequest(val label: Uuid, val timestamp: Instant, val projectId: Uuid, val warmups: List<Float>, data class EntryRequest(val label: Uuid, val timestamp: Instant, val projectId: Uuid, val warmups: List<Float>,
val measurements: List<Float>, val unit: TimeUnit) val measurements: List<Float>, val unit: TimeUnit, val input: String, val hardware: String)
@Serializable @Serializable
data class LogoutRequest(val refresh: Uuid) data class LogoutRequest(val refresh: Uuid)
@@ -54,7 +54,7 @@ data class LabelResponse(val id: Uuid, val name: String, val color: String)
@Serializable @Serializable
data class EntryResponse(val id: Uuid, val labelId: Uuid, val timestamp: Instant, val warmups: List<Float>, data class EntryResponse(val id: Uuid, val labelId: Uuid, val timestamp: Instant, val warmups: List<Float>,
val measurements: List<Float>, val unit: TimeUnit) val measurements: List<Float>, val unit: TimeUnit, val input: String, val hardware: String)
@Serializable @Serializable
data class HandshakeResponse(val version: String = ProtocolVersion.VERSION) { data class HandshakeResponse(val version: String = ProtocolVersion.VERSION) {
@@ -65,6 +65,8 @@ class Entry(id: EntityID<Uuid>) : Entity<Uuid>(id) {
var warmups by Entries.warmups var warmups by Entries.warmups
var measurements by Entries.measurements var measurements by Entries.measurements
var unit by Entries.unit var unit by Entries.unit
var input by Entries.input
var hardware by Entries.hardware
var project by Project referencedOn Entries.projectId var project by Project referencedOn Entries.projectId
var label by Label referencedOn Entries.labelId var label by Label referencedOn Entries.labelId
@@ -47,4 +47,6 @@ object Entries : UuidTable() {
val warmups = list<Float>("warmups") val warmups = list<Float>("warmups")
val measurements = list<Float>("measurements") val measurements = list<Float>("measurements")
val unit = enumeration<TimeUnit>("unit") val unit = enumeration<TimeUnit>("unit")
val input = varchar("input", 255).default("")
val hardware = varchar("hardware", 255).default("")
} }
@@ -56,7 +56,7 @@ object ProjectHandler {
private fun Project.toResponse(user: User) = ProjectResponse( private fun Project.toResponse(user: User) = ProjectResponse(
id.value, name, NamedID(owner.username, owner.id.value), isPublic, isEditableBy(user), id.value, name, NamedID(owner.username, owner.id.value), isPublic, isEditableBy(user),
labels.orderBy(Labels.label to SortOrder.ASC).map { LabelResponse(it.id.value, it.label, it.color) }, labels.orderBy(Labels.label to SortOrder.ASC).map { LabelResponse(it.id.value, it.label, it.color) },
entries.orderBy(Entries.timestamp to SortOrder.ASC).map { EntryResponse(it.id.value, it.label.id.value, it.timestamp, it.warmups, it.measurements, it.unit) }) entries.orderBy(Entries.timestamp to SortOrder.ASC).map { EntryResponse(it.id.value, it.label.id.value, it.timestamp, it.warmups, it.measurements, it.unit, it.input, it.hardware) })
fun home(user: User, req: EmptyRequest) = transaction { fun home(user: User, req: EmptyRequest) = transaction {
val own = user.projects.orderBy(Projects.name to SortOrder.ASC).map { val own = user.projects.orderBy(Projects.name to SortOrder.ASC).map {
@@ -188,9 +188,11 @@ object ProjectHandler {
timestamp = req.timestamp timestamp = req.timestamp
warmups = req.warmups warmups = req.warmups
unit = req.unit unit = req.unit
input = req.input
hardware = req.hardware
} }
val response = EntryResponse(entry.id.value, entry.label.id.value, entry.timestamp, entry.warmups, entry.measurements, entry.unit) val response = EntryResponse(entry.id.value, entry.label.id.value, entry.timestamp, entry.warmups, entry.measurements, entry.unit, entry.input, entry.hardware)
ServerScope.launch { ServerScope.launch {
Buses.projectBus(proj.id.value).sendAll(ProjectEvent.NewEntry(response)) Buses.projectBus(proj.id.value).sendAll(ProjectEvent.NewEntry(response))
} }