repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
quickshell/nuguland/VaultPanel.qml
Rawpragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Window
Item {
id: root
required property Theme theme
required property Vault vaultService
readonly property bool locked: !vaultService.unlocked
readonly property bool setupMode: vaultService.setupEditing || !vaultService.profile.serverUrl || !vaultService.profile.account
readonly property var detail: vaultService.detail || ({})
readonly property var fields: detail.fields || []
function focusPrimary() {
if (setupMode)
serverUrlSetup.input.forceActiveFocus();
else if (vaultService.status === "second-factor")
secondFactorCode.input.forceActiveFocus();
else if (locked)
masterPassword.input.forceActiveFocus();
else if (detail.requiresReprompt)
repromptPassword.input.forceActiveFocus();
else
searchField.input.forceActiveFocus();
}
function clearPasswordInputs() {
masterPassword.text = "";
repromptPassword.text = "";
showPassword.checked = false;
showRepromptPassword.checked = false;
}
function fieldEnding(suffix) {
for (let i = 0; i < fields.length; i++)
if (String(fields[i].id || "").endsWith(suffix))
return fields[i];
return null;
}
function copyFieldEnding(suffix) {
const field = fieldEnding(suffix);
if (field && (field.kind === "text" || field.kind === "url"))
vaultService.copy(field.id);
}
function fieldCopyable(suffix) {
const field = fieldEnding(suffix);
return !!field && (field.kind === "text" || field.kind === "url");
}
function reconcileVisibleSelection() {
const visible = vaultService.visibleItems;
if (visible.length === 0) {
vaultService.selectedId = "";
vaultService.detail = ({});
vaultService.resultScrollPosition = 0;
vaultService.detailScrollPosition = 0;
return;
}
if (!visible.some(item => item.id === vaultService.selectedId))
vaultService.select(visible[0].id);
}
onSetupModeChanged: if (visible)
Qt.callLater(root.focusPrimary)
onLockedChanged: if (visible)
Qt.callLater(root.focusPrimary)
Component.onCompleted: Qt.callLater(root.focusPrimary)
onVisibleChanged: if (visible)
Qt.callLater(root.focusPrimary)
onDetailChanged: if (visible && detail.requiresReprompt)
Qt.callLater(root.focusPrimary)
Connections {
target: root.vaultService
function onStatusChanged() {
if (root.visible && root.vaultService.status === "second-factor")
Qt.callLater(root.focusPrimary);
}
function onSelectedIdChanged() {
repromptPassword.text = "";
showRepromptPassword.checked = false;
}
function onLockGenerationChanged() {
root.clearPasswordInputs();
}
function onPanelOpenChanged() {
if (!root.vaultService.panelOpen)
root.clearPasswordInputs();
}
}
Connections {
target: root.Window.window
enabled: results.restoringScroll || detailScroll.restoringScroll
function onFrameSwapped() {
if (results.restoringScroll)
results.restoreScroll();
if (detailScroll.restoringScroll)
detailScroll.restoreScroll();
}
}
Shortcut {
sequence: "Ctrl+U"
context: Qt.WindowShortcut
enabled: !root.locked && root.fieldCopyable(".username")
onActivated: root.copyFieldEnding(".username")
}
Shortcut {
sequence: "Ctrl+P"
context: Qt.WindowShortcut
enabled: !root.locked && root.fieldCopyable(".password")
onActivated: root.copyFieldEnding(".password")
}
Shortcut {
sequence: "Ctrl+T"
context: Qt.WindowShortcut
enabled: !root.locked && !!root.detail.totp
onActivated: root.vaultService.copy("totp-current", root.detail.totp.currentSlot)
}
Shortcut {
sequence: "Ctrl+Shift+T"
context: Qt.WindowShortcut
enabled: !root.locked && !!root.detail.totp
onActivated: root.vaultService.copy("totp-next", root.detail.totp.nextSlot)
}
Item {
id: browserPane
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
width: Math.round(parent.width * 0.37)
PopupTextField {
id: searchField
objectName: "vaultSearchField"
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: 14
height: 42
theme: root.theme
icon: ""
placeholderText: "search vault"
text: root.vaultService.query
enabled: !root.locked
onEdited: text => {
root.vaultService.query = text;
root.reconcileVisibleSelection();
}
Keys.onUpPressed: event => {
event.accepted = true;
root.vaultService.moveSelection(-1);
}
Keys.onDownPressed: event => {
event.accepted = true;
root.vaultService.moveSelection(1);
}
Keys.onReturnPressed: event => {
event.accepted = true;
if (root.vaultService.selectedId.length > 0)
root.vaultService.refreshDetail();
}
}
Flickable {
id: results
anchors.top: searchField.bottom
anchors.topMargin: 8
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: 14
anchors.bottom: statusRow.top
anchors.bottomMargin: 8
contentHeight: resultColumn.implicitHeight
clip: true
boundsBehavior: Flickable.StopAtBounds
property bool restoringScroll: true
function restoreScroll() {
contentY = Math.min(root.vaultService.resultScrollPosition, Math.max(0, contentHeight - height));
restoringScroll = false;
}
function revealItem(item) {
const point = item.mapToItem(resultColumn, 0, 0);
if (point.y < contentY)
contentY = point.y;
else if (point.y + item.height > contentY + height)
contentY = Math.min(point.y + item.height - height, Math.max(0, contentHeight - height));
}
onContentYChanged: if (!restoringScroll)
root.vaultService.resultScrollPosition = contentY
Column {
id: resultColumn
width: results.width
spacing: 5
NuguText {
visible: root.locked || root.vaultService.visibleItems.length === 0
width: parent.width
height: 80
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
theme: root.theme
variant: "meta"
text: root.locked ? "unlock to search vault" : (root.vaultService.status === "loading" ? "loading vault" : "no matching items")
}
Repeater {
model: root.locked ? [] : root.vaultService.groupedItems
delegate: Column {
id: group
required property var modelData
width: resultColumn.width
spacing: 4
NuguText {
width: parent.width
height: 24
verticalAlignment: Text.AlignBottom
theme: root.theme
variant: "meta"
text: group.modelData.label
}
Repeater {
model: group.modelData.items
delegate: PopupCard {
id: itemRow
required property var modelData
theme: root.theme
padding: 0
active: modelData.id === root.vaultService.selectedId
interactive: true
width: group.width
height: 56
activeFocusOnTab: true
Accessible.role: Accessible.ListItem
Accessible.name: modelData.title + ", " + modelData.itemType
onActiveChanged: if (active)
Qt.callLater(results.revealItem, itemRow)
Component.onCompleted: if (active)
Qt.callLater(results.revealItem, itemRow)
Keys.onReturnPressed: event => {
event.accepted = true;
root.vaultService.select(modelData.id);
}
onClicked: root.vaultService.select(modelData.id)
Item {
id: iconSlot
anchors.left: parent.left
anchors.leftMargin: 8
anchors.verticalCenter: parent.verticalCenter
width: 36
height: 36
Image {
id: favicon
anchors.fill: parent
visible: source.toString().length > 0 && status !== Image.Error
source: itemRow.modelData.favicon ? "file://" + itemRow.modelData.favicon : ""
sourceSize.width: 36
sourceSize.height: 36
fillMode: Image.PreserveAspectFit
asynchronous: true
}
Rectangle {
anchors.fill: parent
visible: !favicon.visible
radius: 9
color: root.theme.withAlpha(root.theme.ui.accent, 0.1)
Text {
anchors.centerIn: parent
color: root.theme.ui.accent
font.family: "Symbols Nerd Font Mono"
font.pixelSize: 16
text: itemRow.modelData.itemType === "login" ? "" : (itemRow.modelData.itemType === "card" ? "" : (itemRow.modelData.itemType === "identity" ? "" : (itemRow.modelData.itemType === "ssh key" ? "" : "")))
}
}
}
Column {
anchors.left: iconSlot.right
anchors.leftMargin: 10
anchors.right: favoriteMark.left
anchors.rightMargin: 8
anchors.verticalCenter: parent.verticalCenter
spacing: 2
NuguText {
theme: root.theme
width: parent.width
text: itemRow.modelData.title
elide: Text.ElideRight
}
NuguText {
theme: root.theme
variant: "meta"
width: parent.width
text: itemRow.modelData.itemType + (itemRow.modelData.subtitle ? " · " + itemRow.modelData.subtitle : "")
elide: Text.ElideRight
}
}
Text {
id: favoriteMark
anchors.right: parent.right
anchors.rightMargin: 11
anchors.verticalCenter: parent.verticalCenter
width: 18
visible: itemRow.modelData.favorite
color: root.theme.ui.accent
font.family: "Symbols Nerd Font Mono"
font.pixelSize: 14
text: ""
}
}
}
}
}
}
}
Row {
id: statusRow
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: 14
anchors.bottom: parent.bottom
height: 24
spacing: 6
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: 7
height: 7
radius: 4
color: root.vaultService.status === "unlocked" ? root.theme.info : (root.vaultService.status === "loading" ? root.theme.warning : root.theme.ui.minor)
}
NuguText {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 13 - (settingsButton.visible ? settingsButton.width + statusRow.spacing : 0)
theme: root.theme
variant: "meta"
text: root.vaultService.status === "unlocked" ? "vault ready" : root.vaultService.status.replace(/-/g, " ")
elide: Text.ElideRight
}
PopupButton {
id: settingsButton
theme: root.theme
text: "settings"
accessibleName: "vault settings"
height: 24
visible: !root.setupMode
enabled: !root.vaultService.setupSaving && !root.vaultService.lockPending
onClicked: root.vaultService.beginSetup()
}
}
}
Rectangle {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: browserPane.right
width: 1
color: root.theme.withAlpha(root.theme.ui.minor, 0.35)
}
Item {
id: detailPane
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: browserPane.right
anchors.leftMargin: 18
anchors.right: parent.right
Item {
anchors.fill: parent
visible: root.locked
Column {
visible: root.setupMode
anchors.centerIn: parent
width: Math.min(390, parent.width - 32)
spacing: 12
NuguText {
theme: root.theme
variant: "title"
font.pixelSize: 24
text: "configure vault"
}
NuguText {
theme: root.theme
text: "server url"
}
SetupField {
id: serverUrlSetup
objectName: "vaultSetupServerUrl"
theme: root.theme
placeholder: "vault.example.com"
text: root.vaultService.setupUrlDraft
enabled: !root.vaultService.setupSaving && !root.vaultService.connectionBusy
onEdited: text => root.vaultService.setupUrlDraft = text
onAccepted: accountSetup.input.forceActiveFocus()
}
NuguText {
theme: root.theme
text: "account"
}
SetupField {
id: accountSetup
objectName: "vaultSetupAccount"
theme: root.theme
placeholder: "email address"
text: root.vaultService.setupAccountDraft
enabled: !root.vaultService.setupSaving && !root.vaultService.connectionBusy
onEdited: text => root.vaultService.setupAccountDraft = text
onAccepted: root.vaultService.saveSetup()
}
Row {
spacing: 8
PopupButton {
theme: root.theme
text: root.vaultService.connectionTesting ? "testing" : "test connection"
enabled: !root.vaultService.setupSaving && !root.vaultService.lockPending && !root.vaultService.connectionBusy
onClicked: root.vaultService.testConnection()
}
PopupButton {
theme: root.theme
text: root.vaultService.setupSaving ? "saving" : "save"
role: "primary"
enabled: !root.vaultService.setupSaving && !root.vaultService.lockPending && !root.vaultService.connectionBusy
onClicked: root.vaultService.saveSetup()
}
PopupButton {
theme: root.theme
text: "cancel"
visible: !!root.vaultService.profile.serverUrl && !!root.vaultService.profile.account
enabled: !root.vaultService.setupSaving && !root.vaultService.lockPending
onClicked: root.vaultService.cancelSetup()
}
}
NuguText {
width: parent.width
theme: root.theme
variant: "meta"
text: root.vaultService.connectionResult
visible: text.length > 0
wrapMode: Text.WordWrap
}
NuguText {
width: parent.width
theme: root.theme
variant: "meta"
color: root.theme.error
text: root.vaultService.setupError || root.vaultService.message
visible: text.length > 0
wrapMode: Text.WordWrap
}
}
Column {
visible: !root.setupMode
anchors.centerIn: parent
width: Math.min(390, parent.width - 32)
spacing: 12
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
width: 66
height: 66
radius: 18
color: root.theme.withAlpha(root.theme.ui.accent, 0.1)
border.width: 1
border.color: root.theme.withAlpha(root.theme.ui.accent, 0.45)
Text {
anchors.centerIn: parent
color: root.theme.ui.accent
font.family: "Symbols Nerd Font Mono"
font.pixelSize: 28
text: ""
}
}
NuguText {
anchors.horizontalCenter: parent.horizontalCenter
theme: root.theme
variant: "title"
font.pixelSize: 24
text: root.vaultService.status === "second-factor" ? "verify account" : (root.vaultService.status === "authentication-failure" ? "unlock failed" : "unlock vault")
}
NuguText {
width: parent.width
horizontalAlignment: Text.AlignHCenter
theme: root.theme
variant: "meta"
text: String(root.vaultService.profile.serverUrl || "") + "\n" + String(root.vaultService.profile.account || "")
}
Row {
visible: root.vaultService.status !== "second-factor"
width: parent.width
height: visible ? 48 : 0
spacing: 6
PopupTextField {
id: masterPassword
objectName: "vaultMasterPassword"
theme: root.theme
width: parent.width - 46
height: parent.height
placeholderText: "master password"
echoMode: showPassword.checked ? TextInput.Normal : TextInput.Password
inputMethodHints: Qt.ImhSensitiveData
onAccepted: {
const secret = text;
text = "";
root.vaultService.unlock(secret);
}
}
PopupButton {
id: showPassword
theme: root.theme
width: 40
height: 40
anchors.verticalCenter: parent.verticalCenter
icon: checked ? "" : ""
text: "show master password"
accessibleName: text
tooltip: text
showText: false
onClicked: checked = !checked
}
}
PopupButton {
visible: root.vaultService.status !== "second-factor"
anchors.horizontalCenter: parent.horizontalCenter
height: visible ? implicitHeight : 0
theme: root.theme
role: "primary"
text: root.vaultService.status === "loading" ? "unlocking" : "unlock"
enabled: root.vaultService.status !== "loading"
onClicked: {
const secret = masterPassword.text;
masterPassword.text = "";
root.vaultService.unlock(secret);
}
}
Column {
visible: root.vaultService.status === "second-factor"
width: parent.width
height: visible ? implicitHeight : 0
spacing: 8
NuguText {
width: parent.width
horizontalAlignment: Text.AlignHCenter
theme: root.theme
variant: "meta"
text: root.vaultService.secondFactorHint()
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: 6
Repeater {
model: root.vaultService.challenge.providers || []
delegate: PopupButton {
required property var modelData
theme: root.theme
text: String(modelData.label || "verification")
checked: Number(modelData.id) === root.vaultService.secondFactorProvider
enabled: !!modelData.codeSupported && !root.vaultService.verificationPending
onClicked: {
root.vaultService.selectSecondFactor(modelData.id);
secondFactorCode.input.forceActiveFocus();
}
}
}
}
PopupTextField {
id: secondFactorCode
width: parent.width
height: 44
theme: root.theme
placeholderText: "verification code"
inputMethodHints: Qt.ImhSensitiveData
text: root.vaultService.secondFactorDraft
enabled: !root.vaultService.verificationPending
onEdited: text => root.vaultService.secondFactorDraft = text
onAccepted: root.vaultService.submitSecondFactor(text)
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: 8
PopupButton {
theme: root.theme
text: root.vaultService.verificationPending ? "verifying" : "verify"
role: "primary"
enabled: root.vaultService.secondFactorProvider >= 0 && !root.vaultService.verificationPending
onClicked: root.vaultService.submitSecondFactor(secondFactorCode.text)
}
PopupButton {
theme: root.theme
text: "cancel"
onClicked: root.vaultService.cancelUnlock()
}
}
}
NuguText {
visible: root.vaultService.message.length > 0
width: parent.width
horizontalAlignment: Text.AlignHCenter
theme: root.theme
variant: "meta"
color: root.vaultService.verificationPending || root.vaultService.message === "verification required" ? root.theme.ui.minor : root.theme.error
text: root.vaultService.message
wrapMode: Text.WordWrap
}
NuguText {
width: parent.width
horizontalAlignment: Text.AlignHCenter
theme: root.theme
variant: "meta"
text: root.vaultService.cache.available ? (root.vaultService.cache.stale ? "encrypted cache stale" : "encrypted cache ready") : "no encrypted cache"
}
}
}
Item {
anchors.fill: parent
visible: !root.locked
Item {
id: detailHeader
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: 58
NuguText {
id: detailTitle
width: parent.width - headerActions.width - 12
theme: root.theme
variant: "title"
font.pixelSize: 22
text: String(root.detail.title || "select an item")
elide: Text.ElideRight
}
NuguText {
anchors.top: detailTitle.bottom
anchors.topMargin: 2
width: parent.width - headerActions.width - 12
theme: root.theme
variant: "meta"
text: String(root.detail.itemType || "") + (root.detail.subtitle ? " · " + root.detail.subtitle : "")
elide: Text.ElideRight
}
Row {
id: headerActions
anchors.right: parent.right
anchors.top: parent.top
spacing: 6
PopupButton {
theme: root.theme
width: 36
height: 36
icon: root.detail.favorite ? "" : ""
text: root.detail.favorite ? "unfavorite" : "favorite"
accessibleName: text
tooltip: text
showText: false
checked: !!root.detail.favorite
enabled: !!root.detail.id
onClicked: root.vaultService.setFavorite(!root.detail.favorite)
}
PopupButton {
theme: root.theme
width: 36
height: 36
icon: ""
text: "sync vault"
accessibleName: text
tooltip: text
showText: false
onClicked: root.vaultService.sync()
}
PopupButton {
theme: root.theme
width: 36
height: 36
icon: ""
text: "lock vault"
accessibleName: text
tooltip: text
showText: false
onClicked: root.vaultService.lock()
}
}
}
Flickable {
id: detailScroll
anchors.top: detailHeader.bottom
anchors.topMargin: 8
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: feedbackLabel.top
anchors.bottomMargin: 5
contentHeight: detailColumn.implicitHeight
clip: true
boundsBehavior: Flickable.StopAtBounds
property bool restoringScroll: true
function restoreScroll() {
contentY = Math.min(root.vaultService.detailScrollPosition, Math.max(0, contentHeight - height));
restoringScroll = false;
}
onContentYChanged: if (!restoringScroll)
root.vaultService.detailScrollPosition = contentY
Column {
id: detailColumn
width: detailScroll.width
spacing: 8
PopupCard {
visible: !!root.detail.requiresReprompt
theme: root.theme
active: true
padding: 10
width: parent.width
height: visible ? 108 : 0
Column {
anchors.fill: parent
spacing: 7
NuguText {
width: parent.width
theme: root.theme
variant: "meta"
text: root.vaultService.repromptError || "master password required for sensitive fields"
color: root.vaultService.repromptError ? root.theme.error : root.theme.ui.minor
elide: Text.ElideRight
}
Row {
width: parent.width
height: 44
spacing: 6
PopupTextField {
id: repromptPassword
theme: root.theme
width: parent.width - 86
height: parent.height
placeholderText: "master password"
echoMode: showRepromptPassword.checked ? TextInput.Normal : TextInput.Password
inputMethodHints: Qt.ImhSensitiveData
onAccepted: {
const secret = text;
text = "";
root.vaultService.reprompt(secret);
}
}
PopupButton {
id: showRepromptPassword
theme: root.theme
width: 34
height: 34
anchors.verticalCenter: parent.verticalCenter
icon: checked ? "" : ""
text: "show master password"
accessibleName: text
tooltip: text
showText: false
onClicked: checked = !checked
}
PopupButton {
theme: root.theme
width: 34
height: 34
anchors.verticalCenter: parent.verticalCenter
icon: ""
text: "verify master password"
accessibleName: text
tooltip: text
showText: false
onClicked: {
const secret = repromptPassword.text;
repromptPassword.text = "";
root.vaultService.reprompt(secret);
}
}
}
}
}
PopupButton {
visible: !!root.detail.totp
theme: root.theme
width: parent.width
height: visible ? 34 : 0
text: "totp"
icon: root.vaultService.sectionExpanded("totp") ? "" : ""
accessibleName: (root.vaultService.sectionExpanded("totp") ? "collapse " : "expand ") + text
tooltip: accessibleName
onClicked: root.vaultService.toggleSection("totp")
}
PopupCard {
visible: !!root.detail.totp && root.vaultService.sectionExpanded("totp")
theme: root.theme
active: true
padding: 12
width: parent.width
height: visible ? 106 : 0
Row {
anchors.fill: parent
spacing: 16
Column {
width: parent.width * 0.62
spacing: 7
NuguText {
theme: root.theme
variant: "meta"
text: "current code"
}
PopupButton {
theme: root.theme
height: 46
text: String(root.detail.totp ? root.detail.totp.current : "")
accessibleName: "copy current code"
tooltip: accessibleName
onClicked: root.vaultService.copy("totp-current", root.detail.totp.currentSlot)
}
}
Column {
width: parent.width * 0.32
spacing: 7
NuguText {
theme: root.theme
variant: "meta"
text: "next"
}
PopupButton {
theme: root.theme
width: parent.width
height: 34
text: String(root.detail.totp ? root.detail.totp.next : "")
accessibleName: "copy next code"
tooltip: accessibleName
onClicked: root.vaultService.copy("totp-next", root.detail.totp.nextSlot)
}
NuguText {
theme: root.theme
variant: "meta"
text: String(root.detail.totp ? root.detail.totp.remaining : 0) + "s remaining"
}
}
}
}
PopupButton {
visible: root.fields.length > 0
theme: root.theme
width: parent.width
height: visible ? 34 : 0
text: "fields"
icon: root.vaultService.sectionExpanded("fields") ? "" : ""
accessibleName: (root.vaultService.sectionExpanded("fields") ? "collapse " : "expand ") + text
tooltip: accessibleName
onClicked: root.vaultService.toggleSection("fields")
}
Repeater {
model: root.vaultService.sectionExpanded("fields") ? root.fields : []
delegate: PopupCard {
id: fieldRow
required property var modelData
objectName: "vault-field-" + modelData.id
theme: root.theme
padding: 0
width: detailColumn.width
height: 58
Column {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.right: actions.left
anchors.rightMargin: 8
anchors.verticalCenter: parent.verticalCenter
spacing: 3
NuguText {
width: parent.width
theme: root.theme
variant: "meta"
text: String(fieldRow.modelData.label || "field")
elide: Text.ElideRight
}
NuguText {
width: parent.width
theme: root.theme
font.family: "IBM Plex Mono"
text: fieldRow.modelData.kind === "restricted" ? (root.detail.requiresReprompt ? "re-prompt required" : "access denied") : fieldRow.modelData.sensitive && !root.vaultService.isRevealed(fieldRow.modelData.id) ? "••••••••••••" : String(fieldRow.modelData.value || "")
elide: Text.ElideRight
}
}
Row {
id: actions
anchors.right: parent.right
anchors.rightMargin: 8
anchors.verticalCenter: parent.verticalCenter
spacing: 5
PopupButton {
visible: fieldRow.modelData.sensitive && fieldRow.modelData.kind !== "restricted"
theme: root.theme
width: 34
height: 34
icon: root.vaultService.isRevealed(fieldRow.modelData.id) ? "" : ""
text: root.vaultService.isRevealed(fieldRow.modelData.id) ? "conceal " + fieldRow.modelData.label : "reveal " + fieldRow.modelData.label
accessibleName: text
tooltip: text
showText: false
onClicked: root.vaultService.toggleReveal(fieldRow.modelData.id)
}
PopupButton {
visible: fieldRow.modelData.kind === "url"
theme: root.theme
width: 34
height: 34
icon: ""
text: "open " + fieldRow.modelData.label
accessibleName: text
tooltip: text
showText: false
onClicked: root.vaultService.open(fieldRow.modelData.id)
}
PopupButton {
visible: fieldRow.modelData.kind === "text" || fieldRow.modelData.kind === "url"
theme: root.theme
width: 34
height: 34
icon: ""
text: "copy " + fieldRow.modelData.label
accessibleName: text
tooltip: text
showText: false
onClicked: root.vaultService.copy(fieldRow.modelData.id)
}
NuguText {
visible: fieldRow.modelData.kind === "unsupported" || fieldRow.modelData.kind === "restricted"
anchors.verticalCenter: parent.verticalCenter
theme: root.theme
variant: "meta"
text: fieldRow.modelData.kind === "restricted" ? "restricted" : "unsupported"
}
}
}
}
}
}
NuguText {
id: feedbackLabel
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: 20
horizontalAlignment: Text.AlignRight
theme: root.theme
variant: "meta"
color: root.theme.info
text: root.vaultService.feedback
}
}
}
}