Add musical markers popup in the editor
This commit is contained in:
parent
f32147f24d
commit
9550ab0e3c
2 changed files with 510 additions and 23 deletions
|
|
@ -0,0 +1,415 @@
|
|||
package mg.dot.feufaro.solfa
|
||||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AddCircleOutline
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||
import androidx.compose.material.icons.filled.RemoveCircleOutline
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.PlainTooltip
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TooltipBox
|
||||
import androidx.compose.material3.TooltipDefaults
|
||||
import androidx.compose.material3.rememberTooltipState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Popup
|
||||
import androidx.compose.ui.window.PopupProperties
|
||||
|
||||
data class Marker(
|
||||
val abbr: String,
|
||||
val description: String,
|
||||
val canEditValue: Boolean = false,
|
||||
val defaultValue: String = "",
|
||||
val options: List<String> = emptyList()
|
||||
)
|
||||
|
||||
data class MarkerGroup(
|
||||
val title: String,
|
||||
val markers: List<Marker>
|
||||
)
|
||||
|
||||
private val markerGroups = listOf(
|
||||
MarkerGroup(
|
||||
title = "Nuances",
|
||||
markers = listOf(
|
||||
Marker("ppp", "Très très doux"),
|
||||
Marker("pp", "Pianissimo"),
|
||||
Marker("p", "Piano"),
|
||||
Marker("mp", "Mezzo piano"),
|
||||
Marker("mf", "Mezzo forte"),
|
||||
Marker("f", "Forte"),
|
||||
Marker("ff", "Fortissimo"),
|
||||
Marker("fff", "Très très fort"),
|
||||
|
||||
Marker("cres.", "Augmenter progressivement le volume"),
|
||||
Marker("<", "Crescendo"),
|
||||
Marker("decresc.", "Diminuer progressivement le volume"),
|
||||
Marker("dim.", "Diminuendo"),
|
||||
Marker(">", "Decrescendo"),
|
||||
|
||||
Marker("sf", "Accent soudain"),
|
||||
Marker("sfz", "Sforzato"),
|
||||
Marker("fz", "Forzando"),
|
||||
Marker("rfz", "Rinforzando"),
|
||||
Marker("fp", "Forte puis immédiatement piano"),
|
||||
Marker("pf", "Piano puis forte"),
|
||||
Marker("sub.p", "Subito piano"),
|
||||
Marker("sub.f", "Subito forte")
|
||||
)
|
||||
),
|
||||
|
||||
MarkerGroup(
|
||||
title = "Tempo",
|
||||
markers = listOf(
|
||||
Marker("𝄐", "Point d'orgue"),
|
||||
Marker("Largh.", "Très très lent"),
|
||||
Marker("Grave", "Très lent et solennel"),
|
||||
Marker("Largo", "Large et lent"),
|
||||
Marker("Lento", "Lent"),
|
||||
Marker("Adagio", "Assez lent"),
|
||||
Marker("And.", "Andante"),
|
||||
Marker("Mod.", "Moderato"),
|
||||
Marker("Alleg.", "Allegretto"),
|
||||
Marker("All.", "Allegro"),
|
||||
Marker("Viv.", "Vivace"),
|
||||
Marker("Presto", "Très rapide"),
|
||||
Marker("Prestiss.", "Extrêmement rapide"),
|
||||
|
||||
Marker("accel.", "Accélérer progressivement"),
|
||||
Marker("rit.", "Ralentir progressivement"),
|
||||
Marker("rall.", "Ralentir"),
|
||||
Marker("riten.", "Retenir le tempo"),
|
||||
Marker("string.", "Presser le tempo"),
|
||||
Marker("♩=", "Changement tempo", canEditValue = true, defaultValue = "60"),
|
||||
Marker("allarg.", "Élargir le tempo"),
|
||||
Marker("a tempo", "Retour au tempo initial"),
|
||||
Marker("Tempo I", "Retour au premier tempo"),
|
||||
Marker("rubato", "Tempo libre"),
|
||||
Marker("meno mosso", "Moins rapide"),
|
||||
Marker("più mosso", "Plus rapide")
|
||||
)
|
||||
),
|
||||
|
||||
MarkerGroup(
|
||||
title = "Reprises",
|
||||
markers = listOf(
|
||||
Marker("D.C.", "Da Capo"),
|
||||
Marker("D.C. Fin", "Retour au début jusqu'à Fine"),
|
||||
Marker("D.C. al Coda", "Retour au début puis aller à la Coda"),
|
||||
Marker("D.S.", "Retour au Segno"),
|
||||
Marker("End", "Fin"),
|
||||
Marker("D.S. Fin", "Retour au Segno jusqu'à Fine"),
|
||||
Marker("Farany", "Fin"),
|
||||
Marker("$", "Segno"),
|
||||
Marker("Fine", "Fin"),
|
||||
Marker("𝄌", "Coda"),
|
||||
Marker("To Coda", "Aller à la Coda"),
|
||||
Marker("||:", "Début de reprise"),
|
||||
Marker(":||", "Fin de reprise"),
|
||||
Marker("Bis", "Rejouer")
|
||||
)
|
||||
),
|
||||
|
||||
MarkerGroup(
|
||||
title = "Tonalité",
|
||||
markers = listOf(
|
||||
Marker("Dô dia", "Modulation", canEditValue = true, defaultValue = "C", options = Transpose.keyToNumber)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@Composable
|
||||
fun MarkerPopup(
|
||||
menuPosition: IntOffset,
|
||||
onDismiss: () -> Unit,
|
||||
onMarkerSelected: (String) -> Unit
|
||||
) {
|
||||
var expandedGroupIndex by remember {
|
||||
mutableStateOf<Int?>(null)
|
||||
}
|
||||
Popup(
|
||||
offset = menuPosition,
|
||||
onDismissRequest = onDismiss,
|
||||
properties = PopupProperties(
|
||||
focusable = true,
|
||||
dismissOnBackPress = true,
|
||||
dismissOnClickOutside = true
|
||||
)
|
||||
) {
|
||||
|
||||
Surface (
|
||||
modifier = Modifier
|
||||
.widthIn(max=250.dp)
|
||||
.heightIn(max=450.dp),
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = MaterialTheme.colorScheme.onSecondary.copy(0.75f),
|
||||
) {
|
||||
LazyColumn {
|
||||
itemsIndexed(markerGroups) { index, group ->
|
||||
ExpandableGroup(
|
||||
group = group,
|
||||
expanded = expandedGroupIndex == index,
|
||||
onExpand = {
|
||||
expandedGroupIndex = if(expandedGroupIndex == index) null else index
|
||||
},
|
||||
onMarkerSelected = {
|
||||
onMarkerSelected(it)
|
||||
onDismiss()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(
|
||||
ExperimentalFoundationApi::class,
|
||||
ExperimentalMaterial3Api::class,
|
||||
ExperimentalLayoutApi::class
|
||||
)
|
||||
@Composable
|
||||
private fun ExpandableGroup(
|
||||
group: MarkerGroup,
|
||||
expanded: Boolean,
|
||||
onExpand: () -> Unit,
|
||||
onMarkerSelected: (String) -> Unit
|
||||
) {
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onExpand() }
|
||||
.padding(horizontal = 12.dp, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = group.title,
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.headlineSmall.copy(
|
||||
color = MaterialTheme.colorScheme.background
|
||||
)
|
||||
)
|
||||
Icon(
|
||||
imageVector = if (expanded)
|
||||
Icons.Default.RemoveCircleOutline
|
||||
else
|
||||
Icons.Default.AddCircleOutline,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = expanded,
|
||||
enter = expandVertically(
|
||||
animationSpec = tween(250)
|
||||
) + fadeIn(
|
||||
animationSpec = tween(250)
|
||||
),
|
||||
exit = shrinkVertically(
|
||||
animationSpec = tween(200)
|
||||
) + fadeOut(
|
||||
animationSpec = tween(200)
|
||||
)
|
||||
) {
|
||||
Column {
|
||||
FlowRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 220.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 2.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
group.markers.forEach { marker ->
|
||||
if (marker.canEditValue) {
|
||||
var value by remember { mutableStateOf(marker.defaultValue) }
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
.background(Color.Gray)
|
||||
.padding(10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
if (marker.options.isNotEmpty()) {
|
||||
Text(
|
||||
marker.abbr,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
var optionsExpanded by remember { mutableStateOf(false) }
|
||||
Box(modifier = Modifier.fillMaxWidth(0.6f)) {
|
||||
OutlinedButton(
|
||||
onClick = { optionsExpanded = true },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(2.dp)
|
||||
) {
|
||||
Text(
|
||||
value,
|
||||
style = MaterialTheme.typography.titleLarge.copy(
|
||||
fontWeight = FontWeight.Bold
|
||||
),
|
||||
color = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
}
|
||||
|
||||
DropdownMenu(
|
||||
expanded = optionsExpanded,
|
||||
onDismissRequest = { optionsExpanded = false }
|
||||
) {
|
||||
marker.options.forEach { option ->
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
value = option
|
||||
optionsExpanded = false
|
||||
},
|
||||
text = {
|
||||
Text(option)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Row(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
TextField(
|
||||
value = value,
|
||||
onValueChange = { input ->
|
||||
value = input.filter { it.isDigit() }
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(0.70f),
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Number
|
||||
),
|
||||
leadingIcon = {
|
||||
Text(
|
||||
text = marker.abbr,
|
||||
fontSize = MaterialTheme.typography.titleMedium.fontSize,
|
||||
color = MaterialTheme.colorScheme.onSecondary
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
Text(
|
||||
text = "bpm",
|
||||
fontSize = MaterialTheme.typography.titleSmall.fontSize,
|
||||
color = MaterialTheme.colorScheme.onSecondary
|
||||
)
|
||||
}
|
||||
)
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
val current = value.toIntOrNull() ?: 0
|
||||
value = (current + 1).toString()
|
||||
},
|
||||
modifier = Modifier.size(24.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.KeyboardArrowUp,
|
||||
contentDescription = "Augmenter le tempo",
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
val current = value.toIntOrNull() ?: 0
|
||||
value = (current - 1).coerceAtLeast(0).toString()
|
||||
},
|
||||
modifier = Modifier.size(24.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.KeyboardArrowDown,
|
||||
contentDescription = "Diminuer le tempo",
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
val result = marker.abbr + value
|
||||
onMarkerSelected(result)
|
||||
},
|
||||
modifier = Modifier.size(40.dp)
|
||||
) {
|
||||
Text(text = "✓", color = Color.White)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
tooltip = {
|
||||
PlainTooltip(
|
||||
containerColor = Color.DarkGray,
|
||||
contentColor = Color.White
|
||||
) {
|
||||
Text(
|
||||
text = marker.description,
|
||||
fontSize = MaterialTheme.typography.bodySmall.fontSize,
|
||||
modifier = Modifier.padding(8.dp)
|
||||
)
|
||||
}
|
||||
},
|
||||
state = rememberTooltipState()
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f, fill = true)
|
||||
.padding(4.dp)
|
||||
.background(Color.Gray)
|
||||
.clickable { onMarkerSelected(marker.abbr) }
|
||||
.padding(horizontal = 7.dp, vertical = 2.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = marker.abbr,
|
||||
fontSize = MaterialTheme.typography.titleSmall.fontSize,
|
||||
color = MaterialTheme.colorScheme.surface
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
|
|
@ -16,9 +16,13 @@ import androidx.compose.material3.*
|
|||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.layout.positionInParent
|
||||
import androidx.compose.ui.layout.positionInWindow
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
|
|
@ -29,6 +33,7 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
import androidx.compose.ui.unit.*
|
||||
import androidx.compose.ui.window.Popup
|
||||
import androidx.compose.ui.window.PopupProperties
|
||||
import mg.dot.feufaro.solfa.MarkerPopup
|
||||
import mg.dot.feufaro.solfa.Solfa
|
||||
import mg.dot.feufaro.solfa.TUOEditState
|
||||
import mg.dot.feufaro.solfa.TimeUnitObject
|
||||
|
|
@ -102,7 +107,11 @@ fun TUODetailDialog(
|
|||
}
|
||||
}
|
||||
|
||||
var canAddMark = mutableStateOf(false)
|
||||
var showMarkerPopup by remember { mutableStateOf(false) }
|
||||
var markerButtonPos by remember {
|
||||
mutableStateOf(IntOffset.Zero)
|
||||
}
|
||||
|
||||
Popup(
|
||||
offset = menuPosition,
|
||||
onDismissRequest = onDismiss,
|
||||
|
|
@ -271,7 +280,18 @@ fun TUODetailDialog(
|
|||
IconButton(
|
||||
modifier = Modifier.size(20.dp),
|
||||
onClick = {
|
||||
newSep = ""
|
||||
val state = TUOEditState(
|
||||
tuoIndex = globalIndex,
|
||||
notesByVoice = notes.toMap(),
|
||||
originalNotes = originalNotes.toMap(),
|
||||
originalLyricsByStanza = originalLyricsByStz.toMutableMap(),
|
||||
lyricsByStanza = mutableMapOf(),
|
||||
templateFragment = templateFragment,
|
||||
marker = if(marker.isEmpty()) "_" else marker,
|
||||
originalSep = initialSep,
|
||||
sep = ""
|
||||
)
|
||||
onSave(state)
|
||||
}) {
|
||||
Text(
|
||||
text = "/",
|
||||
|
|
@ -285,7 +305,19 @@ fun TUODetailDialog(
|
|||
IconButton(
|
||||
modifier = Modifier.size(20.dp),
|
||||
onClick = {
|
||||
newSep = "/"
|
||||
|
||||
val state = TUOEditState(
|
||||
tuoIndex = globalIndex,
|
||||
notesByVoice = notes.toMap(),
|
||||
originalNotes = originalNotes.toMap(),
|
||||
originalLyricsByStanza = originalLyricsByStz.toMutableMap(),
|
||||
lyricsByStanza = mutableMapOf(),
|
||||
templateFragment = templateFragment,
|
||||
marker = if(marker.isEmpty()) "_" else marker,
|
||||
originalSep = initialSep,
|
||||
sep = "/"
|
||||
)
|
||||
onSave(state)
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
|
|
@ -312,23 +344,13 @@ fun TUODetailDialog(
|
|||
)
|
||||
|
||||
}
|
||||
if(!editState.marker.isNullOrEmpty() || canAddMark.value) {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
MyTextEditField(
|
||||
value = marker,
|
||||
customFontSize = 14.sp,
|
||||
color = MaterialTheme.colorScheme.tertiary.copy(alpha = 1.5f),
|
||||
customPadding = 8.dp,
|
||||
customBrush = SolidColor(Color.White),
|
||||
isEditable = isEditable,
|
||||
isAddable = canAdd,
|
||||
funTransform = ::transformMarkerInput,
|
||||
onValueChng = { marker = it }
|
||||
)
|
||||
}
|
||||
if(!templateFragment.isNullOrBlank() && templateFragment !="-") {
|
||||
val hasMarker = marker.isNotEmpty()
|
||||
val tooltipText = if (hasMarker) {
|
||||
"Enlever la marqueur"
|
||||
} else {
|
||||
"Ajouter une marqueur"
|
||||
}
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
tooltip = {
|
||||
|
|
@ -336,18 +358,41 @@ fun TUODetailDialog(
|
|||
containerColor = Color.DarkGray,
|
||||
contentColor = Color.White
|
||||
) {
|
||||
Text(text = "Ajouter une marqueur", fontSize = 12.sp)
|
||||
Text(text = tooltipText, fontSize = MaterialTheme.typography.titleSmall.fontSize)
|
||||
}
|
||||
},
|
||||
state = rememberTooltipState()
|
||||
) {
|
||||
IconButton(
|
||||
modifier = Modifier.size(30.dp),
|
||||
modifier = Modifier.size(30.dp)
|
||||
.onGloballyPositioned { coordinates ->
|
||||
markerButtonPos = coordinates.positionInParent()
|
||||
.let {
|
||||
IntOffset(
|
||||
it.x.toInt(),
|
||||
it.y.toInt()
|
||||
)
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
canAddMark.value = true
|
||||
if(hasMarker) {
|
||||
val state = TUOEditState(
|
||||
tuoIndex = globalIndex,
|
||||
notesByVoice = notes.toMap(),
|
||||
originalNotes = originalNotes.toMap(),
|
||||
originalLyricsByStanza = originalLyricsByStz.toMutableMap(),
|
||||
lyricsByStanza = mutableMapOf(),
|
||||
templateFragment = templateFragment,
|
||||
marker = "_",
|
||||
originalSep = initialSep,
|
||||
sep = newSep
|
||||
)
|
||||
onSave(state)
|
||||
}
|
||||
showMarkerPopup = true
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
imageVector = if(hasMarker) Icons.Default.Remove else Icons.Default.Add,
|
||||
tint = MaterialTheme.colorScheme.secondary.copy(alpha = 1.5f),
|
||||
contentDescription = null
|
||||
)
|
||||
|
|
@ -541,6 +586,33 @@ fun TUODetailDialog(
|
|||
}
|
||||
}
|
||||
}
|
||||
if (showMarkerPopup) {
|
||||
MarkerPopup(
|
||||
menuPosition = IntOffset(
|
||||
x = markerButtonPos.x + 180,
|
||||
y = markerButtonPos.y + 100
|
||||
),
|
||||
onDismiss = {
|
||||
showMarkerPopup = false
|
||||
},
|
||||
onMarkerSelected = { marker ->
|
||||
showMarkerPopup = false
|
||||
|
||||
val state = TUOEditState(
|
||||
tuoIndex = globalIndex,
|
||||
notesByVoice = notes.toMap(),
|
||||
originalNotes = originalNotes.toMap(),
|
||||
originalLyricsByStanza = originalLyricsByStz.toMutableMap(),
|
||||
lyricsByStanza = mutableMapOf(),
|
||||
templateFragment = templateFragment,
|
||||
marker = marker,
|
||||
originalSep = initialSep,
|
||||
sep = newSep
|
||||
)
|
||||
onSave(state)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun unpackLyrics(lyrics: String): String {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue