Lots of layout

This commit is contained in:
jay-tux 2025-02-19 18:04:41 +01:00
parent bccff22866
commit 2c4de6d8aa
Signed by: jay-tux
GPG Key ID: 84302006B056926E
10 changed files with 496 additions and 52 deletions

View File

@ -1,23 +1,43 @@
package com.jaytux.grader
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.jaytux.grader.ui.ChevronLeft
import com.jaytux.grader.ui.CoursesView
import com.jaytux.grader.ui.toDp
import com.jaytux.grader.viewmodel.CourseListState
import org.jetbrains.compose.ui.tooling.preview.Preview
data class UiRoute(val heading: String, val content: @Composable (push: (UiRoute) -> Unit) -> Unit)
@Composable
@Preview
fun App() {
MaterialTheme {
val courseList = CourseListState()
Surface(Modifier.fillMaxSize().padding(10.dp)) {
CoursesView(courseList)
var stack by remember {
val start = UiRoute("Courses Overview") { CoursesView(courseList, it) }
mutableStateOf(listOf(start))
}
Column {
Surface(Modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.primary, tonalElevation = 10.dp, shadowElevation = 10.dp) {
Row(Modifier.padding(10.dp)) {
IconButton({ stack = stack.toMutableList().also { it.removeLast() } }, enabled = stack.size >= 2) {
Icon(ChevronLeft, "Back", Modifier.size(MaterialTheme.typography.headlineLarge.fontSize.toDp()))
}
Text(stack.last().heading, Modifier.align(Alignment.CenterVertically), style = MaterialTheme.typography.headlineLarge)
}
}
Surface(Modifier.fillMaxSize()) {
Box(Modifier.padding(10.dp)) {
stack.last().content { stack += (it) }
}
}
}
}
}

View File

@ -28,17 +28,19 @@ object Groups : UUIDTable("groups") {
object Students : UUIDTable("students") {
val name = varchar("name", 50)
val contact = varchar("contact", 50)
val note = text("note")
}
object GroupStudents : Table("grpStudents") {
object GroupStudents : CompositeIdTable("grpStudents") {
val groupId = reference("group_id", Groups.id)
val studentId = reference("student_id", Students.id)
val role = varchar("role", 50).nullable()
override val primaryKey = PrimaryKey(groupId, studentId)
}
object SoloStudents : Table("soloStudents") {
object EditionStudents : Table("editionStudents") {
val editionId = reference("edition_id", Editions.id)
val studentId = reference("student_id", Students.id)
@ -46,15 +48,15 @@ object SoloStudents : Table("soloStudents") {
}
object GroupAssignments : UUIDTable("grpAssgmts") {
val editionId = reference("edition_id_", Editions.id)
val name = varchar("name_", 50)
val assignment = text("assignment_")
val editionId = reference("edition_id", Editions.id)
val name = varchar("name", 50)
val assignment = text("assignment")
}
object SoloAssignments : UUIDTable("soloAssgmts") {
val editionId = GroupAssignments.reference("edition_id", Editions.id)
val name = GroupAssignments.varchar("name", 50)
val assignment = GroupAssignments.text("assignment")
val editionId = reference("edition_id", Editions.id)
val name = varchar("name", 50)
val assignment = text("assignment")
}
object GroupFeedbacks : CompositeIdTable("grpFdbks") {

View File

@ -10,7 +10,7 @@ object Database {
transaction {
SchemaUtils.create(
Courses, Editions, Groups,
Students, GroupStudents, SoloStudents,
Students, GroupStudents, EditionStudents,
GroupAssignments, SoloAssignments,
GroupFeedbacks, IndividualFeedbacks, SoloFeedbacks
)

View File

@ -21,7 +21,9 @@ class Edition(id: EntityID<UUID>) : Entity<UUID>(id) {
var course by Course referencedOn Editions.courseId
var name by Editions.name
val groups by Group referrersOn Groups.editionId
val soloStudents by Student via SoloStudents
val soloStudents by Student via EditionStudents
val soloAssignments by SoloAssignment referrersOn SoloAssignments.editionId
// val groupAssignments by GroupAssignment referrersOn GroupAssignments.editionId
}
class Group(id: EntityID<UUID>) : Entity<UUID>(id) {
@ -37,8 +39,9 @@ class Student(id: EntityID<UUID>) : Entity<UUID>(id) {
var name by Students.name
var note by Students.note
var contact by Students.contact
val groups by Group via GroupStudents
val soloCourses by Edition via SoloStudents
val courses by Edition via EditionStudents
}
class GroupAssignment(id: EntityID<UUID>) : Entity<UUID>(id) {

View File

@ -1,5 +1,6 @@
package com.jaytux.grader.ui
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@ -9,7 +10,6 @@ import androidx.compose.material.OutlinedTextField
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@ -20,12 +20,17 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.*
import com.jaytux.grader.data.Course
import androidx.compose.ui.window.DialogWindow
import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.rememberDialogState
import com.jaytux.grader.UiRoute
import com.jaytux.grader.data.Edition
import com.jaytux.grader.viewmodel.CourseListState
import com.jaytux.grader.viewmodel.EditionListState
import com.jaytux.grader.viewmodel.EditionState
@Composable
fun CoursesView(state: CourseListState) {
fun CoursesView(state: CourseListState, push: (UiRoute) -> Unit) {
val data by state.courses.entities
var showDialog by remember { mutableStateOf(false) }
@ -41,7 +46,7 @@ fun CoursesView(state: CourseListState) {
}
else {
LazyColumn(Modifier.fillMaxSize()) {
items(data) { CourseWidget(it) { state.delete(it) } }
items(data) { CourseWidget(state.getEditions(it), { state.delete(it) }, push) }
item {
Button({ showDialog = true }, Modifier.fillMaxWidth()) {
@ -51,45 +56,71 @@ fun CoursesView(state: CourseListState) {
}
}
if(showDialog) AddCourseDialog(data.map { it.name }, { showDialog = false }) { state.new(it) }
if(showDialog) AddStringDialog("Course name", data.map { it.name }, { showDialog = false }) { state.new(it) }
}
@Composable
fun AddCourseDialog(taken: List<String>, onClose: () -> Unit, onSave: (String) -> Unit) = DialogWindow(
onCloseRequest = onClose,
state = rememberDialogState(size = DpSize(400.dp, 300.dp), position = WindowPosition(Alignment.Center))
) {
Surface(Modifier.fillMaxSize().padding(10.dp)) {
Box(Modifier.fillMaxSize()) {
var name by remember { mutableStateOf("") }
Column(Modifier.align(Alignment.Center)) {
OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), label = { Text("Course name") }, isError = name in taken)
Row {
Button({ onClose() }, Modifier.weight(0.45f)) { Text("Cancel") }
Spacer(Modifier.weight(0.1f))
Button({ onSave(name); onClose() }, Modifier.weight(0.45f)) { Text("Save") }
}
}
}
}
}
fun CourseWidget(state: EditionListState, onDelete: () -> Unit, push: (UiRoute) -> Unit) {
val editions by state.editions.entities
var isOpened by remember { mutableStateOf(false) }
var showDialog by remember { mutableStateOf(false) }
@Composable
fun CourseWidget(course: Course, onDelete: () -> Unit) {
val editions = remember(course) { course.loadEditions().size }
Surface(Modifier.fillMaxWidth().padding(horizontal = 5.dp, vertical = 10.dp), shape = MaterialTheme.shapes.medium, tonalElevation = 2.dp, shadowElevation = 2.dp) {
val callback = { it: Edition ->
val s = EditionState(it)
val route = UiRoute("${state.course.name}: ${it.name}") {
EditionView(s)
}
push(route)
}
Surface(Modifier.fillMaxWidth().padding(horizontal = 5.dp, vertical = 10.dp).clickable { isOpened = !isOpened }, shape = MaterialTheme.shapes.medium, tonalElevation = 2.dp, shadowElevation = 2.dp) {
Row {
Column(Modifier.weight(1f)) {
Text(course.name, Modifier.padding(5.dp), style = MaterialTheme.typography.headlineMedium)
Column(Modifier.weight(1f).padding(5.dp)) {
Row {
Spacer(Modifier.width(15.dp))
Text("$editions editions", fontStyle = FontStyle.Italic)
Icon(
if (isOpened) ChevronDown else ChevronRight, "Toggle editions",
Modifier.size(MaterialTheme.typography.headlineMedium.fontSize.toDp())
.align(Alignment.CenterVertically)
)
Column {
Text(state.course.name, style = MaterialTheme.typography.headlineMedium)
}
}
Row {
Spacer(Modifier.width(25.dp))
Text(
"${editions.size} edition(s)",
fontStyle = FontStyle.Italic,
style = MaterialTheme.typography.bodySmall
)
}
if(isOpened) {
Row {
Spacer(Modifier.width(25.dp))
Column {
editions.forEach { EditionWidget(it, { callback(it) }) { state.delete(it) } }
Button({ showDialog = true }, Modifier.fillMaxWidth()) { Text("Add edition") }
}
}
}
}
Column {
IconButton({ onDelete() }) { Icon(Icons.Default.Delete, "Remove") }
IconButton({ TODO() }) { Icon(Icons.Default.Edit, "Edit") }
IconButton({ TODO() }, enabled = false) { Icon(Icons.Default.Edit, "Edit") }
}
}
}
if(showDialog) AddStringDialog("Edition name", editions.map { it.name }, { showDialog = false }) { state.new(it) }
}
@Composable
fun EditionWidget(edition: Edition, onOpen: () -> Unit, onDelete: () -> Unit) {
Surface(Modifier.fillMaxWidth().padding(horizontal = 5.dp, vertical = 10.dp).clickable { onOpen() }, shape = MaterialTheme.shapes.medium, tonalElevation = 2.dp, shadowElevation = 2.dp) {
Row(Modifier.padding(5.dp)) {
Text(edition.name, Modifier.weight(1f), style = MaterialTheme.typography.headlineSmall)
IconButton({ onDelete() }) { Icon(Icons.Default.Delete, "Remove") }
}
}
}

View File

@ -0,0 +1,186 @@
package com.jaytux.grader.ui
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.Checkbox
import androidx.compose.material.OutlinedTextField
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.*
import androidx.compose.material3.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogWindow
import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.rememberDialogState
import com.jaytux.grader.data.*
import com.jaytux.grader.viewmodel.EditionState
@Composable
fun EditionView(state: EditionState) = Row {
var isGroup by remember { mutableStateOf(false) }
val students by state.students.entities
val groups by state.groups.entities
val solo by state.solo.entities
val groupAs by state.groupAs//.entities
TabLayout(
listOf("Students", "Groups"),
if(isGroup) 1 else 0,
{ isGroup = it == 1 },
{ Text(it) },
Modifier.weight(0.25f)
) {
Column(Modifier.fillMaxSize()) {
if(isGroup) {
Box(Modifier.weight(0.5f)) {
GroupsWidget(state.course, state.edition, groups, {}) { state.newGroup(it) }
}
Box(Modifier.weight(0.5f)) { GroupAssignmentsWidget(groupAs, {}) {} }
}
else {
Box(Modifier.weight(0.5f)) {
StudentsWidget(state.course, state.edition, students, {}) { name, note, contact, addToEdition ->
state.newStudent(name, note, contact, addToEdition)
}
}
Box(Modifier.weight(0.5f)) { AssignmentsWidget(solo, {}) {} }
}
}
}
Box(Modifier.weight(0.75f)) {}
}
@Composable
fun StudentsWidget(
course: Course,
edition: Edition,
students: List<Student>,
onSelect: (Int) -> Unit,
onAdd: (name: String, note: String, contact: String, addToEdition: Boolean) -> Unit
) = Column(Modifier.padding(10.dp)) {
Text("Student list", style = MaterialTheme.typography.headlineMedium)
var showDialog by remember { mutableStateOf(false) }
if(students.isEmpty()) {
Box(Modifier.fillMaxSize()) {
Column(Modifier.align(Alignment.Center)) {
Text(
"Course ${course.name} (edition ${edition.name})\nhas no students yet.",
Modifier.align(Alignment.CenterHorizontally),
textAlign = TextAlign.Center
)
Button({ showDialog = true }, Modifier.align(Alignment.CenterHorizontally)) {
Text("Add a student")
}
}
}
}
else {
LazyColumn(Modifier.padding(5.dp).weight(1f)) {
itemsIndexed(students) { idx, it ->
Surface(Modifier.fillMaxWidth().clickable { onSelect(idx) }) {
Text(it.name, Modifier.padding(5.dp))
}
}
}
Button({ showDialog = true }, Modifier.fillMaxWidth()) {
Text("Add a student")
}
}
if(showDialog) StudentDialog(course, edition, { showDialog = false }, onAdd)
}
@Composable
fun StudentDialog(
course: Course,
edition: Edition,
onClose: () -> Unit,
onAdd: (name: String, note: String, contact: String, addToEdition: Boolean) -> Unit
) = DialogWindow(
onCloseRequest = onClose,
state = rememberDialogState(size = DpSize(600.dp, 400.dp), position = WindowPosition(Alignment.Center))
) {
Surface(Modifier.fillMaxSize().padding(10.dp)) {
Box(Modifier.fillMaxSize()) {
var name by remember { mutableStateOf("") }
var contact by remember { mutableStateOf("") }
var note by remember { mutableStateOf("") }
var add by remember { mutableStateOf(true) }
Column(Modifier.align(Alignment.Center)) {
OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), singleLine = true, label = { Text("Student name") })
OutlinedTextField(contact, { contact = it }, Modifier.fillMaxWidth(), singleLine = true, label = { Text("Student contact") })
OutlinedTextField(note, { note = it }, Modifier.fillMaxWidth(), singleLine = false, minLines = 3, label = { Text("Note") })
Row {
Checkbox(add, { add = it })
Text("Add student to ${course.name} ${edition.name}?", Modifier.align(Alignment.CenterVertically))
}
CancelSaveRow(name.isNotBlank() && contact.isNotBlank(), onClose) {
onAdd(name, note, contact, add)
onClose()
}
}
}
}
}
@Composable
fun GroupsWidget(
course: Course,
edition: Edition,
groups: List<Group>,
onSelect: (Int) -> Unit,
onAdd: (name: String) -> Unit
) = Column(Modifier.padding(10.dp)) {
Text("Group list", style = MaterialTheme.typography.headlineMedium)
var showDialog by remember { mutableStateOf(false) }
if(groups.isEmpty()) {
Box(Modifier.fillMaxSize()) {
Column(Modifier.align(Alignment.Center)) {
Text(
"Course ${course.name} (edition ${edition.name})\nhas no groups yet.",
Modifier.align(Alignment.CenterHorizontally),
textAlign = TextAlign.Center
)
Button({ showDialog = true }, Modifier.align(Alignment.CenterHorizontally)) {
Text("Add a group")
}
}
}
}
else {
LazyColumn(Modifier.padding(5.dp).weight(1f)) {
itemsIndexed(groups) { idx, it ->
Surface(Modifier.fillMaxWidth().clickable { onSelect(idx) }) {
Text(it.name, Modifier.padding(5.dp))
}
}
}
Button({ showDialog = true }, Modifier.fillMaxWidth()) {
Text("Add a group")
}
}
if(showDialog) AddStringDialog("Group name", groups.map { it.name }, { showDialog = false }) { onAdd(it) }
}
@Composable
fun AssignmentsWidget(assignments: List<SoloAssignment>, onSelect: (Int) -> Unit, onAdd: (name: String) -> Unit) {
//
}
@Composable
fun GroupAssignmentsWidget(assignments: List<GroupAssignment>, onSelect: (Int) -> Unit, onAdd: (name: String) -> Unit) {
//
}

View File

@ -0,0 +1,88 @@
package com.jaytux.grader.ui
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathFillType
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
val ChevronRight: ImageVector by lazy {
ImageVector.Builder(
name = "ChevronRight",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f
).apply {
path(
fill = null,
fillAlpha = 1.0f,
stroke = SolidColor(Color(0xFF000000)),
strokeAlpha = 1.0f,
strokeLineWidth = 2f,
strokeLineCap = StrokeCap.Round,
strokeLineJoin = StrokeJoin.Round,
strokeLineMiter = 1.0f,
pathFillType = PathFillType.NonZero
) {
moveTo(9f, 18f)
lineToRelative(6f, -6f)
lineToRelative(-6f, -6f)
}
}.build()
}
val ChevronDown: ImageVector by lazy {
ImageVector.Builder(
name = "ChevronDown",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f
).apply {
path(
fill = null,
fillAlpha = 1.0f,
stroke = SolidColor(Color(0xFF000000)),
strokeAlpha = 1.0f,
strokeLineWidth = 2f,
strokeLineCap = StrokeCap.Round,
strokeLineJoin = StrokeJoin.Round,
strokeLineMiter = 1.0f,
pathFillType = PathFillType.NonZero
) {
moveTo(6f, 9f)
lineToRelative(6f, 6f)
lineToRelative(6f, -6f)
}
}.build()
}
public val ChevronLeft: ImageVector by lazy {
ImageVector.Builder(
name = "ChevronLeft",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 24f,
viewportHeight = 24f
).apply {
path(
fill = null,
fillAlpha = 1.0f,
stroke = SolidColor(Color(0xFF000000)),
strokeAlpha = 1.0f,
strokeLineWidth = 2f,
strokeLineCap = StrokeCap.Round,
strokeLineJoin = StrokeJoin.Round,
strokeLineMiter = 1.0f,
pathFillType = PathFillType.NonZero
) {
moveTo(15f, 18f)
lineToRelative(-6f, -6f)
lineToRelative(6f, -6f)
}
}.build()
}

View File

@ -0,0 +1,9 @@
package com.jaytux.grader.ui
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
@Composable
fun TextUnit.toDp(): Dp = with(LocalDensity.current) { value.toDp() }

View File

@ -0,0 +1,67 @@
package com.jaytux.grader.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogWindow
import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.rememberDialogState
@Composable
fun CancelSaveRow(canSave: Boolean, onCancel: () -> Unit, onSave: () -> Unit) {
Row {
Button({ onCancel() }, Modifier.weight(0.45f)) { Text("Cancel") }
Spacer(Modifier.weight(0.1f))
Button({ onSave() }, Modifier.weight(0.45f), enabled = canSave) { Text("Save") }
}
}
@Composable
fun <T> TabLayout(
options: List<T>,
currentIndex: Int,
onSelect: (Int) -> Unit,
optionContent: @Composable (T) -> Unit,
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) = Column(modifier) {
TabRow(currentIndex) {
options.forEachIndexed { idx, it ->
Tab(
selected = idx == currentIndex,
onClick = { onSelect(idx) },
text = { optionContent(it) }
)
}
}
content()
}
@Composable
fun AddStringDialog(label: String, taken: List<String>, onClose: () -> Unit, onSave: (String) -> Unit) = DialogWindow(
onCloseRequest = onClose,
state = rememberDialogState(size = DpSize(400.dp, 300.dp), position = WindowPosition(Alignment.Center))
) {
Surface(Modifier.fillMaxSize().padding(10.dp)) {
Box(Modifier.fillMaxSize()) {
var name by remember { mutableStateOf("") }
Column(Modifier.align(Alignment.Center)) {
androidx.compose.material.OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), label = { Text(label) }, isError = name in taken)
CancelSaveRow(name.isNotBlank() && name !in taken, onClose) {
onSave(name)
onClose()
}
}
}
}
}

View File

@ -3,11 +3,10 @@ package com.jaytux.grader.viewmodel
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import com.jaytux.grader.data.Course
import com.jaytux.grader.data.Edition
import com.jaytux.grader.data.Editions
import com.jaytux.grader.data.*
import org.jetbrains.exposed.dao.Entity
import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.transactions.transaction
import java.util.*
@ -36,8 +35,47 @@ class CourseListState {
transaction { course.delete() }
courses.refresh()
}
fun getEditions(course: Course) = EditionListState(course)
}
class EditionListState(private val course: Course) {
class EditionListState(val course: Course) {
val editions = RawDbState { Edition.find { Editions.courseId eq course.id }.toList() }
fun new(name: String) {
transaction { Edition.new { this.name = name; this.course = this@EditionListState.course } }
editions.refresh()
}
fun delete(edition: Edition) {
transaction { edition.delete() }
editions.refresh()
}
}
class EditionState(val edition: Edition) {
val course = transaction { edition.course }
val students = RawDbState { edition.soloStudents.toList() }
val groups = RawDbState { edition.groups.toList() }
val groupAs = mutableStateOf(listOf<GroupAssignment>())
val solo = RawDbState { edition.soloAssignments.toList() }
fun newStudent(name: String, contact: String, note: String, addToEdition: Boolean) {
transaction {
val student = Student.new { this.name = name; this.contact = contact; this.note = note }
if(addToEdition) EditionStudents.insert {
it[editionId] = edition.id
it[studentId] = student.id
}
}
if(addToEdition) students.refresh()
}
fun newGroup(name: String) {
transaction {
Group.new { this.name = name; this.edition = this@EditionState.edition }
groups.refresh()
}
}
}