Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,35 @@ PrefsScrollView
KeyNavigation.tab: uiScaleSpinBox

}
}

PrefsGroupRect
{
title: qsTr("Onboarding")

CheckBox
{
id: showOnboardingCheckbox
label: qsTr("Show onboarding tour on next start")
checked: !preferencesModel.onboardingCompleted
onCheckedChanged:
{
preferencesModel.onboardingStep = 0
preferencesModel.onboardingCompleted = !checked
}
toolTip: qsTr("Enable this to see the guided tour again the next time JASP starts.")

KeyNavigation.tab: startTutorialButton
}

RoundedButton
{
id: startTutorialButton
text: qsTr("Start tutorial")
onClicked: mainWindowRoot.startTutorial()
activeFocusOnTab: true
KeyNavigation.tab: uiScaleSpinBox
}
}

PrefsGroupRect
Expand Down
47 changes: 47 additions & 0 deletions Desktop/components/JASP/Widgets/MainWindow.qml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ Window
mainWindow.closeWindows();
}

function startTutorial()
{
onboardingOverlay.start(preferencesModel.onboardingStep)
}

function toggleFullScreen()
{
mainWindowRoot.visibility = mainWindowRoot.visibility === Window.FullScreen ? Window.Windowed : Window.FullScreen;
Expand Down Expand Up @@ -344,5 +349,47 @@ Window
onClicked: mainWindow.toggleChat()
}
}

Tutorial
{
id: onboardingOverlay
z: 1000

steps:
[
{
target: ribbon,
title: qsTr("Data and Analysis Section"),
text: qsTr("This is the main entry point for the Data and Analysis menu."),
placement: "bottom"
},
{
target: filemenu,
title: qsTr("File Menu and Preferences"),
text: qsTr("Clicking the hamburger button in the upper left corner opens/closes this file menu and preferences interface, used to open and save files, and to configure JASP global preferences."),
placement: "right",
onEnter: function() { fileMenuModel.visible = true; },
onExit: function() { fileMenuModel.visible = false; }
},
{
target: modulesMenu,
title: qsTr("Modules library"),
text: qsTr("This is where you can manage analytics modules. In the list on the far right of this area, you can enable/disable installed modules, and you can also manage all available modules from the online modules store on the left."),
placement: "left",
onEnter: function() { modulesMenu.opened = true; },
onExit: function() { modulesMenu.opened = false; }
},
{
target: chatToggleButton,
title: qsTr("AI Assistant"),
text: qsTr("Click the robot icon here or press Ctrl+J to bring up the AI chat panel.Detailed AI settings can be found in the preferences menu."),
placement: "top"
}
]
}

Component.onCompleted:
if (!preferencesModel.onboardingCompleted)
startTutorial()

}
270 changes: 270 additions & 0 deletions Desktop/components/JASP/Widgets/Tutorial.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
import QtQuick
import JASP.Controls
Item
{
id: onboardingOverlay

property var steps: []
property int currentIndex: -1
property bool active: false

readonly property var currentStep: (currentIndex >= 0 && currentIndex < steps.length) ? steps[currentIndex] : null
readonly property bool lastStep: currentIndex === steps.length - 1

anchors.fill: parent
z: 1000
visible: active && currentStep && currentStep.target && currentStep.target.width > 0 && currentStep.target.height > 0


function start(fromStep)
{
if (steps.length === 0)
return;

active = true;
_goToStep(fromStep !== undefined ? fromStep : 0);
}

function _runStepCallback(index, callbackName)
{
if (index >= 0 && index < steps.length && steps[index][callbackName])
steps[index][callbackName]();
}

function _goToStep(index)
{
_runStepCallback(currentIndex, "onExit");
currentIndex = index;
_runStepCallback(currentIndex, "onEnter");
}

function next()
{
if (lastStep)
finish();
else
{
_goToStep(currentIndex + 1);
preferencesModel.onboardingStep = currentIndex;
}
}

function previous()
{
if (currentIndex <= 0)
return;
_goToStep(currentIndex - 1);
preferencesModel.onboardingStep = currentIndex;
}

function finish()
{
_runStepCallback(currentIndex, "onExit");
active = false;
currentIndex = -1;

preferencesModel.onboardingCompleted = true;
preferencesModel.onboardingStep = 0;

}

property rect targetRect:
{
if (!currentStep || !currentStep.target || currentStep.target.width <= 0 || currentStep.target.height <= 0)
return Qt.rect(0, 0, 0, 0);

var t = currentStep.target;
var dummy = t.x + t.y + t.width + t.height + onboardingOverlay.width + onboardingOverlay.height;
var pt = t.mapToItem(onboardingOverlay, 0, 0);

if (isNaN(pt.x) || isNaN(pt.y))
return Qt.rect(0, 0, 0, 0);

return Qt.rect(pt.x, pt.y, t.width, t.height);
}

Rectangle
{
color: "#000000"
opacity: 0.8
x: 0
y: 0
width: parent.width
height: Math.max(0, targetRect.y)
}

Rectangle
{
color: "#000000"
opacity: 0.8
x: 0
y: Math.max(0, targetRect.y + targetRect.height)
width: parent.width
height: Math.max(0, parent.height - (targetRect.y + targetRect.height))
}

Rectangle
{
color: "#000000"
opacity: 0.8
x: 0
y: Math.max(0, targetRect.y)
width: Math.max(0, targetRect.x); height: Math.max(0, targetRect.height)
}

Rectangle
{
color: "#000000"
opacity: 0.8
x: Math.max(0, targetRect.x + targetRect.width)
y: Math.max(0, targetRect.y)
width: Math.max(0, parent.width - (targetRect.x + targetRect.width))
height: Math.max(0, targetRect.height)
}

Rectangle
{
x: targetRect.x
y: targetRect.y
width: targetRect.width
height: targetRect.height
color: jaspTheme.blue
opacity: 0.1

Behavior on x { enabled: preferencesModel.animationsOn; NumberAnimation { duration: 200 } }
Behavior on y { enabled: preferencesModel.animationsOn; NumberAnimation { duration: 200 } }
Behavior on width { enabled: preferencesModel.animationsOn; NumberAnimation { duration: 200 } }
Behavior on height { enabled: preferencesModel.animationsOn; NumberAnimation { duration: 200 } }
}

Rectangle
{
x: targetRect.x - 4
y: targetRect.y - 4
width: Math.max(0, targetRect.width + 8)
height: Math.max(0, targetRect.height + 8)
radius: 4
color: "transparent"
border.width: 2
border.color: jaspTheme.blue

Behavior on x { enabled: preferencesModel.animationsOn; NumberAnimation { duration: 200 } }
Behavior on y { enabled: preferencesModel.animationsOn; NumberAnimation { duration: 200 } }
Behavior on width { enabled: preferencesModel.animationsOn; NumberAnimation { duration: 200 } }
Behavior on height { enabled: preferencesModel.animationsOn; NumberAnimation { duration: 200 } }
}

MouseArea { anchors.fill: parent }

Rectangle
{
id: callout
width: 320
height: calloutColumn.implicitHeight + 24
radius: 6
color: jaspTheme.white
border.width: 1
border.color: jaspTheme.borderColor

readonly property string placement: currentStep && currentStep.placement ? currentStep.placement : "bottom"

x:
{
switch (placement)
{
case "right": return Math.min(targetRect.x + targetRect.width + 12, onboardingOverlay.width - width - 8);
case "left": return Math.max(8, targetRect.x - width - 12);
default: return Math.min(Math.max(targetRect.x, 8), onboardingOverlay.width - width - 8);
}
}

y:
{
switch (placement)
{
case "left":
case "right": return Math.min(Math.max(targetRect.y, 8), onboardingOverlay.height - height - 8);
case "top": return Math.max(8, targetRect.y - height - 12);
default:
return (targetRect.y + targetRect.height + height + 12 <= onboardingOverlay.height)
? targetRect.y + targetRect.height + 12
: Math.max(8, targetRect.y - height - 12);
}
}


Behavior on x { enabled: preferencesModel.animationsOn; NumberAnimation { duration: 200 } }
Behavior on y { enabled: preferencesModel.animationsOn; NumberAnimation { duration: 200 } }

Column
{
id: calloutColumn
anchors.fill: parent
anchors.margins: 12
spacing: 8

Text
{
width: parent.width
text: qsTr("Getting Started")
font.bold: true
font.pixelSize: 16
color: jaspTheme.textEnabled
wrapMode: Text.WordWrap
}

Text
{
width: parent.width
text: currentStep ? currentStep.title : ""
font.bold: true
font.pixelSize: 14
color: jaspTheme.textEnabled
wrapMode: Text.WordWrap
}

Text
{
width: parent.width
text: currentStep ? currentStep.text : ""
font.pixelSize: 12
color: jaspTheme.textEnabled
wrapMode: Text.WordWrap
}

Text
{
text: "" + (onboardingOverlay.currentIndex + 1) + "/" + onboardingOverlay.steps.length
font.pixelSize: 11
color: jaspTheme.textDisabled
}

Row
{
width: parent.width
spacing: 8
layoutDirection: Qt.RightToLeft

Button
{
text: onboardingOverlay.lastStep ? qsTr("Finish") : qsTr("Next")
onClicked: onboardingOverlay.next()
}

Button
{
visible: onboardingOverlay.currentIndex > 0
text: qsTr("Previous")
onClicked: onboardingOverlay.previous()
}

Button
{
text: qsTr("Skip the Tutorial")
onClicked: onboardingOverlay.finish()
visible: !onboardingOverlay.lastStep
}
}
}
}
}
Loading
Loading