Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR3: Display changelog/announcement page in SLEAP GUI #1556

Open
wants to merge 45 commits into
base: shrivaths/changelog-announcement-2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
b7f7748
Adding the bulletin dialog box class
shrivaths16 Oct 17, 2023
25eda37
Merge branch 'shrivaths/changelog-announcement-2' into shrivaths/chan…
shrivaths16 Oct 17, 2023
8706ccb
Add new variable to enable bulletin dialogue
shrivaths16 Oct 17, 2023
fdf5547
Merge branch 'shrivaths/changelog-announcement-2' into shrivaths/chan…
shrivaths16 Oct 19, 2023
d584de2
Merge branch 'shrivaths/changelog-announcement-2' into shrivaths/chan…
shrivaths16 Oct 30, 2023
f809bda
Merge branch 'shrivaths/changelog-announcement-2' into shrivaths/chan…
shrivaths16 Oct 30, 2023
cce7183
Merge branch 'shrivaths/changelog-announcement-2' into shrivaths/chan…
shrivaths16 Nov 2, 2023
2c68518
Add bulletin popup to application
shrivaths16 Nov 2, 2023
ca25347
Add Qthread to multiple windows
shrivaths16 Nov 14, 2023
27f0d28
Set dialog to top, maintain reference to thread, remove unused imports
Nov 14, 2023
168056e
bulletin pop up on top, change to pathlib
shrivaths16 Nov 28, 2023
5e2fb37
Merge branch 'shrivaths/changelog-announcement-2' into shrivaths/chan…
shrivaths16 Nov 30, 2023
944428f
merge with base branch shrivaths/changelog-announcent-2
shrivaths16 Dec 2, 2023
793faa6
Merge branch 'shrivaths/changelog-announcement-2' into shrivaths/chan…
shrivaths16 Dec 5, 2023
561a109
Fix bulletin dialog display conditions
shrivaths16 Dec 5, 2023
465eb3f
Merge branch 'shrivaths/changelog-announcement-2' into shrivaths/chan…
shrivaths16 Dec 5, 2023
f7c510d
small test modification
shrivaths16 Dec 6, 2023
d071ac8
Merge branch 'shrivaths/changelog-announcement-2' into shrivaths/chan…
shrivaths16 Dec 14, 2023
58ffca8
pyside6 >=6.0
shrivaths16 Dec 18, 2023
02aa249
setting environment variable QT_API to pyside2
shrivaths16 Dec 18, 2023
a724ec2
try with pyside6 and set environment variable
shrivaths16 Dec 19, 2023
9827510
pyside2 == 5.14.1
shrivaths16 Dec 19, 2023
cd98d7c
pyside6 ==6.2.2.1
shrivaths16 Dec 19, 2023
a9673d8
pyside6 ==6.2.2.1
shrivaths16 Dec 20, 2023
b7e475d
Resetting back to default
shrivaths16 Dec 20, 2023
2cc02d7
Merge branch 'shrivaths/changelog-announcement-2' into shrivaths/chan…
shrivaths16 Dec 20, 2023
709de70
Merge branch 'shrivaths/changelog-announcement-2' into shrivaths/chan…
shrivaths16 Dec 21, 2023
18974db
add pip install PySide2==5.14.1 in conda_mac build.sh
shrivaths16 Dec 21, 2023
cada26c
Add title to bulletin popup
shrivaths16 Dec 21, 2023
3c35dda
add navbar
shrivaths16 Jan 2, 2024
713c068
Merge branch 'shrivaths/changelog-announcement-2' into shrivaths/chan…
shrivaths16 Jan 3, 2024
86d1a44
Merge branch 'shrivaths/changelog-announcement-2' into shrivaths/chan…
shrivaths16 Jan 11, 2024
1737561
test skip method for macos
shrivaths16 Jan 11, 2024
7859b93
test with different os name
shrivaths16 Jan 11, 2024
ec2b2d7
correct pytest skip
shrivaths16 Jan 11, 2024
59f2438
test skip functionality
shrivaths16 Jan 11, 2024
bd8d004
test skipping entire module
shrivaths16 Jan 11, 2024
ea2111d
skip macos tests
shrivaths16 Jan 11, 2024
7ff49f0
misplaced skip
shrivaths16 Jan 11, 2024
5391131
Merge branch 'shrivaths/changelog-announcement-2' into shrivaths/chan…
shrivaths16 Jan 11, 2024
6ddb158
having online bulletin for macos
shrivaths16 Jan 12, 2024
895a074
Adjust test functions to include online bulletin
shrivaths16 Jan 12, 2024
af67d55
inlcude review changes
shrivaths16 Jan 17, 2024
b5d5139
Merge branch 'shrivaths/changelog-announcement-2' into shrivaths/chan…
shrivaths16 Jan 17, 2024
150453d
PR4: Add helpmenu option (#1628)
shrivaths16 Jan 19, 2024
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
43 changes: 41 additions & 2 deletions sleap/gui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
from logging import getLogger
from pathlib import Path
from typing import Callable, List, Optional, Tuple
from datetime import date
import webbrowser

from qtpy import QtCore, QtGui
from qtpy.QtCore import QEvent, Qt
Expand All @@ -65,6 +67,14 @@
from sleap.gui.dialogs.formbuilder import FormBuilderModalDialog
from sleap.gui.dialogs.metrics import MetricsTableDialog
from sleap.gui.dialogs.shortcuts import ShortcutDialog

# Open bulletin online if there is an ImportError (for MacOS)
ONLINE_BULLETIN = False
try:
from sleap.gui.dialogs.bulletin import BulletinWorker
except ImportError:
ONLINE_BULLETIN = True

from sleap.gui.overlays.instance import InstanceOverlay
from sleap.gui.overlays.tracks import TrackListOverlay, TrackTrailOverlay
from sleap.gui.shortcuts import Shortcuts
Expand Down Expand Up @@ -158,8 +168,7 @@ def __init__(
self.state["share usage data"] = prefs["share usage data"]
self.state["skeleton_preview_image"] = None
self.state["skeleton_description"] = "No skeleton loaded yet"
self.state["announcement last seen date"] = prefs["announcement last seen date"]
self.state["announcement"] = prefs["announcement"]

if no_usage_data:
self.state["share usage data"] = False
self.state["clipboard_track"] = None
Expand All @@ -170,6 +179,10 @@ def __init__(
self.state.connect("show non-visible nodes", self.plotFrame)

self.release_checker = ReleaseChecker()

self.state["announcement last seen date"] = prefs["announcement last seen date"]
self.state["announcement"] = prefs["announcement"]

self.announcement_checker = AnnouncementChecker(state=self.state)

if self.state["share usage data"]:
Expand All @@ -191,6 +204,29 @@ def __init__(
else:
self.state["project_loaded"] = False

self.new_announcement_available = (
self.announcement_checker.new_announcement_available()
)
# Display announcement bulletin popup
if self.new_announcement_available:
self.announcement_checker.update_latest_announcement()
self.bulletin_dialog()

def bulletin_dialog(self):
"""Displays bulletin dialog is new announcement is available."""
# TODO: Change the URL to the actual SLEAP website before merging to main
if ONLINE_BULLETIN:
webbrowser.open("https://sleap.ai/develop/bulletin.html")
else:
# Initialize the bulletin popup worker
popup_worker = BulletinWorker(
"".join(["# What's New? \n", self.state["announcement"]]), self
)
popup_worker.show_bulletin()

# Save the bulletin worker so we can close them later
self._child_windows["bulletin_worker"] = popup_worker # Needed!

def setWindowTitle(self, value):
"""Sets window title (if value is not None)."""
if value is not None:
Expand Down Expand Up @@ -1001,6 +1037,9 @@ def new_instance_menu_action():
helpMenu.addSeparator()
helpMenu.addAction("Keyboard Shortcuts", self._show_keyboard_shortcuts_window)

helpMenu.addSeparator()
helpMenu.addAction("What's New?", self.commands.showBulletin)

def process_events_then(self, action: Callable):
"""Decorates a function with a call to first process events."""

Expand Down
10 changes: 10 additions & 0 deletions sleap/gui/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,10 @@ def openPrereleaseVersion(self):
"""Open the current prerelease version."""
self.execute(OpenPrereleaseVersion)

def showBulletin(self):
"""Opens the latest bulletin"""
self.execute(ShowBulletin)


# File Commands

Expand Down Expand Up @@ -3398,6 +3402,12 @@ def do_action(context: CommandContext, params: dict):
context.openWebsite(rls.url)


class ShowBulletin(AppCommand):
@staticmethod
def do_action(context: CommandContext, params: dict):
context.app.bulletin_dialog()


def copy_to_clipboard(text: str):
"""Copy a string to the system clipboard.

Expand Down
72 changes: 72 additions & 0 deletions sleap/gui/dialogs/bulletin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""
GUI for displaying the new announcement.
"""

from qtpy.QtCore import Signal, Qt
from qtpy.QtWebEngineWidgets import QWebEngineView
from qtpy.QtCore import Property, Signal, QObject, QUrl
from qtpy.QtWebChannel import QWebChannel
from qtpy import QtWidgets
from pathlib import Path


class BulletinWorker(QtWidgets.QMainWindow):
def __init__(self, content, parent=None):
super(BulletinWorker, self).__init__(parent)
self._content = content
# Set the window to stay on top
self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
Comment on lines +13 to +18
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class BulletinWorker is named as if it performs background work, but it inherits from QMainWindow and seems to be responsible for UI presentation. Consider renaming the class to reflect its purpose more accurately.

self.setWindowTitle("What's New?")
self.setGeometry(0, 0, 900, 750)
self.center_on_screen()

def center_on_screen(self):
# Get the screen geometry
screen_geometry = QtWidgets.QDesktopWidget().screenGeometry()

# Calculate the center of the screen
center_x = (screen_geometry.width() - self.width()) // 2
center_y = (screen_geometry.height() - self.height()) // 2

# Move the window to the center of the screen
self.move(center_x, center_y)

def show_bulletin(self):

self.document = Document()

# Set the webchannel
self.channel = QWebChannel()
self.channel.registerObject("content", self.document)

self.document.set_text(self._content)
self.view = QWebEngineView()
self.view.page().setWebChannel(self.channel)

filename = str(Path(__file__).resolve().parent / "bulletin/markdown.html")
url = QUrl.fromLocalFile(filename)
self.view.load(url)
Comment on lines +46 to +48
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path to the markdown.html file is constructed using string conversion and concatenation. It's more idiomatic in Python to use the / operator provided by pathlib for path joining.

- filename = str(Path(__file__).resolve().parent / "bulletin/markdown.html")
+ filename = Path(__file__).resolve().parent / "bulletin/markdown.html"

Committable suggestion

IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
filename = str(Path(__file__).resolve().parent / "bulletin/markdown.html")
url = QUrl.fromLocalFile(filename)
self.view.load(url)
filename = Path(__file__).resolve().parent / "bulletin/markdown.html"
url = QUrl.fromLocalFile(filename)
self.view.load(url)


self.view.setGeometry(0, 0, 600, 400)
# Set the central window with view
self.setCentralWidget(self.view)
self.show()


class Document(QObject):
textChanged = Signal(str)

def __init__(self, parent=None):
super().__init__(parent)
self.m_text = ""
Comment on lines +59 to +61
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per the previous review comment, consider adding an optional text parameter to the __init__ method of the Document class to allow setting the initial text upon instantiation.

-    def __init__(self, parent=None):
+    def __init__(self, parent=None, text=""):
         super().__init__(parent)
-        self.m_text = ""
+        self.m_text = text

Committable suggestion

IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
def __init__(self, parent=None):
super().__init__(parent)
self.m_text = ""
def __init__(self, parent=None, text=""):
super().__init__(parent)
self.m_text = text


Comment on lines +59 to +62
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The __init__ method initializes m_text as an empty string. If there's a possibility that text could be passed during initialization, consider adding it as an optional argument to __init__. This would allow for more flexible usage of the class.

-    def __init__(self, parent=None):
+    def __init__(self, parent=None, text=""):
         super().__init__(parent)
-        self.m_text = ""
+        self.m_text = text

Commitable suggestion (Beta)
Suggested change
def __init__(self, parent=None):
super().__init__(parent)
self.m_text = ""
def __init__(self, parent=None, text=""):
super().__init__(parent)
self.m_text = text

def get_text(self):
return self.m_text

def set_text(self, text):
if self.m_text == text:
return
self.m_text = text
self.textChanged.emit(self.m_text)

text = Property(str, fget=get_text, fset=set_text, notify=textChanged)
47 changes: 47 additions & 0 deletions sleap/gui/dialogs/bulletin/markdown.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
body {
font-family: Helvetica, Arial, sans-serif;
}

h1,
h2,
h3,
h4,
h5 {
color: #636364;
font-style: italic;
}

#container {
display: flex;
}

#navbar {
width: 200px; /* Set the fixed width for the navbar */
position: fixed;
top: 0;
left: 0;
height: 100vh;
background-color: #2e2e2e;
color: #fff;
padding: 12px;
box-sizing: border-box;
flex-grow: 1;
list-style: disc;
}

#navbar ul {
margin-left: 2px;
padding-left: 12px;

}

#navbar a {
color: white;
}

#content {
flex-grow: 1;
padding: 20px;
box-sizing: border-box;
margin-left: 200px; /* Adjust margin to match navbar width */
}
70 changes: 70 additions & 0 deletions sleap/gui/dialogs/bulletin/markdown.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>What's New?</title>
<link rel="stylesheet" href="pico.min.css">
<link rel="stylesheet" type="text/css" href="markdown.css">
<script src="marked.js"></script>
<script src="qrc:/qtwebchannel/qwebchannel.js"></script>
</head>
<body>
<div id="container">
<div id="content"></div>
<div id="navbar"></div>
</div>
<script>
'use strict';

var content = document.getElementById('content');
var navbar = document.getElementById('navbar');

var updateText = function(text) {
content.innerHTML = marked(text);
// Wait till the document is loaded to add navbar
setTimeout(function() {
generateNavbar();
}, 100);
}

new QWebChannel(qt.webChannelTransport,
function(channel) {
var content = channel.objects.content;
updateText(content.text);
content.textChanged.connect(updateText);
}
);

function generateNavbar() {
const h2Tags = document.querySelectorAll('h2');
const ul = document.createElement('ul');
var uniqueIdCounter = 1;

h2Tags.forEach((h2, index) => {
const li = document.createElement('li');
const link = document.createElement('a');
if (!h2.id) {
h2.id = `generated-id-${uniqueIdCounter}`;
uniqueIdCounter++;
}
link.href = '#' + h2.id;
link.textContent = h2.textContent;
link.addEventListener('click', function(event) {
event.preventDefault();
scrollToSection(h2);
});
li.appendChild(link);
ul.appendChild(li);
});
navbar.appendChild(ul);
document.body.insertBefore(navbar, document.body.firstChild);
};

function scrollToSection(element) {
element.scrollIntoView({ behavior: 'smooth' });
}

</script>
</body>
</html>
Loading
Loading