Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

quickshell/nuguland/PrivilegeSurface.qml

Raw
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Window
import "privilege.js" as PrivilegeUi

Rectangle {
    id: root

    required property Theme theme
    required property Privilege privilegeService
    property var panelState: privilegeService ? parseJson(privilegeService.stateJson(), {
        "request": {},
        "privilegeSurfacePanelContract": {},
        "buttons": [],
        "confirming": false
    }) : ({
            "request": {},
            "privilegeSurfacePanelContract": {},
            "buttons": [],
            "confirming": false
        })
    readonly property var request: panelState.request || ({})
    property bool secretVisible: false
    property bool promptWasActive: false
    readonly property bool secretPromptField: true
    readonly property bool denyCancelConfirmRow: true
    readonly property bool failureShakeSlot: true

    function parseJson(text, valueWhenInvalid) {
        try {
            return JSON.parse(String(text));
        } catch (error) {
            return valueWhenInvalid;
        }
    }

    function refreshFrom(text) {
        const wasActive = promptWasActive;
        panelState = parseJson(text, panelState);
        promptWasActive = !!panelState.active;
        if (!wasActive && promptWasActive)
            Qt.callLater(root.focusSecret);
    }

    function submitSecret(secret) {
        if (privilegeService)
            refreshFrom(privilegeService.submit(secret));
    }

    function deny() {
        if (privilegeService)
            refreshFrom(privilegeService.deny());
    }

    function cancel() {
        if (privilegeService)
            refreshFrom(privilegeService.cancel());
    }

    function focusSecret() {
        if (root.visible && root.panelState.active) {
            root.forceActiveFocus();
            secretInput.forceActiveFocus();
        }
    }

    color: theme ? theme.withAlpha(theme.ui.backdrop, 0.94) : "black"
    visible: panelState.active || panelState.error.length > 0 || panelState.confirming
    focus: visible
    onVisibleChanged: {
        if (visible && panelState.active)
            Qt.callLater(root.focusSecret);
    }
    Component.onCompleted: {
        promptWasActive = !!panelState.active;
        Qt.callLater(root.focusSecret);
    }

    Connections {
        target: root.privilegeService

        function onActiveChanged() {
            root.refreshFrom(root.privilegeService.stateJson());
        }

        function onConfirmingChanged() {
            root.refreshFrom(root.privilegeService.stateJson());
        }

        function onErrorChanged() {
            root.refreshFrom(root.privilegeService.stateJson());
        }
    }

    Connections {
        target: root.Window.window

        function onActiveChanged() {
            if (root.Window.window.active && root.visible && root.panelState.active)
                Qt.callLater(root.focusSecret);
        }
    }

    Keys.onEscapePressed: event => {
        event.accepted = true;
        root.cancel();
    }

    Rectangle {
        anchors.centerIn: parent
        width: Math.min(parent.width - 48, 680)
        height: cardColumn.implicitHeight + 44
        radius: 24
        color: root.theme ? root.theme.withAlpha(root.theme.ui.backdrop, 0.98) : "#181818"
        border.width: 1
        border.color: root.theme ? root.theme.withAlpha(root.panelState.error ? root.theme.error : root.theme.ui.accent, 0.32) : "transparent"

        Column {
            id: cardColumn

            anchors.left: parent.left
            anchors.right: parent.right
            anchors.top: parent.top
            anchors.margins: 22
            spacing: 16

            Column {
                width: parent.width
                spacing: 10

                NuguText {
                    theme: root.theme
                    variant: "title"
                    text: root.panelState.confirming ? "authorization granted" : (root.panelState.error && !root.panelState.active ? "authentication unavailable" : "authorization required")
                    width: parent.width
                    elide: Text.ElideRight
                }

                Rectangle {
                    width: parent.width
                    height: commandText.implicitHeight + 20
                    radius: 16
                    color: root.theme ? root.theme.withAlpha(root.theme.ui.minor, 0.08) : "transparent"

                    Text {
                        id: commandText

                        anchors.left: parent.left
                        anchors.right: parent.right
                        anchors.top: parent.top
                        anchors.margins: 10
                        text: PrivilegeUi.commandMarkdown(root.request.message || "A system change needs your password.")
                        color: root.theme ? root.theme.content.normal : "white"
                        textFormat: Text.RichText
                        wrapMode: Text.WordWrap
                        elide: Text.ElideNone
                    }
                }
            }

            Rectangle {
                id: successBanner

                visible: root.panelState.confirming
                width: parent.width
                height: visible ? 58 : 0
                radius: 16
                color: root.theme ? root.theme.withAlpha(root.theme.info, 0.16) : "transparent"
                border.width: 1
                border.color: root.theme ? root.theme.withAlpha(root.theme.info, 0.62) : "transparent"
                opacity: visible ? 1 : 0
                scale: visible ? 1 : 0.96

                Behavior on opacity {
                    NumberAnimation {
                        duration: root.theme && root.theme.lowPowerMode ? 0 : 150
                        easing.type: Easing.OutCubic
                    }
                }

                Behavior on scale {
                    NumberAnimation {
                        duration: root.theme && root.theme.lowPowerMode ? 0 : 180
                        easing.type: Easing.OutBack
                    }
                }

                Row {
                    anchors.centerIn: parent
                    spacing: 12

                    Text {
                        anchors.verticalCenter: parent.verticalCenter
                        text: "✓"
                        color: root.theme ? root.theme.info : "white"
                        font.family: "IBM Plex Mono"
                        font.pixelSize: 20
                        font.bold: true
                    }

                    NuguText {
                        anchors.verticalCenter: parent.verticalCenter
                        theme: root.theme
                        variant: "title"
                        text: "authorized"
                        color: root.theme ? root.theme.info : "white"
                    }
                }
            }

            PrivilegeFailureShake {
                theme: root.theme
                objectName: "failureShakeSlot"
                visible: !root.panelState.confirming
                active: root.panelState.error && root.panelState.error.length > 0
                width: parent.width
                height: inputColumn.implicitHeight

                Column {
                    id: inputColumn

                    width: parent.width
                    spacing: 7

                    Rectangle {
                        id: secretBox

                        objectName: "secretPromptField"
                        width: parent.width
                        height: 48
                        radius: 16
                        color: root.theme ? root.theme.withAlpha(root.panelState.error ? root.theme.error : root.theme.ui.focus, root.panelState.error ? 0.12 : (secretInput.activeFocus ? 0.16 : 0.1)) : "transparent"
                        border.width: 1
                        border.color: root.theme ? root.theme.withAlpha(root.panelState.error ? root.theme.error : root.theme.ui.focus, root.panelState.error ? 0.72 : (secretInput.activeFocus ? 0.82 : 0.32)) : "transparent"

                        Behavior on color {
                            ColorAnimation {
                                duration: root.theme && root.theme.lowPowerMode ? 0 : 120
                            }
                        }

                        Behavior on border.color {
                            ColorAnimation {
                                duration: root.theme && root.theme.lowPowerMode ? 0 : 120
                            }
                        }

                        NuguText {
                            anchors.left: parent.left
                            anchors.leftMargin: 14
                            anchors.verticalCenter: parent.verticalCenter
                            theme: root.theme
                            variant: "meta"
                            text: root.request.prompt || "password"
                            visible: secretInput.text.length === 0
                            opacity: secretInput.activeFocus ? 0.42 : 0.68
                        }

                        MouseArea {
                            anchors.fill: parent
                            cursorShape: Qt.IBeamCursor
                            onClicked: secretInput.forceActiveFocus()
                        }

                        TextInput {
                            id: secretInput

                            anchors.fill: parent
                            anchors.leftMargin: 14
                            anchors.rightMargin: 58
                            verticalAlignment: TextInput.AlignVCenter
                            color: root.theme ? root.theme.content.normal : "white"
                            selectionColor: root.theme ? root.theme.ui.focus : "white"
                            selectedTextColor: root.theme ? root.theme.content.backdrop : "black"
                            font.family: "IBM Plex Mono"
                            echoMode: root.secretVisible || root.request.responseVisible ? TextInput.Normal : TextInput.Password
                            inputMethodHints: Qt.ImhSensitiveData
                            activeFocusOnTab: true
                            KeyNavigation.tab: revealButton
                            KeyNavigation.backtab: authorizeButton
                            Keys.onReturnPressed: event => {
                                event.accepted = true;
                                root.submitSecret(text);
                                text = "";
                            }
                            Keys.onEnterPressed: event => {
                                event.accepted = true;
                                root.submitSecret(text);
                                text = "";
                            }
                            Keys.onEscapePressed: event => {
                                event.accepted = true;
                                root.cancel();
                                text = "";
                            }
                        }

                        PopupButton {
                            id: revealButton

                            anchors.right: parent.right
                            anchors.rightMargin: 8
                            anchors.verticalCenter: parent.verticalCenter
                            theme: root.theme
                            width: 38
                            height: 32
                            icon: root.secretVisible ? "" : ""
                            text: root.secretVisible ? "hide password" : "show password"
                            accessibleName: text
                            tooltip: text
                            showText: false
                            checked: root.secretVisible
                            KeyNavigation.tab: cancelButton
                            KeyNavigation.backtab: secretInput
                            onClicked: root.secretVisible = !root.secretVisible
                        }
                    }

                    NuguText {
                        visible: root.panelState.error && root.panelState.error.length > 0
                        theme: root.theme
                        variant: "meta"
                        text: root.panelState.error === "Authentication failed" ? "password rejected" : (root.panelState.error === "no-active-polkit-flow" ? "no active authentication request" : root.panelState.error)
                        width: parent.width
                        color: root.theme ? root.theme.error : "white"
                        elide: Text.ElideRight
                    }
                }
            }

            Row {
                objectName: "denyCancelConfirmRow"
                visible: !root.panelState.confirming
                width: parent.width
                spacing: 10

                PopupButton {
                    id: cancelButton

                    theme: root.theme
                    width: Math.floor((parent.width - 10) * 0.38)
                    height: 40
                    icon: ""
                    text: "cancel"
                    accessibleName: text
                    tooltip: text
                    role: "ghost"
                    KeyNavigation.tab: authorizeButton
                    KeyNavigation.backtab: revealButton
                    onClicked: {
                        root.cancel();
                        secretInput.text = "";
                    }
                }

                PopupButton {
                    id: authorizeButton

                    theme: root.theme
                    width: parent.width - Math.floor((parent.width - 10) * 0.38) - 10
                    height: 40
                    icon: ""
                    text: "authorize"
                    accessibleName: text
                    tooltip: text
                    role: "primary"
                    enabled: root.panelState.active
                    KeyNavigation.tab: secretInput
                    KeyNavigation.backtab: cancelButton
                    onClicked: {
                        root.submitSecret(secretInput.text);
                        secretInput.text = "";
                    }
                }
            }
        }
    }

    component PrivilegeFailureShake: Item {
        id: shakeRoot

        required property Theme theme
        default property alias content: contentItem.data
        property bool active: false
        property real boundedDistance: 12
        readonly property var shakeOffsets: [0, -boundedDistance, boundedDistance, -boundedDistance / 2, boundedDistance / 2, 0]

        // The caller supplies width and height; content only fills that frame.
        onActiveChanged: {
            if (active)
                shake.restart();
        }

        Item {
            id: contentItem

            anchors.fill: parent
        }

        SequentialAnimation {
            id: shake

            running: false
            alwaysRunToEnd: true

            ScriptAction {
                script: translation.x = shakeRoot.shakeOffsets[0]
            }

            NumberAnimation {
                target: translation
                property: "x"
                to: shakeRoot.shakeOffsets[1]
                duration: shakeRoot.theme.lowPowerMode ? 0 : 50
                easing.type: Easing.OutCubic
            }

            NumberAnimation {
                target: translation
                property: "x"
                to: shakeRoot.shakeOffsets[2]
                duration: shakeRoot.theme.lowPowerMode ? 0 : 50
                easing.type: Easing.OutCubic
            }

            NumberAnimation {
                target: translation
                property: "x"
                to: shakeRoot.shakeOffsets[3]
                duration: shakeRoot.theme.lowPowerMode ? 0 : 40
                easing.type: Easing.OutCubic
            }

            NumberAnimation {
                target: translation
                property: "x"
                to: shakeRoot.shakeOffsets[4]
                duration: shakeRoot.theme.lowPowerMode ? 0 : 40
                easing.type: Easing.OutCubic
            }

            NumberAnimation {
                target: translation
                property: "x"
                to: shakeRoot.shakeOffsets[5]
                duration: shakeRoot.theme.lowPowerMode ? 0 : 30
                easing.type: Easing.OutCubic
            }
        }

        transform: Translate {
            id: translation

            x: 0
        }
    }
}