Added input, hardware fields to data
This commit is contained in:
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -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()
|
||||||
|
|||||||
+38
-12
@@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+129
-15
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user