Rename Label->Version, add version meta field

This commit is contained in:
2026-08-09 20:18:11 +02:00
parent 60b1a69bc6
commit a90b2ebeca
15 changed files with 168 additions and 176 deletions
@@ -98,7 +98,7 @@ object CLI {
sealed interface IProjectIdentification
sealed interface ILabelIdentification
data class ProjectName(val user: String, val project: String) : IProjectIdentification
data class LabelName(val name: String): ILabelIdentification
data class VersionName(val name: String): ILabelIdentification
data class ID(val id: Uuid) : IProjectIdentification, ILabelIdentification
sealed interface IData<T> {
@@ -168,22 +168,23 @@ object CLI {
}
@Suppress("unused")
class AddLabel : CliktCommand(name = "add-label") {
class AddVersion : CliktCommand(name = "add-version") {
val parent by requireObject<Project>()
val name by option("--name", help = "Set the label's name (*)")
val color by option("--color", help = "Set the label's color (*)").check("Color must be specified in RGB-hex-format (#ABCDEF)") {
val name by option("--name", help = "Set the version's name (*)")
val color by option("--color", help = "Set the version's color (*)").check("Color must be specified in RGB-hex-format (#ABCDEF)") {
it.length == 7 && it[0] == '#' && it.substring(1, it.length).all { c -> c.isDigit() || c in "ABCDEF" }
}
val meta by option("--meta", help = "Add metadata to the version (*)")
override fun run() = ProjectHandlers.newLabel(name, color, parent.finder)
override fun run() = ProjectHandlers.newVersion(name, color, parent.finder, meta)
}
@Suppress("unused")
class AddData : CliktCommand(name = "add-data") {
val parent by requireObject<Project>()
val label by mutuallyExclusiveOptions<ILabelIdentification>(
option("--label-id", help = "Set the label by UUID.").convert { ID(Uuid.parse(it)) },
option("--label", help = "Set the label by name.").convert { LabelName(it) }
val version by mutuallyExclusiveOptions<ILabelIdentification>(
option("--version-id", help = "Set the version by UUID.").convert { ID(Uuid.parse(it)) },
option("--version", help = "Set the version by name.").convert { VersionName(it) }
).single()
val warmup by mutuallyExclusiveOptions<IData<Float>>(
option("--warmup", help = "Set warmup data directly.").float().split(",").transformAll { DirectData(it.flatten()) },
@@ -204,7 +205,7 @@ object CLI {
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, version, warmup, measurement, unit, input, hardware)
}
}
}
@@ -1,16 +1,11 @@
package com.jaytux.phoebench.clients.cli
import com.github.ajalt.clikt.parameters.options.convert
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.jaytux.phoebench.clients.cli.CLI.Commands.Project.ProjectName
import com.jaytux.phoebench.common.ApiRoute
import com.jaytux.phoebench.common.Either
import com.jaytux.phoebench.common.EmptyRequest
import com.jaytux.phoebench.common.EntryRequest
import com.jaytux.phoebench.common.ErrorResponse
import com.jaytux.phoebench.common.LabelRequest
import com.jaytux.phoebench.common.LabelResponse
import com.jaytux.phoebench.common.VersionRequest
import com.jaytux.phoebench.common.ProjectRequest
import com.jaytux.phoebench.common.Routes
import com.jaytux.phoebench.common.TimeUnit
@@ -19,9 +14,6 @@ import com.jaytux.phoebench.common.error
import com.jaytux.phoebench.common.fold
import com.jaytux.phoebench.common.map
import com.jaytux.phoebench.common.value
import kotlinx.coroutines.runBlocking
import kotlinx.datetime.toLocalDateTime
import java.nio.file.OpenOption
import kotlin.io.path.Path
import kotlin.io.path.exists
import kotlin.io.path.inputStream
@@ -100,7 +92,7 @@ object ProjectHandlers {
}
}
fun newLabel(name: String?, color: String?, project: CLI.Commands.Project.IProjectIdentification?) {
fun newVersion(name: String?, color: String?, project: CLI.Commands.Project.IProjectIdentification?, version: String?) {
val id = ensureProjectIdentification(project)
val useName = name.maybePrompt("label name") { it }
val useColor = color.maybePrompt("color") {
@@ -111,10 +103,11 @@ object ProjectHandlers {
exitProcess(-1)
}
}
val useVersion = version.maybePrompt("version") { it }
tryAuthenticated {
id.toId().bind { projectId ->
Client.callRoute(Routes.Label.new, LabelRequest(useName, useColor, projectId))
Client.callRoute(Routes.Version.new, VersionRequest(useName, useColor, useVersion, projectId))
}
}.fold({
System.err.println("Could not create label: ${it.msg}")
@@ -139,11 +132,11 @@ object ProjectHandlers {
label: CLI.Commands.Project.ILabelIdentification?,
warmup: CLI.Commands.Project.IData<Float>?,
measurement: CLI.Commands.Project.IData<Float>?,
unit: TimeUnit?
unit: TimeUnit?, input: String?, hardware: String?
) {
val projectId = ensureProjectIdentification(project)
val labelId = label.maybePrompt("label name") {
CLI.Commands.Project.LabelName(it)
val versionId = label.maybePrompt("version name") {
CLI.Commands.Project.VersionName(it)
}
val warmupData = warmup.ensure("warmup").toList()
val measureData = measurement.ensure("measurements").toList()
@@ -162,20 +155,22 @@ object ProjectHandlers {
}
}
}
val useInput = input.maybePrompt("benchmark input") { it }
val useHardware = hardware.maybePrompt("hardware description") { it }
tryAuthenticated {
projectId.toId().bind { pId ->
when(labelId) {
is CLI.Commands.Project.ID -> labelId.id.value()
is CLI.Commands.Project.LabelName -> Client.callRoute(Routes.Project.get, pId).bind {
it.usedLabels.firstOrNull { l -> l.name == labelId.name }?.id?.value()
?: ErrorResponse("Label ${labelId.name} does not exist in this project.").error()
when(versionId) {
is CLI.Commands.Project.ID -> versionId.id.value()
is CLI.Commands.Project.VersionName -> Client.callRoute(Routes.Project.get, pId).bind {
it.usedLabels.firstOrNull { l -> l.name == versionId.name }?.id?.value()
?: ErrorResponse("Label ${versionId.name} does not exist in this project.").error()
}
}.map { pId to it }
}.bind { (pId, lId) ->
}.bind { (pId, vId) ->
Client.callRoute(Routes.Entry.new, EntryRequest(
lId, Clock.System.now(), pId,
warmupData, measureData, timeUnit
vId, Clock.System.now(), pId,
warmupData, measureData, timeUnit, useInput, useHardware
))
}
}
@@ -8,15 +8,13 @@ import com.jaytux.phoebench.common.Either
import com.jaytux.phoebench.common.EntryRequest
import com.jaytux.phoebench.common.EntryResponse
import com.jaytux.phoebench.common.ErrorResponse
import com.jaytux.phoebench.common.LabelRequest
import com.jaytux.phoebench.common.LabelResponse
import com.jaytux.phoebench.common.PartialEntryRequest
import com.jaytux.phoebench.common.PartialLabelRequest
import com.jaytux.phoebench.common.VersionRequest
import com.jaytux.phoebench.common.VersionResponse
import com.jaytux.phoebench.common.PartialProjectRequest
import com.jaytux.phoebench.common.PartialVersionRequest
import com.jaytux.phoebench.common.ProjectResponse
import com.jaytux.phoebench.common.Routes
import com.jaytux.phoebench.common.TimeUnit
import kotlinx.datetime.TimeZone
import kotlin.time.Instant
import kotlin.uuid.Uuid
@@ -25,8 +23,8 @@ interface IProjectRepo {
suspend fun update(name: String? = null, isPublic: Boolean? = null): Either<ErrorResponse, Unit>
suspend fun delete(): Either<ErrorResponse, Unit>
suspend fun newLabel(name: String, color: Color): Either<ErrorResponse, LabelResponse>
suspend fun updateLabel(id: Uuid, name: String? = null, color: Color? = null): Either<ErrorResponse, Unit>
suspend fun newVersion(name: String, color: Color, meta: String): Either<ErrorResponse, VersionResponse>
suspend fun updateVersion(id: Uuid, name: String? = null, color: Color? = null, meta: String? = null): Either<ErrorResponse, Unit>
suspend fun deleteLabel(id: Uuid): Either<ErrorResponse, Unit>
suspend fun newEntry(label: Uuid, timestamp: Instant, warmups: List<Float>, measurements: List<Float>,
@@ -44,14 +42,14 @@ interface IProjectRepo {
override suspend fun delete(): Either<ErrorResponse, Unit> =
_client.callRoute(Routes.Project.delete, _projectId).ignoreValue()
override suspend fun newLabel(name: String, color: Color): Either<ErrorResponse, LabelResponse> =
_client.callRoute(Routes.Label.new, LabelRequest(name, color.hexString(), _projectId))
override suspend fun newVersion(name: String, color: Color, meta: String): Either<ErrorResponse, VersionResponse> =
_client.callRoute(Routes.Version.new, VersionRequest(name, color.hexString(), meta, _projectId))
override suspend fun updateLabel(id: Uuid, name: String?, color: Color?): Either<ErrorResponse, Unit> =
_client.callRoute(Routes.Label.update, id to PartialLabelRequest(name, color?.hexString())).ignoreValue()
override suspend fun updateVersion(id: Uuid, name: String?, color: Color?, meta: String?): Either<ErrorResponse, Unit> =
_client.callRoute(Routes.Version.update, id to PartialVersionRequest(name, color?.hexString(), meta)).ignoreValue()
override suspend fun deleteLabel(id: Uuid): Either<ErrorResponse, Unit> =
_client.callRoute(Routes.Label.delete, id).ignoreValue()
_client.callRoute(Routes.Version.delete, id).ignoreValue()
override suspend fun newEntry(label: Uuid, timestamp: Instant, warmups: List<Float>,
measurements: List<Float>, unit: TimeUnit, input: String, hardware: String
@@ -11,13 +11,11 @@ import com.jaytux.phoebench.clients.SnackProvider.Companion.snackOr
import com.jaytux.phoebench.clients.hexString
import com.jaytux.phoebench.clients.immutable
import com.jaytux.phoebench.clients.insort
import com.jaytux.phoebench.clients.systemTz
import com.jaytux.phoebench.clients.withScope
import com.jaytux.phoebench.common.EntryResponse
import com.jaytux.phoebench.common.LabelResponse
import com.jaytux.phoebench.common.VersionResponse
import com.jaytux.phoebench.common.ProjectEvent
import com.jaytux.phoebench.common.TimeUnit
import com.jaytux.phoebench.common.foldSuspend
import kotlinx.coroutines.Job
import kotlin.time.Clock
import kotlin.time.Instant
@@ -32,7 +30,7 @@ class ProjectVM(
private val _sseRepo: ISSERepo = ISSERepo.default(_client),
private val _forceBack: () -> Unit
) : ViewModel() {
data class Label(val id: Uuid, val name: String, val colorStr: String, val uiColor: Color = parseColor(colorStr)) {
data class Version(val id: Uuid, val name: String, val colorStr: String, val meta: String, val uiColor: Color = parseColor(colorStr)) {
companion object {
private val _snack by lazy { SnackProvider.get() }
private val _errColor = Color(red = 252, green = 20, blue = 182)
@@ -50,17 +48,17 @@ class ProjectVM(
return Color(red = r, green = g, blue = b)
}
fun fromResponse(it: LabelResponse) = Label(it.id, it.name, it.color)
fun fromResponse(it: VersionResponse) = Version(it.id, it.name, it.color, it.meta)
val invalid = Label(Uuid.fromLongs(0L, 0L), "<invalid>", _errColor.hexString(), _errColor)
val invalid = Version(Uuid.fromLongs(0L, 0L), "<invalid>", _errColor.hexString(), "", _errColor)
}
}
data class Entry(val id: Uuid, val label: Label, val timeStamp: Instant, val warmups: List<Float>,
data class Entry(val id: Uuid, val label: Version, val timeStamp: Instant, val warmups: List<Float>,
val measurements: List<Float>, val nativeUnit: TimeUnit, val input: String, val hardware: String) {
companion object {
fun fromResponse(it: EntryResponse, map: Map<Uuid, Label>) = Entry(
it.id, map[it.labelId] ?: Label.invalid, it.timestamp,
fun fromResponse(it: EntryResponse, map: Map<Uuid, Version>) = Entry(
it.id, map[it.labelId] ?: Version.invalid, it.timestamp,
it.warmups, it.measurements, it.unit, it.input, it.hardware
)
}
@@ -70,7 +68,7 @@ class ProjectVM(
private val _owner = mutableStateOf<String?>(null)
private val _public = mutableStateOf(false)
private val _editable = mutableStateOf(false)
private val _labels = mutableStateOf(mapOf<Uuid, Label>())
private val _labels = mutableStateOf(mapOf<Uuid, Version>())
private val _entries = mutableStateOf(listOf<Entry>())
private val _inputs = mutableStateOf(setOf<String>())
private val _hardware = mutableStateOf(setOf<String>())
@@ -79,7 +77,7 @@ class ProjectVM(
val owner = _owner.immutable()
val public = _public.immutable()
val editable = _editable.immutable()
val labels = _labels.immutable()
val versions = _labels.immutable()
val entries = _entries.immutable()
val inputs = _inputs.immutable()
val hardware = _hardware.immutable()
@@ -104,7 +102,7 @@ class ProjectVM(
_owner.value = it.owner.name
_public.value = it.isPublic
_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 Version.fromResponse(l) }
val localInputs = mutableSetOf<String>()
val localHardware = mutableSetOf<String>()
_entries.value = it.entries.map { e ->
@@ -122,14 +120,14 @@ class ProjectVM(
when(event) {
ProjectEvent.Deleted -> back()
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.LabelDeleted -> _labels.value = _labels.value.filter { it.value.id != event.id }
is ProjectEvent.VersionChanged -> _labels.value += (event.label.id to Version.fromResponse(event.label))
is ProjectEvent.VersionDeleted -> _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)
_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.NewVersion -> _labels.value += (event.label.id to Version.fromResponse(event.label))
is ProjectEvent.Updated -> {
_name.value = event.changes.name
_owner.value = event.changes.owner.name
@@ -157,21 +155,21 @@ class ProjectVM(
_forceBack()
}
fun mkLabel(name: String, color: Color) {
fun mkLabel(name: String, color: Color, meta: String) {
withScope {
_repo.newLabel(name, color).snackOr {
_repo.newVersion(name, color, meta).snackOr {
if(_job == null)
_labels.value += it.id to Label(it.id, it.name, it.color)
_labels.value += it.id to Version(it.id, it.name, it.color, it.meta)
}
}
}
fun updateLabel(id: Uuid, name: String?, color: Color?) {
fun updateLabel(id: Uuid, name: String?, color: Color?, meta: String?) {
withScope {
val old = _labels.value[id] ?: return@withScope
_repo.updateLabel(id, name, color).snackOr {
_repo.updateVersion(id, name, color).snackOr {
if(_job == null)
_labels.value += id to Label(id, name ?: old.name, color?.hexString() ?: old.colorStr, color ?: old.uiColor)
_labels.value += id to Version(id, name ?: old.name, color?.hexString() ?: old.colorStr, meta ?: old.meta, color ?: old.uiColor)
}
}
}
@@ -185,7 +183,7 @@ class ProjectVM(
}
}
fun mkEntry(label: Label, warmups: List<Float>, measurements: List<Float>, unit: TimeUnit, input: String, hardware: String) {
fun mkEntry(label: Version, warmups: List<Float>, measurements: List<Float>, unit: TimeUnit, input: String, hardware: String) {
withScope {
_repo.newEntry(label.id, Clock.System.now(), warmups, measurements, unit, input, hardware).snackOr {
if(_job == null) {
@@ -20,14 +20,11 @@ import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.font.FontStyle
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.toLowerCase
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.lifecycle.viewmodel.compose.viewModel
import com.composables.icons.lucide.*
import com.jaytux.phoebench.clients.darken
import com.jaytux.phoebench.clients.data.ProjectVM
import com.jaytux.phoebench.clients.data.mutableStateSetFrom
import com.jaytux.phoebench.clients.data.mutableStateSetOf
@@ -52,8 +49,6 @@ import io.github.koalaplot.core.xygraph.AxisContent
import io.github.koalaplot.core.xygraph.DefaultPoint
import io.github.koalaplot.core.xygraph.XYGraph
import io.github.koalaplot.core.xygraph.autoScaleRange
import io.github.koalaplot.core.xygraph.autoScaleXRange
import io.github.koalaplot.core.xygraph.autoScaleYRange
import io.github.koalaplot.core.xygraph.rememberAxisStyle
import io.github.koalaplot.core.xygraph.rememberFloatLinearAxisModel
import io.github.koalaplot.core.xygraph.rememberGridStyle
@@ -70,7 +65,7 @@ fun ProjectView(id: Uuid, forceBack: () -> Unit) {
val owner by vm.owner
val public by vm.public
val editable by vm.editable
val labels by vm.labels
val labels by vm.versions
var editing by remember { mutableStateOf(false) }
var deleting by remember { mutableStateOf(false) }
@@ -152,8 +147,8 @@ fun ProjectView(id: Uuid, forceBack: () -> Unit) {
vm.delete()
}
if(addingLabel) AddLabelDialog({ addingLabel = false; addOpen = false }) { name, color ->
vm.mkLabel(name, color)
if(addingLabel) AddVersionDialog({ addingLabel = false; addOpen = false }) { name, color, meta ->
vm.mkLabel(name, color, meta)
addOpen = false
}
@@ -162,7 +157,7 @@ fun ProjectView(id: Uuid, forceBack: () -> Unit) {
if(addingData) AddEntryDialog(labels, usedInputs, usedHardware,
onCancel = { addingData = false; addOpen = false },
onAddLbl = { name, lbl -> vm.mkLabel(name, lbl) }
onAddLbl = { name, lbl, meta -> vm.mkLabel(name, lbl, meta) }
) { label, warmups, measurements, unit, input, hardware ->
vm.mkEntry(label, warmups, measurements, unit, input, hardware)
addOpen = false
@@ -212,29 +207,32 @@ fun ConfirmDeleteProjectDialog(name: String, onCancel: () -> Unit, onDelete: ()
}
@Composable
fun AddLabelDialog(onCancel: () -> Unit, onAdd: (name: String, color: Color) -> Unit) {
fun AddVersionDialog(onCancel: () -> Unit, onAdd: (name: String, color: Color, meta: String) -> Unit) {
Dialog(onDismissRequest = onCancel) {
var name by remember { mutableStateOf("") }
var color by remember { mutableStateOf(randomColor()) }
var meta by remember { mutableStateOf("") }
Surface(Modifier.padding(15.dp).widthIn(400.dp), shape = MaterialTheme.shapes.medium) {
Column(Modifier.padding(8.dp).width(IntrinsicSize.Min)) {
Text("Create label", Modifier.align(Alignment.CenterHorizontally), style = MaterialTheme.typography.headlineMedium)
Text("Create version", Modifier.align(Alignment.CenterHorizontally), style = MaterialTheme.typography.headlineMedium)
OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), label = { Text("Name") })
Spacer(Modifier.height(10.dp))
KolorPicker(color, { color = it }, alphaTrackVisible = false, modifier = Modifier.aspectRatio(1f))
Spacer(Modifier.height(10.dp))
OutlinedTextField(meta, { meta = it }, Modifier.fillMaxWidth(), label = { Text("Additional information") })
Spacer(Modifier.height(10.dp))
CancelConfirmXRow(onCancel, {
onAdd(name.trim(), color)
}, confirmText = "Create", canConfirm = name.trim().isNotBlank())
onAdd(name.trim(), color, meta.trim())
}, confirmText = "Create", canConfirm = name.trim().isNotBlank() && meta.trim().isNotBlank())
}
}
}
}
@Composable
fun QuickLabel(lbl: ProjectVM.Label, hollow: Boolean = false) {
fun QuickVersion(lbl: ProjectVM.Version, hollow: Boolean = false) {
Row(Modifier.height(IntrinsicSize.Min).padding(vertical = 3.dp), verticalAlignment = Alignment.Bottom) {
Box(Modifier.fillMaxHeight().aspectRatio(1f).background(lbl.uiColor)) {
if(hollow) {
@@ -242,20 +240,29 @@ fun QuickLabel(lbl: ProjectVM.Label, hollow: Boolean = false) {
}
}
Spacer(Modifier.width(15.dp))
Text(lbl.name)
Text(" ${lbl.colorStr}", style = MaterialTheme.typography.bodySmall, color = LocalContentColor.current.copy(alpha = 0.75f))
Column {
Row {
Text(lbl.name)
Text(" ${lbl.colorStr}", Modifier.align(Alignment.Bottom),
style = MaterialTheme.typography.bodySmall,
color = LocalContentColor.current.copy(alpha = 0.75f),
)
}
Text(lbl.meta, maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodySmall,
color = LocalContentColor.current.copy(0.75f))
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AddEntryDialog(
labels: Map<Uuid, ProjectVM.Label>, usedInputs: Set<String>, usedHardware: Set<String>,
onCancel: () -> Unit, onAddLbl: (name: String, color: Color) -> Unit,
onAdd: (label: ProjectVM.Label, warmups: List<Float>, measurements: List<Float>, unit: TimeUnit, input: String, hardware: String) -> Unit
labels: Map<Uuid, ProjectVM.Version>, usedInputs: Set<String>, usedHardware: Set<String>,
onCancel: () -> Unit, onAddLbl: (name: String, color: Color, meta: String) -> Unit,
onAdd: (label: ProjectVM.Version, warmups: List<Float>, measurements: List<Float>, unit: TimeUnit, input: String, hardware: String) -> Unit
) {
Dialog(onDismissRequest = onCancel) {
var label by remember { mutableStateOf<ProjectVM.Label?>(null) }
var label by remember { mutableStateOf<ProjectVM.Version?>(null) }
var warmups by remember { mutableStateOf("") }
var measurements by remember { mutableStateOf("") }
var unit by remember { mutableStateOf(TimeUnit.SECONDS) }
@@ -295,12 +302,12 @@ fun AddEntryDialog(
tonalElevation = 2.dp
) {
Box(Modifier.fillMaxWidth().clickable { dropDownOpen = true }.padding(8.dp)) {
label?.let { QuickLabel(it) } ?: Text("Select label...", fontStyle = FontStyle.Italic)
label?.let { QuickVersion(it) } ?: Text("Select version...", fontStyle = FontStyle.Italic)
}
}
DropdownMenu(dropDownOpen, { dropDownOpen = false }) {
labels.values.forEach { lbl ->
DropdownMenuItem({ QuickLabel(lbl) }, { label = lbl; dropDownOpen = false })
DropdownMenuItem({ QuickVersion(lbl) }, { label = lbl; dropDownOpen = false })
}
HorizontalDivider(Modifier.height(1.dp))
@@ -378,14 +385,14 @@ fun AddEntryDialog(
}
}
if(addingLabel) AddLabelDialog({ addingLabel = false }, onAddLbl)
if(addingLabel) AddVersionDialog({ addingLabel = false }, onAddLbl)
}
}
@Composable
fun ProjectPlotArea(vm: ProjectVM){
val entries by vm.entries
val labels by vm.labels
val versions by vm.versions
val editable by vm.editable
var displayWarmups by remember { mutableStateOf(false) }
@@ -400,8 +407,8 @@ fun ProjectPlotArea(vm: ProjectVM){
var timeFilter by remember { mutableStateOf(0f..1f) }
var timeFilterString by remember { mutableStateOf("") }
val labelFilter = remember(labels) { mutableStateSetFrom(labels.values) }
val labelFilterKey by labelFilter.revision
val versionFilter = remember(versions) { mutableStateSetFrom(versions.values) }
val labelVersionFilter by versionFilter.revision
val allInputs by vm.inputs
val allHardware by vm.hardware
@@ -410,7 +417,7 @@ fun ProjectPlotArea(vm: ProjectVM){
var filterInput by remember { mutableStateOf<String?>(null) }
var filterHardware by remember { mutableStateOf<String?>(null) }
LaunchedEffect(entries, displayWarmups, currentUnit, timeFilter, timeMin, timeMax, labelFilterKey, filterInput, filterHardware) {
LaunchedEffect(entries, displayWarmups, currentUnit, timeFilter, timeMin, timeMax, labelVersionFilter, filterInput, filterHardware) {
var maxX = 0f
var minY = 0f
var maxY = 0f
@@ -421,7 +428,7 @@ fun ProjectPlotArea(vm: ProjectVM){
renderableEntries = entries.mapNotNull { entry ->
val use = if(displayWarmups) entry.warmups else entry.measurements
if(entry.label !in labelFilter) return@mapNotNull null
if(entry.label !in versionFilter) 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
@@ -547,32 +554,32 @@ fun ProjectPlotArea(vm: ProjectVM){
Row(Modifier.weight(0.33f).fillMaxHeight()) {
Column(Modifier.weight(0.5f)) {
Text("Labels", style = MaterialTheme.typography.headlineSmall)
Text("Click a label to toggle its visibility.", fontStyle = FontStyle.Italic,
Text("Versions", style = MaterialTheme.typography.headlineSmall)
Text("Click a version to toggle its visibility.", fontStyle = FontStyle.Italic,
color = LocalContentColor.current.copy(alpha = 0.5f), style = MaterialTheme.typography.bodySmall)
Spacer(Modifier.height(10.dp))
if(labels.isEmpty()) {
if(versions.isEmpty()) {
Box(Modifier.fillMaxWidth().fillMaxHeight(0.25f)) {
Text("No labels yet.", Modifier.align(Alignment.Center), fontStyle = FontStyle.Italic)
Text("No versions yet.", Modifier.align(Alignment.Center), fontStyle = FontStyle.Italic)
}
}
else {
LazyColumn(Modifier.padding(start = 5.dp)) {
items(labels.toList().sortedBy { it.second.name }) { (_, lbl) ->
items(versions.toList().sortedBy { it.second.name }) { (_, lbl) ->
Box(Modifier.fillMaxWidth().clickable {
if(labelFilter.size == labels.size) {
labelFilter.clear()
labelFilter.add(lbl)
if(versionFilter.size == versions.size) {
versionFilter.clear()
versionFilter.add(lbl)
}
else {
labelFilter.toggle(lbl)
if(labelFilter.isEmpty()) labelFilter.addAll(labels.values)
versionFilter.toggle(lbl)
if(versionFilter.isEmpty()) versionFilter.addAll(versions.values)
}
}) {
Box {
QuickLabel(lbl, lbl !in labelFilter)
QuickVersion(lbl, lbl !in versionFilter)
if(lbl !in labelFilter) {
if(lbl !in versionFilter) {
Box(Modifier.matchParentSize()) {
HorizontalDivider(
Modifier.fillMaxWidth().align(Alignment.Center),
@@ -607,7 +614,9 @@ fun ProjectPlotArea(vm: ProjectVM){
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 { Spacer(Modifier.height(10.dp)) }
item { HorizontalDivider(Modifier.height(1.dp)) }
item { Spacer(Modifier.height(10.dp)) }
item { Text("Hardware", style = MaterialTheme.typography.headlineSmall) }
item {