qcanvas 1.0.12.dev2__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of qcanvas might be problematic. Click here for more details.
- qcanvas/icons/file-download-failed.svg +22 -5
- qcanvas/icons/file-downloaded.svg +22 -5
- qcanvas/icons/file-not-downloaded.svg +22 -5
- qcanvas/icons/rc_icons.py +323 -328
- qcanvas/run.py +24 -0
- qcanvas/ui/course_viewer/content_tree.py +8 -4
- qcanvas/ui/course_viewer/course_viewer.py +0 -10
- qcanvas/ui/course_viewer/tabs/resource_rich_browser.py +17 -8
- qcanvas/ui/main_ui/qcanvas_window.py +1 -1
- qcanvas/ui/setup/setup_checker.py +2 -2
- qcanvas/ui/setup/setup_dialog.py +144 -65
- qcanvas/util/__init__.py +0 -2
- qcanvas/util/auto_downloader.py +1 -2
- qcanvas/util/paths.py +15 -26
- qcanvas/util/runtime.py +20 -0
- qcanvas/util/settings/_client_settings.py +11 -2
- qcanvas/util/themes.py +3 -0
- {qcanvas-1.0.12.dev2.dist-info → qcanvas-1.1.0.dist-info}/METADATA +3 -3
- {qcanvas-1.0.12.dev2.dist-info → qcanvas-1.1.0.dist-info}/RECORD +21 -20
- {qcanvas-1.0.12.dev2.dist-info → qcanvas-1.1.0.dist-info}/WHEEL +0 -0
- {qcanvas-1.0.12.dev2.dist-info → qcanvas-1.1.0.dist-info}/entry_points.txt +0 -0
qcanvas/run.py
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
# nuitka-project: --enable-plugin=pyside6
|
|
2
|
+
# nuitka-project: --windows-icon-from-ico=./deploy/windows/qcanvas.ico
|
|
3
|
+
# nuitka-project: --windows-console-mode=attach
|
|
4
|
+
# nuitka-project: --linux-icon=deploy/appimage/qcanvas.svg
|
|
5
|
+
# nuitka-project: --standalone
|
|
6
|
+
# nuitka-project: --include-module=yt_dlp
|
|
7
|
+
|
|
8
|
+
# Anti-bloat
|
|
9
|
+
|
|
10
|
+
##########################################################
|
|
11
|
+
# nuitka-project: --include-package=aiosqlite
|
|
12
|
+
# nuitka-project: --nofollow-import-to=aiosqlite.tests.*
|
|
13
|
+
##########################################################
|
|
14
|
+
# nuitka-project: --nofollow-import-to=rich
|
|
15
|
+
##########################################################
|
|
16
|
+
# nuitka-project: --nofollow-import-to=sqlalchemy.dialects.mssql
|
|
17
|
+
# nuitka-project: --nofollow-import-to=sqlalchemy.dialects.postgresql
|
|
18
|
+
# nuitka-project: --nofollow-import-to=sqlalchemy.dialects.oracle
|
|
19
|
+
# nuitka-project: --nofollow-import-to=sqlalchemy.dialects.mysql
|
|
20
|
+
##########################################################
|
|
21
|
+
# nuitka-project: --nofollow-import-to=rich
|
|
22
|
+
##########################################################
|
|
23
|
+
# nuitka-project: --nofollow-import-to=yt_dlp.extractor.lazy_extractors
|
|
24
|
+
|
|
1
25
|
import logging
|
|
2
26
|
from logging import INFO, WARNING
|
|
3
27
|
|
|
@@ -45,8 +45,8 @@ class ContentTree(MemoryTreeWidget, Generic[T]):
|
|
|
45
45
|
*,
|
|
46
46
|
header_text: str | Sequence[str],
|
|
47
47
|
indentation: int = 20,
|
|
48
|
-
max_width: int,
|
|
49
|
-
min_width: int,
|
|
48
|
+
max_width: Optional[int] = None,
|
|
49
|
+
min_width: Optional[int] = None,
|
|
50
50
|
) -> None:
|
|
51
51
|
if not isinstance(header_text, str) and isinstance(header_text, Sequence):
|
|
52
52
|
self.setHeaderLabels(header_text)
|
|
@@ -54,8 +54,12 @@ class ContentTree(MemoryTreeWidget, Generic[T]):
|
|
|
54
54
|
self.setHeaderLabel(header_text)
|
|
55
55
|
|
|
56
56
|
self.setIndentation(indentation)
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
|
|
58
|
+
if max_width is not None:
|
|
59
|
+
self.setMaximumWidth(max_width)
|
|
60
|
+
|
|
61
|
+
if min_width is not None:
|
|
62
|
+
self.setMinimumWidth(min_width)
|
|
59
63
|
|
|
60
64
|
def reload(self, data: T, *, sync_receipt: SyncReceipt) -> None:
|
|
61
65
|
self._reloading = True
|
|
@@ -47,18 +47,12 @@ class CourseViewer(QWidget):
|
|
|
47
47
|
downloader=downloader,
|
|
48
48
|
sync_receipt=sync_receipt,
|
|
49
49
|
)
|
|
50
|
-
# self._files_tab = FileTab.create_from_receipt(
|
|
51
|
-
# course=course,
|
|
52
|
-
# downloader=downloader,
|
|
53
|
-
# sync_receipt=sync_receipt,
|
|
54
|
-
# )
|
|
55
50
|
|
|
56
51
|
self._tabs = QTabWidget()
|
|
57
52
|
self._tabs.addTab(self._pages_tab, "Pages")
|
|
58
53
|
self._tabs.addTab(self._assignments_tab, "Assignments")
|
|
59
54
|
self._tabs.addTab(self._mail_tab, "Mail")
|
|
60
55
|
self._tabs.addTab(QLabel("Not implemented"), "Files")
|
|
61
|
-
# self._tabs.addTab(self._files_tab, "Files")
|
|
62
56
|
# self._tabs.addTab(QLabel("Not implemented"), "Panopto") # The meme lives on!
|
|
63
57
|
|
|
64
58
|
self.setLayout(layout(QVBoxLayout, self._course_label, self._tabs))
|
|
@@ -84,10 +78,6 @@ class CourseViewer(QWidget):
|
|
|
84
78
|
updates = sync_receipt.updates_by_course.get(self._course_id, None)
|
|
85
79
|
|
|
86
80
|
if updates is not None:
|
|
87
|
-
# if len(updates.updated_resources) > 0:
|
|
88
|
-
# raise Exception("Looks like you forgot to update the other numbers??????"")
|
|
89
|
-
# self._highlight_tab(0)
|
|
90
|
-
|
|
91
81
|
if len(updates.updated_pages) > 0:
|
|
92
82
|
self._highlight_tab(0)
|
|
93
83
|
|
|
@@ -50,12 +50,6 @@ class ResourceRichBrowser(QTextBrowser):
|
|
|
50
50
|
self._downloader.download_finished.connect(self._download_updated)
|
|
51
51
|
self._downloader.download_failed.connect(self._download_updated)
|
|
52
52
|
|
|
53
|
-
# _dark_listener.theme_changed.connect(self._theme_changed)
|
|
54
|
-
|
|
55
|
-
# @Slot()
|
|
56
|
-
# def _theme_changed(self, theme: str) -> None:
|
|
57
|
-
# print(theme)
|
|
58
|
-
|
|
59
53
|
def show_blank(self, completely_blank: bool = False) -> None:
|
|
60
54
|
if completely_blank:
|
|
61
55
|
self.clear()
|
|
@@ -102,16 +96,31 @@ class ResourceRichBrowser(QTextBrowser):
|
|
|
102
96
|
)
|
|
103
97
|
continue
|
|
104
98
|
|
|
105
|
-
file_link_tag = self._create_resource_link_tag(
|
|
99
|
+
file_link_tag = self._create_resource_link_tag(
|
|
100
|
+
doc, resource_id, resource_link.name == "img"
|
|
101
|
+
)
|
|
106
102
|
resource_link.replace_with(file_link_tag)
|
|
107
103
|
except NoExtractorError:
|
|
108
104
|
pass
|
|
109
105
|
|
|
110
106
|
return str(doc)
|
|
111
107
|
|
|
112
|
-
def _create_resource_link_tag(
|
|
108
|
+
def _create_resource_link_tag(
|
|
109
|
+
self, doc: BeautifulSoup, resource_id: str, is_image: bool
|
|
110
|
+
) -> Tag:
|
|
113
111
|
resource = self._current_content_resources[resource_id]
|
|
114
112
|
|
|
113
|
+
# todo not sure if this is a good idea or not
|
|
114
|
+
# if is_image and resource.download_state == db.ResourceDownloadState.DOWNLOADED:
|
|
115
|
+
# location = self._downloader.resource_download_location(resource)
|
|
116
|
+
#
|
|
117
|
+
# file_link_tag = doc.new_tag(
|
|
118
|
+
# "img",
|
|
119
|
+
# attrs={
|
|
120
|
+
# "source": location.absolute(),
|
|
121
|
+
# },
|
|
122
|
+
# )
|
|
123
|
+
# else:
|
|
115
124
|
file_link_tag = doc.new_tag(
|
|
116
125
|
"a",
|
|
117
126
|
attrs={
|
|
@@ -23,7 +23,7 @@ from qcanvas.ui.main_ui.options.quick_sync_option import QuickSyncOption
|
|
|
23
23
|
from qcanvas.ui.main_ui.options.sync_on_start_option import SyncOnStartOption
|
|
24
24
|
from qcanvas.ui.main_ui.options.theme_selection_menu import ThemeSelectionMenu
|
|
25
25
|
from qcanvas.ui.main_ui.status_bar_progress_display import StatusBarProgressDisplay
|
|
26
|
-
from qcanvas.util import paths, settings
|
|
26
|
+
from qcanvas.util import auto_downloader, paths, settings
|
|
27
27
|
from qcanvas.util.qurl_util import file_url
|
|
28
28
|
from qcanvas.util.ui_tools import create_qaction
|
|
29
29
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
3
|
import qcanvas.util.settings as settings
|
|
4
|
-
from qcanvas.util import is_url
|
|
4
|
+
from qcanvas.util.url_checker import is_url
|
|
5
5
|
|
|
6
6
|
_logger = logging.getLogger(__name__)
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def needs_setup() -> bool:
|
|
10
|
-
if not is_url(settings.client.panopto_url):
|
|
10
|
+
if not settings.client.panopto_disabled and not is_url(settings.client.panopto_url):
|
|
11
11
|
return True
|
|
12
12
|
elif not is_url(settings.client.canvas_url):
|
|
13
13
|
return True
|
qcanvas/ui/setup/setup_dialog.py
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from threading import Semaphore
|
|
3
|
+
from typing import Optional
|
|
3
4
|
|
|
4
5
|
from qasync import asyncSlot
|
|
5
6
|
from qcanvas_api_clients.canvas import CanvasClient, CanvasClientConfig
|
|
6
7
|
from qcanvas_api_clients.panopto import PanoptoClient, PanoptoClientConfig
|
|
7
8
|
from qcanvas_api_clients.util.request_exceptions import ConfigInvalidError
|
|
8
|
-
from qtpy.QtCore import QUrl, Signal, Slot
|
|
9
|
+
from qtpy.QtCore import Qt, QUrl, Signal, Slot
|
|
9
10
|
from qtpy.QtGui import QDesktopServices, QIcon
|
|
10
11
|
from qtpy.QtWidgets import *
|
|
11
12
|
|
|
12
13
|
import qcanvas.util.settings as settings
|
|
13
14
|
from qcanvas import icons
|
|
14
|
-
from qcanvas.util import
|
|
15
|
-
from qcanvas.util.
|
|
15
|
+
from qcanvas.util.layouts import GridItem, grid_layout_widget, layout
|
|
16
|
+
from qcanvas.util.url_checker import is_url
|
|
16
17
|
|
|
17
18
|
_logger = logging.getLogger(__name__)
|
|
18
19
|
|
|
@@ -21,6 +22,68 @@ _tutorial_url = (
|
|
|
21
22
|
)
|
|
22
23
|
|
|
23
24
|
|
|
25
|
+
class _InputRow:
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
*,
|
|
29
|
+
label: str,
|
|
30
|
+
initial_value: str,
|
|
31
|
+
placeholder_text: Optional[str] = None,
|
|
32
|
+
is_password: bool = False,
|
|
33
|
+
):
|
|
34
|
+
self._label = QLabel(label)
|
|
35
|
+
self._input = QLineEdit(initial_value)
|
|
36
|
+
|
|
37
|
+
if placeholder_text is not None:
|
|
38
|
+
self._input.setPlaceholderText(placeholder_text)
|
|
39
|
+
|
|
40
|
+
if is_password:
|
|
41
|
+
self._input.setEchoMode(QLineEdit.EchoMode.Password)
|
|
42
|
+
|
|
43
|
+
def set_error(self, message: Optional[str]) -> None:
|
|
44
|
+
self._input.setStyleSheet("QLineEdit { border: 1px solid red }")
|
|
45
|
+
self._input.setToolTip(message)
|
|
46
|
+
|
|
47
|
+
def clear_error(self) -> None:
|
|
48
|
+
self._input.setStyleSheet(None)
|
|
49
|
+
self._input.setToolTip(None)
|
|
50
|
+
|
|
51
|
+
def grid_row(self) -> list[QWidget]:
|
|
52
|
+
return [self._label, self._input]
|
|
53
|
+
|
|
54
|
+
def disable(self) -> None:
|
|
55
|
+
self._input.setEnabled(False)
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def enabled(self) -> bool:
|
|
59
|
+
return self._input.isEnabled()
|
|
60
|
+
|
|
61
|
+
@enabled.setter
|
|
62
|
+
def enabled(self, value: bool) -> None:
|
|
63
|
+
self._input.setEnabled(value)
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def text(self) -> str:
|
|
67
|
+
return self._input.text().strip()
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def url_text(self) -> str:
|
|
71
|
+
url = self.text
|
|
72
|
+
|
|
73
|
+
if not url.startswith("http"):
|
|
74
|
+
return "https://" + url
|
|
75
|
+
else:
|
|
76
|
+
return url
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def is_valid_url(self) -> bool:
|
|
80
|
+
return is_url(self.url_text)
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def is_empty(self) -> bool:
|
|
84
|
+
return len(self.text) == 0
|
|
85
|
+
|
|
86
|
+
|
|
24
87
|
class SetupDialog(QDialog):
|
|
25
88
|
closed = Signal()
|
|
26
89
|
|
|
@@ -33,38 +96,58 @@ class SetupDialog(QDialog):
|
|
|
33
96
|
self.setWindowIcon(QIcon(icons.main_icon))
|
|
34
97
|
|
|
35
98
|
self._semaphore = Semaphore()
|
|
36
|
-
|
|
37
|
-
self._canvas_url_box
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
99
|
+
|
|
100
|
+
self._canvas_url_box = _InputRow(
|
|
101
|
+
label="Canvas URL",
|
|
102
|
+
initial_value=settings.client.canvas_url,
|
|
103
|
+
placeholder_text="https://instance.canvas.com",
|
|
104
|
+
)
|
|
105
|
+
self._panopto_url_box = _InputRow(
|
|
106
|
+
label="Panopto URL",
|
|
107
|
+
initial_value=settings.client.panopto_url,
|
|
108
|
+
placeholder_text="https://instance.panopto.com",
|
|
109
|
+
)
|
|
110
|
+
self._canvas_api_key_box = _InputRow(
|
|
111
|
+
label="Canvas API Key",
|
|
112
|
+
initial_value=settings.client.canvas_api_key,
|
|
113
|
+
is_password=True,
|
|
114
|
+
)
|
|
115
|
+
self._disable_panopto_checkbox = QCheckBox("Continue without Panopto")
|
|
116
|
+
self._disable_panopto_checkbox.checkStateChanged.connect(
|
|
117
|
+
self._disable_panopto_check_changed
|
|
118
|
+
)
|
|
42
119
|
self._button_box = self._setup_button_box()
|
|
43
|
-
self._button_box.accepted.connect(self._accepted)
|
|
44
|
-
self._button_box.helpRequested.connect(self._help_requested)
|
|
45
120
|
self._waiting_indicator = self._setup_progress_bar()
|
|
46
|
-
self._status_bar = QStatusBar()
|
|
47
121
|
|
|
48
122
|
self.setLayout(
|
|
49
123
|
layout(
|
|
50
124
|
QVBoxLayout,
|
|
51
125
|
grid_layout_widget(
|
|
52
126
|
[
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
127
|
+
self._canvas_url_box.grid_row(),
|
|
128
|
+
self._canvas_api_key_box.grid_row(),
|
|
129
|
+
self._panopto_url_box.grid_row(),
|
|
130
|
+
[
|
|
131
|
+
GridItem(
|
|
132
|
+
self._disable_panopto_checkbox,
|
|
133
|
+
col_span=2,
|
|
134
|
+
alignment=Qt.AlignmentFlag.AlignRight,
|
|
135
|
+
)
|
|
136
|
+
],
|
|
56
137
|
]
|
|
57
138
|
),
|
|
58
139
|
self._waiting_indicator,
|
|
59
140
|
self._button_box,
|
|
60
|
-
self._status_bar,
|
|
61
141
|
)
|
|
62
142
|
)
|
|
63
143
|
|
|
64
144
|
def _setup_button_box(self) -> QDialogButtonBox:
|
|
65
145
|
box = QDialogButtonBox()
|
|
66
146
|
box.addButton(QDialogButtonBox.StandardButton.Ok)
|
|
67
|
-
box.addButton("Get a Canvas API
|
|
147
|
+
box.addButton("Get a Canvas API Key", QDialogButtonBox.ButtonRole.HelpRole)
|
|
148
|
+
|
|
149
|
+
box.accepted.connect(self._verify_settings)
|
|
150
|
+
box.helpRequested.connect(self._help_requested)
|
|
68
151
|
return box
|
|
69
152
|
|
|
70
153
|
def _setup_progress_bar(self) -> QProgressBar:
|
|
@@ -81,32 +164,33 @@ class SetupDialog(QDialog):
|
|
|
81
164
|
widget.setSizePolicy(size_policy)
|
|
82
165
|
|
|
83
166
|
@asyncSlot()
|
|
84
|
-
async def
|
|
167
|
+
async def _verify_settings(self) -> None:
|
|
85
168
|
if self._semaphore.acquire(False):
|
|
86
169
|
try:
|
|
87
170
|
self._clear_errors()
|
|
88
171
|
|
|
89
|
-
if not self.
|
|
90
|
-
self._status_bar.showMessage("Invalid input!", 5000)
|
|
172
|
+
if not self._check_all_inputs():
|
|
91
173
|
return
|
|
92
174
|
|
|
93
175
|
self._waiting_indicator.setVisible(True)
|
|
94
|
-
self._status_bar.showMessage("Checking configuration...")
|
|
95
176
|
|
|
96
177
|
canvas_config = CanvasClientConfig(
|
|
97
|
-
api_token=self._canvas_api_key_box.text
|
|
98
|
-
canvas_url=self.
|
|
178
|
+
api_token=self._canvas_api_key_box.text,
|
|
179
|
+
canvas_url=self._canvas_url_box.url_text,
|
|
99
180
|
)
|
|
100
181
|
|
|
101
182
|
if not await self._check_canvas_config(canvas_config):
|
|
102
183
|
return
|
|
103
184
|
|
|
104
|
-
if
|
|
105
|
-
self.
|
|
106
|
-
|
|
185
|
+
if self._panopto_enabled:
|
|
186
|
+
if not await self._check_panopto_config(canvas_config):
|
|
187
|
+
self._show_panopto_help()
|
|
188
|
+
return
|
|
107
189
|
except Exception as e:
|
|
108
|
-
self._status_bar.showMessage(f"An error occurred: {e}", 5000)
|
|
109
190
|
_logger.warning("Checking config failed", exc_info=e)
|
|
191
|
+
|
|
192
|
+
error_box = QErrorMessage(self)
|
|
193
|
+
error_box.showMessage(f"Checking config failed: {e}")
|
|
110
194
|
finally:
|
|
111
195
|
self._waiting_indicator.setVisible(False)
|
|
112
196
|
self._semaphore.release()
|
|
@@ -117,59 +201,40 @@ class SetupDialog(QDialog):
|
|
|
117
201
|
_logger.debug("Validation already in progress")
|
|
118
202
|
|
|
119
203
|
def _clear_errors(self) -> None:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
self._status_bar.clearMessage()
|
|
126
|
-
line_edit.setStyleSheet(None)
|
|
127
|
-
line_edit.setToolTip(None)
|
|
128
|
-
|
|
129
|
-
def _all_inputs_valid(self) -> bool:
|
|
204
|
+
self._canvas_url_box.clear_error()
|
|
205
|
+
self._panopto_url_box.clear_error()
|
|
206
|
+
self._canvas_api_key_box.clear_error()
|
|
207
|
+
|
|
208
|
+
def _check_all_inputs(self) -> bool:
|
|
130
209
|
all_valid = True
|
|
131
210
|
|
|
132
|
-
if not
|
|
211
|
+
if not self._canvas_url_box.is_valid_url:
|
|
133
212
|
all_valid = False
|
|
134
|
-
self.
|
|
135
|
-
|
|
213
|
+
self._canvas_url_box.set_error("Canvas URL is invalid")
|
|
214
|
+
|
|
215
|
+
if self._canvas_api_key_box.is_empty:
|
|
136
216
|
all_valid = False
|
|
137
|
-
self.
|
|
138
|
-
|
|
217
|
+
self._canvas_api_key_box.set_error("Canvas API key is empty")
|
|
218
|
+
|
|
219
|
+
if self._panopto_enabled and not self._panopto_url_box.is_valid_url:
|
|
139
220
|
all_valid = False
|
|
140
|
-
self.
|
|
221
|
+
self._panopto_url_box.set_error("Panopto URL is invalid")
|
|
141
222
|
|
|
142
223
|
return all_valid
|
|
143
224
|
|
|
144
|
-
def _get_url(self, line_edit: QLineEdit) -> str:
|
|
145
|
-
url = line_edit.text().strip()
|
|
146
|
-
|
|
147
|
-
if not url.startswith("http"):
|
|
148
|
-
return "https://" + url
|
|
149
|
-
else:
|
|
150
|
-
return url
|
|
151
|
-
|
|
152
225
|
async def _check_canvas_config(self, canvas_config: CanvasClientConfig) -> bool:
|
|
153
226
|
try:
|
|
154
227
|
await CanvasClient.verify_config(canvas_config)
|
|
155
228
|
return True
|
|
156
229
|
except ConfigInvalidError:
|
|
157
|
-
self.
|
|
230
|
+
self._canvas_api_key_box.set_error("Canvas API key is invalid")
|
|
158
231
|
return False
|
|
159
232
|
|
|
160
|
-
def _show_error(self, line_edit: QLineEdit, text: str) -> None:
|
|
161
|
-
line_edit.setToolTip(text)
|
|
162
|
-
self._waiting_indicator.hide()
|
|
163
|
-
self._highlight_line_edit(line_edit)
|
|
164
|
-
|
|
165
|
-
def _highlight_line_edit(self, line_edit: QLineEdit) -> None:
|
|
166
|
-
line_edit.setStyleSheet("QLineEdit { border: 1px solid red }")
|
|
167
|
-
|
|
168
233
|
async def _check_panopto_config(self, canvas_config: CanvasClientConfig) -> bool:
|
|
169
234
|
client = CanvasClient(canvas_config)
|
|
170
235
|
try:
|
|
171
236
|
await PanoptoClient.verify_config(
|
|
172
|
-
PanoptoClientConfig(panopto_url=self.
|
|
237
|
+
PanoptoClientConfig(panopto_url=self._panopto_url_box.url_text),
|
|
173
238
|
client,
|
|
174
239
|
)
|
|
175
240
|
return True
|
|
@@ -194,15 +259,21 @@ class SetupDialog(QDialog):
|
|
|
194
259
|
|
|
195
260
|
@Slot()
|
|
196
261
|
def _open_panopto_login(self) -> None:
|
|
197
|
-
url = QUrl(self.
|
|
262
|
+
url = QUrl(self._panopto_url_box.url_text)
|
|
198
263
|
url.setPath("/Panopto/Pages/Auth/Login.aspx")
|
|
199
264
|
url.setQuery("instance=Canvas&AllowBounce=true")
|
|
200
265
|
QDesktopServices.openUrl(url)
|
|
201
266
|
|
|
202
267
|
def _save_and_close(self) -> None:
|
|
203
|
-
settings.client.canvas_url = self.
|
|
204
|
-
|
|
205
|
-
|
|
268
|
+
settings.client.canvas_url = self._canvas_url_box.url_text
|
|
269
|
+
|
|
270
|
+
if self._panopto_enabled:
|
|
271
|
+
settings.client.panopto_url = self._panopto_url_box.url_text
|
|
272
|
+
else:
|
|
273
|
+
settings.client.panopto_disabled = True
|
|
274
|
+
|
|
275
|
+
settings.client.canvas_api_key = self._canvas_api_key_box.text
|
|
276
|
+
|
|
206
277
|
self.closed.emit()
|
|
207
278
|
self.close()
|
|
208
279
|
|
|
@@ -223,3 +294,11 @@ class SetupDialog(QDialog):
|
|
|
223
294
|
@Slot()
|
|
224
295
|
def _open_tutorial(self) -> None:
|
|
225
296
|
QDesktopServices.openUrl(QUrl(_tutorial_url))
|
|
297
|
+
|
|
298
|
+
@Slot(Qt.CheckState)
|
|
299
|
+
def _disable_panopto_check_changed(self, state: Qt.CheckState) -> None:
|
|
300
|
+
self._panopto_url_box.enabled = state == Qt.CheckState.Unchecked
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def _panopto_enabled(self) -> bool:
|
|
304
|
+
return self._disable_panopto_checkbox.checkState() == Qt.CheckState.Unchecked
|
qcanvas/util/__init__.py
CHANGED
qcanvas/util/auto_downloader.py
CHANGED
|
@@ -4,8 +4,7 @@ import logging
|
|
|
4
4
|
import qcanvas_backend.database.types as db
|
|
5
5
|
from qcanvas_backend.net.resources.download.resource_manager import ResourceManager
|
|
6
6
|
from qcanvas_backend.net.sync.sync_receipt import SyncReceipt
|
|
7
|
-
from qtpy.QtWidgets import QMessageBox
|
|
8
|
-
from qtpy.QtWidgets import QWidget
|
|
7
|
+
from qtpy.QtWidgets import QMessageBox, QWidget
|
|
9
8
|
|
|
10
9
|
from qcanvas.util import settings
|
|
11
10
|
|
qcanvas/util/paths.py
CHANGED
|
@@ -1,30 +1,27 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
|
-
import platform
|
|
4
|
-
import sys
|
|
5
3
|
from pathlib import Path
|
|
6
4
|
|
|
7
5
|
import cachetools
|
|
8
6
|
import platformdirs
|
|
9
7
|
from qtpy.QtCore import QSettings
|
|
10
8
|
|
|
9
|
+
from qcanvas.util.runtime import *
|
|
10
|
+
|
|
11
11
|
_logger = logging.getLogger(__name__)
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
|
|
14
|
+
def ui_storage() -> Path:
|
|
15
|
+
return root() / ".UI"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def data_storage() -> Path:
|
|
19
|
+
return root()
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
def client_settings() -> QSettings:
|
|
21
|
-
if
|
|
23
|
+
if is_running_portable:
|
|
22
24
|
return QSettings("QCanvas.ini", QSettings.Format.IniFormat)
|
|
23
|
-
elif _is_running_as_pyinstaller and _is_running_on_windows:
|
|
24
|
-
return QSettings(
|
|
25
|
-
str(platformdirs.user_documents_path() / "QCanvasTeam" / "QCanvas.ini"),
|
|
26
|
-
QSettings.Format.IniFormat,
|
|
27
|
-
)
|
|
28
25
|
else:
|
|
29
26
|
return QSettings("QCanvasTeam", "QCanvas")
|
|
30
27
|
|
|
@@ -33,20 +30,12 @@ def client_settings() -> QSettings:
|
|
|
33
30
|
def root() -> Path:
|
|
34
31
|
root_path = Path()
|
|
35
32
|
|
|
36
|
-
if
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
if is_running_as_flatpak:
|
|
34
|
+
# Flatpak does not support portable mode
|
|
35
|
+
root_path = Path(os.environ["XDG_DATA_HOME"])
|
|
36
|
+
elif not is_running_portable and is_running_as_compiled:
|
|
37
|
+
root_path = platformdirs.user_data_path("QCanvasReborn", "QCanvasTeam")
|
|
41
38
|
|
|
42
39
|
print("Root path", root_path.absolute())
|
|
43
40
|
_logger.debug("Root path %s", root_path.absolute())
|
|
44
41
|
return root_path
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def ui_storage() -> Path:
|
|
48
|
-
return root() / ".UI"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def data_storage() -> Path:
|
|
52
|
-
return root()
|
qcanvas/util/runtime.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import platform
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
is_running_portable = Path(".portable").exists()
|
|
7
|
+
is_running_on_windows = platform.system() == "Windows"
|
|
8
|
+
is_running_on_linux = platform.system() == "Linux"
|
|
9
|
+
_is_nuitka = "__compiled__" in globals()
|
|
10
|
+
_is_pyinstaller = getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS")
|
|
11
|
+
is_running_as_compiled = _is_nuitka or _is_pyinstaller
|
|
12
|
+
is_running_as_flatpak = os.environ.get("container", "") == "flatpak"
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"is_running_portable",
|
|
16
|
+
"is_running_as_flatpak",
|
|
17
|
+
"is_running_on_linux",
|
|
18
|
+
"is_running_as_compiled",
|
|
19
|
+
"is_running_on_windows",
|
|
20
|
+
]
|
|
@@ -15,6 +15,7 @@ class _ClientSettings:
|
|
|
15
15
|
canvas_url: MappedSetting[Optional[str]] = MappedSetting(default=None)
|
|
16
16
|
canvas_api_key: MappedSetting[Optional[str]] = MappedSetting(default=None)
|
|
17
17
|
panopto_url: MappedSetting[Optional[str]] = MappedSetting(default=None)
|
|
18
|
+
panopto_disabled = BoolSetting(default=False)
|
|
18
19
|
quick_sync_enabled = BoolSetting(default=False)
|
|
19
20
|
sync_on_start = BoolSetting(default=False)
|
|
20
21
|
download_new_resources = BoolSetting(default=False)
|
|
@@ -27,5 +28,13 @@ class _ClientSettings:
|
|
|
27
28
|
)
|
|
28
29
|
|
|
29
30
|
@property
|
|
30
|
-
def panopto_config(self) -> PanoptoClientConfig:
|
|
31
|
-
|
|
31
|
+
def panopto_config(self) -> Optional[PanoptoClientConfig]:
|
|
32
|
+
"""
|
|
33
|
+
Generates a panopto client config. If panopto is disabled, it returns None.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
if self.panopto_disabled:
|
|
37
|
+
_logger.debug("Panopto is disabled")
|
|
38
|
+
return None
|
|
39
|
+
else:
|
|
40
|
+
return PanoptoClientConfig(panopto_url=self.panopto_url)
|
qcanvas/util/themes.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
3
|
import qdarktheme
|
|
4
|
+
from qtpy.QtWidgets import QApplication, QStyleFactory
|
|
4
5
|
|
|
5
6
|
_logger = logging.getLogger(__name__)
|
|
6
7
|
|
|
@@ -18,6 +19,8 @@ def apply(theme: str) -> None:
|
|
|
18
19
|
theme = ensure_theme_is_valid(theme)
|
|
19
20
|
|
|
20
21
|
if theme != "native":
|
|
22
|
+
QApplication.setStyle(QStyleFactory.create("Fusion"))
|
|
23
|
+
|
|
21
24
|
qdarktheme.setup_theme(
|
|
22
25
|
theme,
|
|
23
26
|
custom_colors={"primary": "e02424"},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: qcanvas
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: QCanvas is a desktop client for Canvas LMS.
|
|
5
5
|
Author: QCanvas
|
|
6
6
|
Author-email: QCanvas@noreply.codeberg.org
|
|
@@ -14,8 +14,8 @@ Requires-Dist: lightdb (>=2.0,<3.0)
|
|
|
14
14
|
Requires-Dist: platformdirs (>=4.2.2,<5.0.0)
|
|
15
15
|
Requires-Dist: pyqtdarktheme-fork (>=2.3.2,<3.0.0)
|
|
16
16
|
Requires-Dist: qasync (>=0.27.1,<0.28.0)
|
|
17
|
-
Requires-Dist: qcanvas-api-clients (>=0.
|
|
18
|
-
Requires-Dist: qcanvas-backend (
|
|
17
|
+
Requires-Dist: qcanvas-api-clients (>=0.3.0,<0.4.0)
|
|
18
|
+
Requires-Dist: qcanvas-backend (>=0.2.0,<0.3.0)
|
|
19
19
|
Requires-Dist: qtpy (>=2.4.1,<3.0.0)
|
|
20
20
|
Requires-Dist: sqlalchemy (>=2.0.31,<3.0.0)
|
|
21
21
|
Requires-Dist: validators (>=0.33.0,<0.34.0)
|