feat(CountdownPill): implement a countdown pill component
- shows the remaining time until expiration with the circular progress bar and a tooltip - configurable timestamp and timeout in seconds (between 5 min and 7 days) - add the respective storybook page
This commit is contained in:
parent
02066d098d
commit
2739d2cf68
|
@ -0,0 +1,100 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
import shared.controls 1.0
|
||||
|
||||
import Storybook 1.0
|
||||
|
||||
SplitView {
|
||||
orientation: Qt.Horizontal
|
||||
|
||||
Logs { id: logs }
|
||||
|
||||
Pane {
|
||||
SplitView.fillWidth: true
|
||||
SplitView.fillHeight: true
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.palette.baseColor4
|
||||
}
|
||||
|
||||
CountdownPill {
|
||||
id: pill
|
||||
anchors.centerIn: parent
|
||||
timestamp: new Date()
|
||||
expirationSeconds: 300 // 5 minutes
|
||||
}
|
||||
}
|
||||
|
||||
LogsAndControlsPanel {
|
||||
SplitView.fillHeight: true
|
||||
SplitView.preferredWidth: 300
|
||||
|
||||
logsView.logText: logs.logText
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Button {
|
||||
text: "Set 2 days, 2 hours, 10 minutes"
|
||||
onClicked: {
|
||||
pill.timestamp = new Date()
|
||||
pill.expirationSeconds = 180600
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: "Set 1.5 hrs"
|
||||
onClicked: {
|
||||
pill.timestamp = new Date()
|
||||
pill.expirationSeconds = 5400
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: "Set 3 mins"
|
||||
onClicked: {
|
||||
pill.timestamp = new Date()
|
||||
pill.expirationSeconds = 180
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: "Set 1 min"
|
||||
onClicked: {
|
||||
pill.timestamp = new Date()
|
||||
pill.expirationSeconds = 60
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: "Set 6 secs"
|
||||
onClicked: {
|
||||
pill.timestamp = new Date()
|
||||
pill.expirationSeconds = 6
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: "Set 5 minutes (10 minutes ago) -> expired"
|
||||
onClicked: {
|
||||
const tenMinsAgo = new Date()
|
||||
tenMinsAgo.setMinutes(tenMinsAgo.getMinutes() - 10)
|
||||
pill.timestamp = tenMinsAgo
|
||||
pill.expirationSeconds = 5*60
|
||||
}
|
||||
}
|
||||
Label {
|
||||
text: "Remaining secs: %1".arg(pill.remainingSeconds)
|
||||
}
|
||||
Label {
|
||||
text: "Expired: %1".arg(pill.isExpired ? "true" : "false")
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// category: Controls
|
||||
|
||||
// https://www.figma.com/design/HrmZp1y4S77QJezRFRl6ku/dApp-Interactions---Milestone-1?node-id=3967-288229&node-type=frame&t=lHLlTZ0aJE0Uo3l9-0
|
|
@ -0,0 +1,127 @@
|
|||
import QtQuick 2.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
import AppLayouts.Communities.controls 1.0
|
||||
|
||||
IssuePill {
|
||||
id: root
|
||||
|
||||
// request timestamp
|
||||
required property date timestamp
|
||||
onTimestampChanged: Qt.callLater(reset)
|
||||
|
||||
// expiration timeout in seconds; min 5 minutes, max 7 days
|
||||
required property int expirationSeconds
|
||||
onExpirationSecondsChanged: Qt.callLater(reset)
|
||||
|
||||
readonly property bool isExpired: expirationSeconds > 0 && d.secsDiff <= 0
|
||||
readonly property int remainingSeconds: d.secsDiff
|
||||
|
||||
signal expired
|
||||
|
||||
iconLoaderComponent: StatusCircularProgressBar {
|
||||
value: d.progress
|
||||
primaryColor: root.baseColor
|
||||
secondaryColor: root.background.border.color
|
||||
}
|
||||
|
||||
implicitHeight: 32
|
||||
text: d.secsDiff < 0 ? qsTr("Expired") : d.formatSeconds(d.secsDiff + 1)
|
||||
|
||||
type: {
|
||||
if (d.secsDiff < 60) // under 1 minute
|
||||
return CountdownPill.Type.Error
|
||||
if (d.secsDiff < 60 * 5) // under 5 minutes
|
||||
return CountdownPill.Type.Warning
|
||||
return CountdownPill.Type.Primary
|
||||
}
|
||||
|
||||
font.family: Theme.palette.codeFont.name
|
||||
|
||||
function reset() {
|
||||
if (expirationSeconds === 0) {
|
||||
timer.stop()
|
||||
return
|
||||
}
|
||||
|
||||
const newTimestamp = timestamp
|
||||
newTimestamp.setSeconds(newTimestamp.getSeconds() + expirationSeconds)
|
||||
d.expirationTimestamp = newTimestamp
|
||||
|
||||
d.ticker = 0
|
||||
d.secsDiff = 0
|
||||
|
||||
console.warn("!!! RESET at:", timestamp, "; expires:", d.expirationTimestamp)
|
||||
|
||||
if (d.expirationTimestamp <= new Date()) {
|
||||
console.warn("Expiration time set in past, or expired already on:", d.expirationTimestamp)
|
||||
d.secsDiff = -1
|
||||
timer.stop()
|
||||
root.expired()
|
||||
return
|
||||
}
|
||||
|
||||
timer.restart()
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
readonly property real progress: d.secsDiff >= 0 ? d.secsDiff/(d.expirationTimestamp.valueOf() - timestamp.valueOf()) * 1000
|
||||
: 0
|
||||
|
||||
property var expirationTimestamp: root.timestamp
|
||||
property int secsDiff
|
||||
property int ticker
|
||||
|
||||
function formatSeconds(seconds) {
|
||||
const isoString = new Date(seconds * 1000).toISOString()
|
||||
const days = Math.floor(seconds/86400)
|
||||
const hrs = parseInt(isoString.substring(11, 13))
|
||||
const mins = parseInt(isoString.substring(14, 16))
|
||||
|
||||
var result = []
|
||||
if (days > 0)
|
||||
result.push(qsTr("%1d", "x days").arg(days))
|
||||
if (hrs > 0)
|
||||
result.push(qsTr("%1h", "x hours").arg(hrs))
|
||||
if (mins > 0) {
|
||||
if (days === 0 && hrs === 0 ) // long form
|
||||
result.push(qsTr("%n min(s)", "", mins))
|
||||
else
|
||||
result.push(qsTr("%1m", "x minutes").arg(mins))
|
||||
}
|
||||
if (days === 0 && hrs === 0 && mins === 0) {
|
||||
const secs = parseInt(isoString.substring(17, 19))
|
||||
if (secs >= 0)
|
||||
result.push(qsTr("%n sec(s)", "", secs))
|
||||
}
|
||||
return result.join(" ")
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timer
|
||||
repeat: true
|
||||
interval: 1000
|
||||
triggeredOnStart: true
|
||||
onTriggered: {
|
||||
d.ticker++
|
||||
d.secsDiff = (d.expirationTimestamp.valueOf() - root.timestamp.valueOf() - d.ticker*1000)/1000
|
||||
console.warn("!!! REMAINING SECS:", d.secsDiff, "; PROGRESS:", d.progress)
|
||||
if (d.secsDiff < 0) { // we let it run 1 more second to finish the animation
|
||||
timer.stop()
|
||||
root.expired()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StatusToolTip {
|
||||
id: tooltip
|
||||
visible: root.hovered && !!text
|
||||
text: root.isExpired ? qsTr("Expired on: %1").arg(LocaleUtils.formatDateTime(d.expirationTimestamp))
|
||||
: qsTr("Expires on: %1").arg(LocaleUtils.formatDateTime(d.expirationTimestamp))
|
||||
}
|
||||
}
|
|
@ -54,3 +54,4 @@ AssetsSectionDelegate 1.0 AssetsSectionDelegate.qml
|
|||
ExpandableTag 1.0 ExpandableTag.qml
|
||||
Padding 1.0 Padding.qml
|
||||
DecoratedListItem 1.0 DecoratedListItem.qml
|
||||
CountdownPill 1.0 CountdownPill.qml
|
||||
|
|
Loading…
Reference in New Issue