Re-orderable assignments
This commit is contained in:
parent
63c4197cfc
commit
49e3b8126f
|
@ -53,6 +53,7 @@ object EditionStudents : Table("editionStudents") {
|
|||
|
||||
object GroupAssignments : UUIDTable("grpAssgmts") {
|
||||
val editionId = reference("edition_id", Editions.id)
|
||||
val number = integer("number").nullable()
|
||||
val name = varchar("name", 50)
|
||||
val assignment = text("assignment")
|
||||
val deadline = datetime("deadline")
|
||||
|
@ -60,6 +61,7 @@ object GroupAssignments : UUIDTable("grpAssgmts") {
|
|||
|
||||
object SoloAssignments : UUIDTable("soloAssgmts") {
|
||||
val editionId = reference("edition_id", Editions.id)
|
||||
val number = integer("number").nullable()
|
||||
val name = varchar("name", 50)
|
||||
val assignment = text("assignment")
|
||||
val deadline = datetime("deadline")
|
||||
|
|
|
@ -10,9 +10,8 @@ import java.util.UUID
|
|||
class Course(id: EntityID<UUID>) : Entity<UUID>(id) {
|
||||
companion object : EntityClass<UUID, Course>(Courses)
|
||||
|
||||
fun loadEditions() = transaction { Edition.find { Editions.courseId eq this@Course.id }.toList() }
|
||||
|
||||
var name by Courses.name
|
||||
val editions by Edition referrersOn Editions.courseId
|
||||
}
|
||||
|
||||
class Edition(id: EntityID<UUID>) : Entity<UUID>(id) {
|
||||
|
@ -57,6 +56,7 @@ class GroupAssignment(id: EntityID<UUID>) : Entity<UUID>(id) {
|
|||
companion object : EntityClass<UUID, GroupAssignment>(GroupAssignments)
|
||||
|
||||
var edition by Edition referencedOn GroupAssignments.editionId
|
||||
var number by GroupAssignments.number
|
||||
var name by GroupAssignments.name
|
||||
var assignment by GroupAssignments.assignment
|
||||
var deadline by GroupAssignments.deadline
|
||||
|
@ -66,6 +66,7 @@ class SoloAssignment(id: EntityID<UUID>) : Entity<UUID>(id) {
|
|||
companion object : EntityClass<UUID, SoloAssignment>(SoloAssignments)
|
||||
|
||||
var edition by Edition referencedOn SoloAssignments.editionId
|
||||
var number by SoloAssignments.number
|
||||
var name by SoloAssignments.name
|
||||
var assignment by SoloAssignments.assignment
|
||||
var deadline by SoloAssignments.deadline
|
||||
|
|
|
@ -4,6 +4,11 @@ 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.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowDownward
|
||||
import androidx.compose.material.icons.filled.ArrowUpward
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
|
@ -11,8 +16,13 @@ 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.*
|
||||
import com.jaytux.grader.data.*
|
||||
import androidx.compose.ui.window.DialogWindow
|
||||
import androidx.compose.ui.window.WindowPosition
|
||||
import androidx.compose.ui.window.rememberDialogState
|
||||
import com.jaytux.grader.data.Course
|
||||
import com.jaytux.grader.data.Edition
|
||||
import com.jaytux.grader.data.Group
|
||||
import com.jaytux.grader.data.Student
|
||||
import com.jaytux.grader.viewmodel.*
|
||||
|
||||
data class Navigators(
|
||||
|
@ -29,9 +39,7 @@ fun EditionView(state: EditionState) = Row(Modifier.padding(0.dp)) {
|
|||
val groups by state.groups.entities
|
||||
val solo by state.solo.entities
|
||||
val groupAs by state.groupAs.entities
|
||||
val mergedAssignments by remember(solo, groupAs) {
|
||||
mutableStateOf(Assignment.merge(groupAs, solo))
|
||||
}
|
||||
val mergedAssignments by remember(solo, groupAs) { mutableStateOf(Assignment.merge(groupAs, solo)) }
|
||||
val hist by state.history
|
||||
|
||||
val navs = Navigators(
|
||||
|
@ -68,7 +76,8 @@ fun EditionView(state: EditionState) = Row(Modifier.padding(0.dp)) {
|
|||
course, edition, mergedAssignments, id,
|
||||
{ state.navTo(it) },
|
||||
{ type, name -> state.newAssignment(type, name) },
|
||||
{ a, name -> state.setAssignmentTitle(a, name) }
|
||||
{ a, name -> state.setAssignmentTitle(a, name) },
|
||||
{ a1, a2 -> state.swapOrder(a1, a2) }
|
||||
) { a -> state.delete(a) }
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +99,12 @@ fun EditionView(state: EditionState) = Row(Modifier.padding(0.dp)) {
|
|||
}
|
||||
OpenPanel.Assignment -> {
|
||||
if(id == -1) PaneHeader("Nothing selected", "assignments", course, edition)
|
||||
else PaneHeader(mergedAssignments[id].name(), "assignment", course, edition)
|
||||
else {
|
||||
when(val a = mergedAssignments[id]) {
|
||||
is Assignment.SAssignment -> PaneHeader(a.name(), "individual assignment", course, edition)
|
||||
is Assignment.GAssignment -> PaneHeader(a.name(), "group assignment", course, edition)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -211,7 +225,7 @@ fun AssignmentPanel(
|
|||
course: Course, edition: Edition, assignments: List<Assignment>,
|
||||
selected: Int, onSelect: (Int) -> Unit,
|
||||
onAdd: (AssignmentType, String) -> Unit, onUpdate: (Assignment, String) -> Unit,
|
||||
onDelete: (Assignment) -> Unit
|
||||
onSwapOrder: (Assignment, Assignment) -> Unit, onDelete: (Assignment) -> Unit
|
||||
) = Column(Modifier.padding(10.dp)) {
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
var deleting by remember { mutableStateOf(-1) }
|
||||
|
@ -264,12 +278,25 @@ fun AssignmentPanel(
|
|||
{ Text("Add an assignment") },
|
||||
{ showDialog = true }
|
||||
) { idx, it ->
|
||||
SelectEditDeleteRow(
|
||||
Selectable(
|
||||
selected == idx,
|
||||
{ onSelect(idx) }, { onSelect(-1) },
|
||||
{ editing = idx }, { deleting = idx }
|
||||
{ onSelect(idx) }, { onSelect(-1) }
|
||||
) {
|
||||
Text(it.name(), Modifier.padding(5.dp))
|
||||
Row {
|
||||
Text(it.name(), Modifier.padding(5.dp).align(Alignment.CenterVertically).weight(1f))
|
||||
Column(Modifier.padding(2.dp)) {
|
||||
Icon(Icons.Default.ArrowUpward, "Move up", Modifier.clickable {
|
||||
if(idx > 0) onSwapOrder(assignments[idx], assignments[idx - 1])
|
||||
})
|
||||
Icon(Icons.Default.ArrowDownward, "Move down", Modifier.clickable {
|
||||
if(idx < assignments.size - 1) onSwapOrder(assignments[idx], assignments[idx + 1])
|
||||
})
|
||||
}
|
||||
Column(Modifier.padding(2.dp)) {
|
||||
Icon(Icons.Default.Edit, "Edit", Modifier.clickable { editing = idx })
|
||||
Icon(Icons.Default.Delete, "Delete", Modifier.clickable { deleting = idx })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -375,16 +375,27 @@ fun ItalicAndNormal(italic: String, normal: String) = Row{
|
|||
Text(normal)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Selectable(
|
||||
isSelected: Boolean,
|
||||
onSelect: () -> Unit, onDeselect: () -> Unit,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
Surface(
|
||||
Modifier.fillMaxWidth().clickable { if(isSelected) onDeselect() else onSelect() },
|
||||
tonalElevation = if (isSelected) 50.dp else 0.dp,
|
||||
shape = MaterialTheme.shapes.medium
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SelectEditDeleteRow(
|
||||
isSelected: Boolean,
|
||||
onSelect: () -> Unit, onDeselect: () -> Unit, onEdit: () -> Unit, onDelete: () -> Unit,
|
||||
content: @Composable BoxScope.() -> Unit
|
||||
) = Surface(
|
||||
Modifier.fillMaxWidth().clickable { if(isSelected) onDeselect() else onSelect() },
|
||||
tonalElevation = if (isSelected) 50.dp else 0.dp,
|
||||
shape = MaterialTheme.shapes.medium
|
||||
) {
|
||||
) = Selectable(isSelected, onSelect, onDeselect) {
|
||||
Row {
|
||||
Box(Modifier.weight(1f).align(Alignment.CenterVertically)) { content() }
|
||||
IconButton(onEdit, Modifier.align(Alignment.CenterVertically)) {
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.jetbrains.exposed.sql.*
|
|||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
|
||||
fun <T> MutableState<T>.immutable(): State<T> = this
|
||||
fun <T> SizedIterable<T>.sortAsc(vararg columns: Expression<*>) = this.orderBy(*(columns.map { it to SortOrder.ASC }.toTypedArray()))
|
||||
|
@ -22,14 +23,17 @@ sealed class Assignment {
|
|||
class GAssignment(val assignment: GroupAssignment) : Assignment() {
|
||||
override fun name(): String = assignment.name
|
||||
override fun id(): EntityID<UUID> = assignment.id
|
||||
override fun index(): Int? = assignment.number
|
||||
}
|
||||
class SAssignment(val assignment: SoloAssignment) : Assignment() {
|
||||
override fun name(): String = assignment.name
|
||||
override fun id(): EntityID<UUID> = assignment.id
|
||||
override fun index(): Int? = assignment.number
|
||||
}
|
||||
|
||||
abstract fun name(): String
|
||||
abstract fun id(): EntityID<UUID>
|
||||
abstract fun index(): Int?
|
||||
|
||||
companion object {
|
||||
fun from(assignment: GroupAssignment) = GAssignment(assignment)
|
||||
|
@ -38,9 +42,7 @@ sealed class Assignment {
|
|||
fun merge(groups: List<GroupAssignment>, solos: List<SoloAssignment>): List<Assignment> {
|
||||
val g = groups.map { from(it) }
|
||||
val s = solos.map { from(it) }
|
||||
return (g + s).sortedBy {
|
||||
(it as? GAssignment)?.assignment?.name ?: (it as SAssignment).assignment.name
|
||||
}
|
||||
return (g + s).sortedWith(compareBy<Assignment> { it.index() }.thenBy { it.name() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -153,9 +155,17 @@ class EditionState(val edition: Edition) {
|
|||
return instant.toLocalDateTime(TimeZone.currentSystemDefault())
|
||||
}
|
||||
|
||||
private fun nextIdx(): Int = max(
|
||||
solo.entities.value.maxOfOrNull { it.number ?: 0 } ?: 0,
|
||||
groupAs.entities.value.maxOfOrNull { it.number ?: 0 } ?: 0
|
||||
) + 1
|
||||
|
||||
fun newSoloAssignment(name: String) {
|
||||
transaction {
|
||||
SoloAssignment.new { this.name = name; this.edition = this@EditionState.edition; assignment = ""; deadline = now() }
|
||||
SoloAssignment.new {
|
||||
this.name = name; this.edition = this@EditionState.edition; assignment = ""; deadline = now()
|
||||
this.number = nextIdx()
|
||||
}
|
||||
solo.refresh()
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +177,10 @@ class EditionState(val edition: Edition) {
|
|||
}
|
||||
fun newGroupAssignment(name: String) {
|
||||
transaction {
|
||||
GroupAssignment.new { this.name = name; this.edition = this@EditionState.edition; assignment = ""; deadline = now() }
|
||||
GroupAssignment.new {
|
||||
this.name = name; this.edition = this@EditionState.edition; assignment = ""; deadline = now()
|
||||
this.number = nextIdx()
|
||||
}
|
||||
groupAs.refresh()
|
||||
}
|
||||
}
|
||||
|
@ -187,6 +200,42 @@ class EditionState(val edition: Edition) {
|
|||
is Assignment.SAssignment -> setSoloAssignmentTitle(assignment.assignment, title)
|
||||
}
|
||||
|
||||
fun swapOrder(a1: Assignment, a2: Assignment) {
|
||||
transaction {
|
||||
when(a1) {
|
||||
is Assignment.GAssignment -> {
|
||||
when(a2) {
|
||||
is Assignment.GAssignment -> {
|
||||
val temp = a1.assignment.number
|
||||
a1.assignment.number = a2.assignment.number
|
||||
a2.assignment.number = temp
|
||||
}
|
||||
is Assignment.SAssignment -> {
|
||||
val temp = a1.assignment.number
|
||||
a1.assignment.number = nextIdx()
|
||||
a2.assignment.number = temp
|
||||
}
|
||||
}
|
||||
}
|
||||
is Assignment.SAssignment -> {
|
||||
when(a2) {
|
||||
is Assignment.GAssignment -> {
|
||||
val temp = a1.assignment.number
|
||||
a1.assignment.number = a2.assignment.number
|
||||
a2.assignment.number = temp
|
||||
}
|
||||
is Assignment.SAssignment -> {
|
||||
val temp = a1.assignment.number
|
||||
a1.assignment.number = a2.assignment.number
|
||||
a2.assignment.number = temp
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
solo.refresh(); groupAs.refresh()
|
||||
}
|
||||
|
||||
fun delete(s: Student) {
|
||||
transaction {
|
||||
EditionStudents.deleteWhere { studentId eq s.id }
|
||||
|
|
Loading…
Reference in New Issue