qcanvas 1.0.12.dev3__py3-none-any.whl → 1.2.0a0__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.

Files changed (36) hide show
  1. qcanvas/app_start/__init__.py +6 -1
  2. qcanvas/icons/__init__.py +1 -0
  3. qcanvas/icons/file-download-failed.svg +22 -5
  4. qcanvas/icons/file-downloaded.svg +22 -5
  5. qcanvas/icons/file-not-downloaded.svg +22 -5
  6. qcanvas/icons/icons.qrc +1 -0
  7. qcanvas/icons/logo-transparent-light.svg +304 -0
  8. qcanvas/icons/rc_icons.py +599 -330
  9. qcanvas/run.py +26 -1
  10. qcanvas/ui/course_viewer/content_tree.py +21 -4
  11. qcanvas/ui/course_viewer/course_viewer.py +13 -14
  12. qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py +9 -12
  13. qcanvas/ui/course_viewer/tabs/file_tab/__init__.py +1 -0
  14. qcanvas/ui/course_viewer/tabs/file_tab/file_tab.py +46 -0
  15. qcanvas/ui/course_viewer/tabs/file_tab/file_tree.py +95 -0
  16. qcanvas/ui/course_viewer/tabs/file_tab/pages_file_tree.py +50 -0
  17. qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py +4 -7
  18. qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py +7 -9
  19. qcanvas/ui/course_viewer/tabs/resource_rich_browser.py +17 -8
  20. qcanvas/ui/course_viewer/tabs/util.py +10 -0
  21. qcanvas/ui/main_ui/course_viewer_container.py +32 -2
  22. qcanvas/ui/main_ui/qcanvas_window.py +1 -1
  23. qcanvas/ui/main_ui/status_bar_progress_display.py +17 -8
  24. qcanvas/ui/setup/setup_checker.py +2 -2
  25. qcanvas/ui/setup/setup_dialog.py +144 -65
  26. qcanvas/util/__init__.py +0 -2
  27. qcanvas/util/auto_downloader.py +1 -2
  28. qcanvas/util/file_icons.py +36 -0
  29. qcanvas/util/paths.py +15 -26
  30. qcanvas/util/runtime.py +20 -0
  31. qcanvas/util/settings/_client_settings.py +11 -2
  32. qcanvas/util/themes.py +9 -0
  33. {qcanvas-1.0.12.dev3.dist-info → qcanvas-1.2.0a0.dist-info}/METADATA +7 -3
  34. {qcanvas-1.0.12.dev3.dist-info → qcanvas-1.2.0a0.dist-info}/RECORD +36 -29
  35. {qcanvas-1.0.12.dev3.dist-info → qcanvas-1.2.0a0.dist-info}/WHEEL +0 -0
  36. {qcanvas-1.0.12.dev3.dist-info → qcanvas-1.2.0a0.dist-info}/entry_points.txt +0 -0
@@ -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 is_url
15
- from qcanvas.util.layouts import grid_layout_widget, layout
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
- self._canvas_url_box = QLineEdit(settings.client.canvas_url)
37
- self._canvas_url_box.setPlaceholderText("https://instance.canvas.com")
38
- self._canvas_api_key_box = QLineEdit(settings.client.canvas_api_key)
39
- self._canvas_api_key_box.setEchoMode(QLineEdit.EchoMode.Password)
40
- self._panopto_url_box = QLineEdit(settings.client.panopto_url)
41
- self._panopto_url_box.setPlaceholderText("https://instance.panopto.com")
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
- [QLabel("Canvas URL"), self._canvas_url_box],
54
- [QLabel("Canvas API Key"), self._canvas_api_key_box],
55
- [QLabel("Panopto URL"), self._panopto_url_box],
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 key", QDialogButtonBox.ButtonRole.HelpRole)
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 _accepted(self) -> None:
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._all_inputs_valid():
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().strip(),
98
- canvas_url=self._get_url(self._canvas_url_box),
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 not await self._check_panopto_config(canvas_config):
105
- self._show_panopto_help()
106
- return
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
- for line_edit in [
121
- self._canvas_url_box,
122
- self._panopto_url_box,
123
- self._canvas_api_key_box,
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 is_url(self._get_url(self._canvas_url_box)):
211
+ if not self._canvas_url_box.is_valid_url:
133
212
  all_valid = False
134
- self._show_error(self._canvas_url_box, "Canvas URL is invalid")
135
- if len(self._canvas_api_key_box.text().strip()) == 0:
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._show_error(self._canvas_api_key_box, "Canvas API key is empty")
138
- if not is_url(self._get_url(self._panopto_url_box)):
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._show_error(self._panopto_url_box, "Panopto URL is invalid")
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._show_error(self._canvas_api_key_box, "Canvas API key is invalid")
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._get_url(self._panopto_url_box)),
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._get_url(self._panopto_url_box))
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._get_url(self._canvas_url_box)
204
- settings.client.panopto_url = self._get_url(self._panopto_url_box)
205
- settings.client.canvas_api_key = self._canvas_api_key_box.text().strip()
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
@@ -1,2 +0,0 @@
1
- # todo remove this
2
- from .url_checker import is_url
@@ -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
 
@@ -0,0 +1,36 @@
1
+ import logging
2
+
3
+ from qtpy.QtCore import QFileInfo, QMimeDatabase
4
+ from qtpy.QtGui import QIcon
5
+ from qtpy.QtWidgets import QApplication, QFileIconProvider, QStyle
6
+
7
+ import qcanvas.util.runtime as runtime
8
+
9
+ _logger = logging.getLogger(__name__)
10
+
11
+ # Windows and linux have different ways of doing this
12
+ if runtime.is_running_on_windows:
13
+ _icon_provider = QFileIconProvider()
14
+
15
+ def icon_for_filename(file_name: str) -> QIcon:
16
+ return _icon_provider.icon(QFileInfo(file_name))
17
+
18
+ else:
19
+ _mime_database = QMimeDatabase()
20
+ _default_icon = None
21
+
22
+ def icon_for_filename(file_name: str) -> QIcon:
23
+ global _default_icon
24
+
25
+ for mime_type in _mime_database.mimeTypesForFileName(file_name):
26
+ icon = QIcon.fromTheme(mime_type.iconName())
27
+
28
+ if not icon.isNull():
29
+ return icon
30
+
31
+ if _default_icon is None:
32
+ _default_icon = QApplication.style().standardIcon(
33
+ QStyle.StandardPixmap.SP_FileIcon
34
+ )
35
+
36
+ return _default_icon
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
- _is_running_portable = Path(".portable").exists()
14
- _is_running_on_windows = platform.system() == "Windows"
15
- _is_running_on_linux = platform.system() == "Linux"
16
- _is_running_as_pyinstaller = getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS")
17
- _is_running_as_flatpak = os.environ.get("container", "") == "flatpak"
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 _is_running_portable:
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 not _is_running_portable:
37
- if _is_running_as_flatpak:
38
- root_path = Path(os.environ["XDG_DATA_HOME"])
39
- elif _is_running_as_pyinstaller:
40
- root_path = platformdirs.user_data_path("QCanvasReborn", "QCanvasTeam")
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()
@@ -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
- return PanoptoClientConfig(panopto_url=self.panopto_url)
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,9 @@
1
1
  import logging
2
2
 
3
+ import darkdetect
3
4
  import qdarktheme
5
+ from qtpy.QtGui import QGuiApplication
6
+ from qtpy.QtWidgets import QApplication, QStyleFactory
4
7
 
5
8
  _logger = logging.getLogger(__name__)
6
9
 
@@ -18,7 +21,13 @@ def apply(theme: str) -> None:
18
21
  theme = ensure_theme_is_valid(theme)
19
22
 
20
23
  if theme != "native":
24
+
21
25
  qdarktheme.setup_theme(
22
26
  theme,
23
27
  custom_colors={"primary": "e02424"},
24
28
  )
29
+
30
+ QApplication.setStyle(QStyleFactory.create("Fusion"))
31
+
32
+ _logger.debug("darkdetect says: %s", darkdetect.theme())
33
+ _logger.debug("qt says: %s", QGuiApplication.styleHints().colorScheme())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qcanvas
3
- Version: 1.0.12.dev3
3
+ Version: 1.2.0a0
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.2.2,<0.3.0)
18
- Requires-Dist: qcanvas-backend (==0.1.10)
17
+ Requires-Dist: qcanvas-api-clients (>=0.3.0,<0.4.0)
18
+ Requires-Dist: qcanvas-backend (>=0.2.2,<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)
@@ -25,6 +25,10 @@ Description-Content-Type: text/markdown
25
25
 
26
26
  QCanvas is an **unofficial** desktop client for Canvas LMS.
27
27
 
28
+ https://codeberg.org/QCanvas/QCanvas
29
+
30
+ https://github.com/QCanvas/QCanvasApp
31
+
28
32
  # Downloads
29
33
 
30
34
  Download it from [releases](https://github.com/QCanvas/QCanvasApp/releases)
@@ -1,68 +1,75 @@
1
1
  qcanvas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- qcanvas/app_start/__init__.py,sha256=Qq_8Zgqhkd_lHLOvUH6NP95-WofxyC0R_vgZx2hQF7g,1274
2
+ qcanvas/app_start/__init__.py,sha256=r16bg9Otw8Sy6MYLmIt5Nu-oSwyYP8Y2kzMtbmeF6m0,1437
3
3
  qcanvas/backend_connectors/__init__.py,sha256=Wj8cmxQng3SSlmlXJyzHaPmvxbkauwsxINckPb7WuHc,108
4
4
  qcanvas/backend_connectors/frontend_resource_manager.py,sha256=87oszxCle0Y9AUAHH3wn_7oilY45ddMET-4EnpTtjn0,1322
5
5
  qcanvas/backend_connectors/qcanvas_task_master.py,sha256=CTPBvf_9w-livscKNspnd98mNvFLYTYHef0RQSUkfFc,742
6
- qcanvas/icons/__init__.py,sha256=eaZWyt-xEsLqRVI2HNvevgRhXUnM_oyDrT5hSyLM1eE,246
7
- qcanvas/icons/file-download-failed.svg,sha256=b1Nx5mW_dbh3nuex4e-11btcGILLg59PHUG5lV6bc2U,1327
8
- qcanvas/icons/file-downloaded.svg,sha256=ymf1NU5uvKZq41kNVTtKkUoIzUE3pbHmx5IvwPYoyiU,789
9
- qcanvas/icons/file-not-downloaded.svg,sha256=TVpIZYDUfOw3S0cmXR3FawiCp0Y_PTSR4XKwrsy1qOs,1270
6
+ qcanvas/icons/__init__.py,sha256=UxZ4PLlkLirumWPU8OreBYXjnelexE6W8VK5u9UWXKo,302
7
+ qcanvas/icons/file-download-failed.svg,sha256=lhDf8ul__T2ByRcd5KZicNtUVl4BdSqeZ3yOR6DvCLk,1091
8
+ qcanvas/icons/file-downloaded.svg,sha256=fWDcSy0PwQX3LZwaEVJLE3fUVg7q1TO3n44svyUyb64,1091
9
+ qcanvas/icons/file-not-downloaded.svg,sha256=7zl_Gmx2IHZrugFYAUjBCSwmfXM0pvlshj8GhewxY5Q,1091
10
10
  qcanvas/icons/file-unknown.svg,sha256=9xlN244HJX3zM_MqdMTnNbKlqLjOFgCx3ZdM4Wc4zC0,1729
11
- qcanvas/icons/icons.qrc,sha256=czwaoP0TNXGoH91cstRbdhvKnfyLlmAp-VmaZoBk0YY,280
11
+ qcanvas/icons/icons.qrc,sha256=h181m7XWs1Xs_yhuT8UNj-LBO5hdcpCGbU0uoeXUT7A,328
12
+ qcanvas/icons/logo-transparent-light.svg,sha256=DsNOFWHGAVCZ-ue-pf1iatQ1t_IBmnyxs3FNw-UUukg,18647
12
13
  qcanvas/icons/main_icon.svg,sha256=st2sfA8HIETmoacJ2Oq84iJzfnNHH-T03ijB-J419_s,16104
13
- qcanvas/icons/rc_icons.py,sha256=Ankeu0oWJk-1qLGVLxmiK5Kt1LVNyDmpR8jJZzOG5gQ,18194
14
+ qcanvas/icons/rc_icons.py,sha256=VI1sBDuXbHHzolgtjDVi2flt4SjB9DKqt-vlQuRvXvY,31692
14
15
  qcanvas/icons/sync.svg,sha256=J-7_KnFbQL3uh-RrTy0_wSJUVW4Cc6ZSTacld6ULv1w,2829
15
- qcanvas/run.py,sha256=lZZPpdlQWbcSJnhY2i2zJ1xizhdy1j2EoV1BUYVXkfw,548
16
+ qcanvas/run.py,sha256=XW4NQEZtBICRqLQUOCzzbFspYtBHBJhkHJKdB25hpVQ,1726
16
17
  qcanvas/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
18
  qcanvas/ui/course_viewer/__init__.py,sha256=XkoFnh4ULw3_i-GDsOlueEWido0PkoDcN9_EX6-nkXY,76
18
- qcanvas/ui/course_viewer/content_tree.py,sha256=sxYcNzyVUbZe-Nmoz7FXvmb9J6bNwqi8G7xZOtVqKhQ,3993
19
+ qcanvas/ui/course_viewer/content_tree.py,sha256=QS0mcyxLDdXt27TBzdCTYu2fuchPO-j6z6VPJT6rhkA,4478
19
20
  qcanvas/ui/course_viewer/course_tree.py,sha256=LkzquiAIKzFwB9dkOx-QmIp7CkKJ8R5Kk0aeYHQLu5s,2709
20
- qcanvas/ui/course_viewer/course_viewer.py,sha256=X-bughys3-6MmrRb7-9vVgRixDZbzKBj66AeabJzmDo,3970
21
+ qcanvas/ui/course_viewer/course_viewer.py,sha256=n7d4KjWJAPJcHjy0BQcRLOsV5fyM1vWIdYA_AmxMlSE,3922
21
22
  qcanvas/ui/course_viewer/tabs/__init__.py,sha256=SlfWUzk6_E5uM9GIV-y9BVeKMwqn3pRx_xWhMyb1dfI,54
22
23
  qcanvas/ui/course_viewer/tabs/assignment_tab/__init__.py,sha256=w936dW7za10Fh6rN0zVA-7Kyiup3kd6C-mPAFHtxmy0,42
23
24
  qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tab.py,sha256=_de3IPBCe27Bt8MoabcX1ZchBbVb-_QHVeYMvL3wzr8,1986
24
- qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py,sha256=sy9Cu5ZMt_Jb6PK-ikyB8CvSvdGecsK_8sPpKigTlfo,2924
25
+ qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py,sha256=z6-8SHBh6OvVy8lH-YeR28cGEh7V0wOi1_EQcw5hFcc,2691
25
26
  qcanvas/ui/course_viewer/tabs/content_tab.py,sha256=CAfzUYawDde0lhL7R7dFu07uS1XEG39VIleS2Cpu30g,3540
27
+ qcanvas/ui/course_viewer/tabs/file_tab/__init__.py,sha256=mMebnrNw_V-lQ-eBXS_kFinL-ZdUAXa459NmuyuCu6s,30
28
+ qcanvas/ui/course_viewer/tabs/file_tab/file_tab.py,sha256=DOpnSDg7vDRvLc4LFf94BHkx0cRy6InYLpKH7n_p4IU,1404
29
+ qcanvas/ui/course_viewer/tabs/file_tab/file_tree.py,sha256=Q5lRr619ktn_CDs4yr5Lv276-BH8JK907fJTn2cDEz0,3199
30
+ qcanvas/ui/course_viewer/tabs/file_tab/pages_file_tree.py,sha256=UDoTkZp1IJTQBevveCXATDQmAYCC6Yf1HivmN2ifHdw,1605
26
31
  qcanvas/ui/course_viewer/tabs/mail_tab/__init__.py,sha256=68iRUUWEP7mudbaxa4ZBKMra4rvs2oZKaZkBWwmUrsI,30
27
32
  qcanvas/ui/course_viewer/tabs/mail_tab/mail_tab.py,sha256=fw0FORJiH19rAGsjBFJHZlNrVCMbwG4n3AFV3wbhwGM,1840
28
- qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py,sha256=5yCLjkwFV1qIOq-NYeUds_gz_gi-yy0OXblNXt_zzVQ,1799
33
+ qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py,sha256=K7RLLZ8KwqJrzsrcxBSAJ9MlJ34O1zqMRsPuL_wMoPQ,1639
29
34
  qcanvas/ui/course_viewer/tabs/page_tab/__init__.py,sha256=lcafxlSEVZ0wqZySxT6hTrvExX-GU2AfcZQbp6W8haU,30
30
35
  qcanvas/ui/course_viewer/tabs/page_tab/page_tab.py,sha256=cv-pCDri7thRJZv-gWUZrL2FUQAHe7vZthRAhejsqWY,771
31
- qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py,sha256=ugxEOaNUdWDsyj7GC-LUtH_OsONgdWRSyugFNPBhZS4,2172
32
- qcanvas/ui/course_viewer/tabs/resource_rich_browser.py,sha256=422vPQF9afS_tLFqakMqk0qf-KR4N0gm9ytT5hVFF6I,6278
33
- qcanvas/ui/course_viewer/tabs/util.py,sha256=rUVEGSREV9vTFs4o3AD2OjaSFA-GPsmelxYWz0J8OP4,48
36
+ qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py,sha256=ffM-IFdNPBdTggmJCnseuO35xs7zqo9C78580p209Jw,1967
37
+ qcanvas/ui/course_viewer/tabs/resource_rich_browser.py,sha256=dTRsJO_xeo72vsX0FrT1XHfr9VlFjnM9woeFxVg0dDw,6649
38
+ qcanvas/ui/course_viewer/tabs/util.py,sha256=Cg8FmPbkiTepV9IlTZQSWtcUaovv66iAD4_BUYJjIAY,344
34
39
  qcanvas/ui/main_ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
- qcanvas/ui/main_ui/course_viewer_container.py,sha256=R3w0EImYQTUMPPtoaTIZZTf8MCMDC_5Ia_c-MBJ6-QQ,1912
40
+ qcanvas/ui/main_ui/course_viewer_container.py,sha256=yXZyVfqyKZWkmsYVqIzNHWfShTib40Rs60BpLzCRM0c,2849
36
41
  qcanvas/ui/main_ui/options/__init__.py,sha256=SlfWUzk6_E5uM9GIV-y9BVeKMwqn3pRx_xWhMyb1dfI,54
37
42
  qcanvas/ui/main_ui/options/auto_download_resources_option.py,sha256=W9BleGh6egfN9d9awnjEAAFq6_6DyXyD-rIopkofVDw,1284
38
43
  qcanvas/ui/main_ui/options/quick_sync_option.py,sha256=qEfmtLllO32ejc-bydCvFRjal5RUlk2HtC5Ld4mT7ss,753
39
44
  qcanvas/ui/main_ui/options/sync_on_start_option.py,sha256=zFFAAyGZEa6qBchJJIm7bJpOwxcntnto9Ee-lzCiHX4,731
40
45
  qcanvas/ui/main_ui/options/theme_selection_menu.py,sha256=MVudpolTToR1TRAtqfaFMUH__G0gc4oyqDAL5BH-7RY,1263
41
- qcanvas/ui/main_ui/qcanvas_window.py,sha256=nNU0g4PFbDkXuFM61CoJne8fuzljqRV23ek3osPLyeM,8554
42
- qcanvas/ui/main_ui/status_bar_progress_display.py,sha256=ExFRWY1SPDS2t8CBOx80TlOdENrxXGnuv7ht-DPcnwY,4836
46
+ qcanvas/ui/main_ui/qcanvas_window.py,sha256=aaN2gIyKpjcdd6NKp9E0DPt6YHBT-DX7RVRqnWSJVaQ,8554
47
+ qcanvas/ui/main_ui/status_bar_progress_display.py,sha256=vbuZwMR4It1qH8EY4ZiBz7akSDfaYZ_V18eS-pBgYq4,5047
43
48
  qcanvas/ui/memory_tree/__init__.py,sha256=-XLitM6teC0zmwPrGf-Q-A53-zgmIPASExdOtaLIvPU,107
44
49
  qcanvas/ui/memory_tree/_tree_memory.py,sha256=CMKfCnrHj22ervaq7xB5U4AiKijYvghUK5ZL0MJIFmQ,1805
45
50
  qcanvas/ui/memory_tree/memory_tree_widget.py,sha256=OT3TnYsUSbMowqR9my19QG4AuAdhTb29SGSa2406-SE,4432
46
51
  qcanvas/ui/memory_tree/memory_tree_widget_item.py,sha256=JXk07AzrKsBnYAqhayIFYAwMfF_D_EfkfJY4Qyez47U,425
47
52
  qcanvas/ui/setup/__init__.py,sha256=QWt2lEyLqWG5QC-BmCBlYyi0LZsBfsQYbP0XkvqA2f8,77
48
- qcanvas/ui/setup/setup_checker.py,sha256=ysQpkVLIWn8BR3hKhekuRIsHNAOU-jnHm061dfQ_OcY,396
49
- qcanvas/ui/setup/setup_dialog.py,sha256=HQTvIbB-T-DMf8-MIMxWWNNN6xdIW34jgAJtwjgki7U,8562
50
- qcanvas/util/__init__.py,sha256=RmC5zxGHoTLudrx9uol55fM5dvIkFjBCroQGYXaELCA,51
51
- qcanvas/util/auto_downloader.py,sha256=NZLr0joiXIWdfLB4DUiwC0MTry5ilREbZ-2qYRFGdxQ,1868
53
+ qcanvas/ui/setup/setup_checker.py,sha256=pRLZJR6ySa09DhMEt8F0VQ2D_r7IzrDJAULIatuokJw,449
54
+ qcanvas/ui/setup/setup_dialog.py,sha256=0oYk5chmMUo2fR6tmu-HkM8QXPkFdewXyx1n_X_WC4c,10272
55
+ qcanvas/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
+ qcanvas/util/auto_downloader.py,sha256=WGawgc849qpPf7ufe_yxRYhwVf2Aj15wTAAQvToxmHg,1842
52
57
  qcanvas/util/basic_fonts.py,sha256=1NK5_kejgH45mENwiTWvE5oOuAvGqWMX3hg9tTUCBi4,243
58
+ qcanvas/util/file_icons.py,sha256=Cf7l3xV5XecAS-jEUtzTs7S-Vym6bs9E2JfUXnGtFFM,1017
53
59
  qcanvas/util/html_cleaner.py,sha256=O9_PhvZZw3RcPjdXZagAbNmp8Hfyq9fydBH-ee-DfII,615
54
60
  qcanvas/util/layouts.py,sha256=7wQ0-DAbRHPPcfVIQoOmVhPdhGqcF-6qWE1-P86e7ys,1351
55
61
  qcanvas/util/logs.py,sha256=VZKFITiW2WR2POEFVv5GRpEXic23Pzjehry-vH3g3Gk,138
56
- qcanvas/util/paths.py,sha256=Gxlo8mGYASyO0NzAJbkhh1VRJjJaEoF8gtbMCqwfpGo,1500
62
+ qcanvas/util/paths.py,sha256=TzG5iy7A6ndzUdeV00lJKXq_gmbtooahueBjgvN02s8,989
57
63
  qcanvas/util/qurl_util.py,sha256=NkskYvrMQJuYWMNF4DFQ4J5-YM5CGl5gHQKxJaAhHBE,197
64
+ qcanvas/util/runtime.py,sha256=EO1HFCN7dZbQtKHQOILuvjZFXGT7SzhlmPvU8ma95Z8,617
58
65
  qcanvas/util/settings/__init__.py,sha256=ivc8bczhQdEJsWse6fc81Xyz0i2YX57pL4UubM3NJfw,228
59
- qcanvas/util/settings/_client_settings.py,sha256=VpJ80rE04iB-1bsQLr6wWzD5fB8fp-goy62o7OGABWo,1119
66
+ qcanvas/util/settings/_client_settings.py,sha256=HyUpXoc7TinOviEd-eCo8g8Fbw8TqGAH2KfCkgPr9N0,1413
60
67
  qcanvas/util/settings/_mapped_setting.py,sha256=Z6635FfDll9cCLfSkVg-unsDLvUWuKT5MmxJEiUkd2k,1823
61
68
  qcanvas/util/settings/_ui_settings.py,sha256=zlWMjpntuqm7ZN3aBEROGrXc4bhtKOfWijmiGkdt1UA,804
62
- qcanvas/util/themes.py,sha256=BE6lMf0lVE-0G_QYhK5emMdtKG4lUJw76HFX4go7R80,473
69
+ qcanvas/util/themes.py,sha256=Lw33zWTkD2jSuntYZYWPi8rxLfj5VLv-_PjtjgkPsEQ,796
63
70
  qcanvas/util/ui_tools.py,sha256=bSM1xrmZPn847YEbXAC9VIAv--8hMLMWrsEMWGA5p3E,916
64
71
  qcanvas/util/url_checker.py,sha256=gaV_KZZsG5bfJaGBv9jbHJjq0rVxIH55HRtucT6Qkx8,144
65
- qcanvas-1.0.12.dev3.dist-info/METADATA,sha256=Ccv-PcxvKyE2GZT8qBf8-zIApOoiOk2ThkQs7AJMM5Y,1715
66
- qcanvas-1.0.12.dev3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
67
- qcanvas-1.0.12.dev3.dist-info/entry_points.txt,sha256=46VbnhQ9w2CYdfhYcPfWgjXYHjsKshu0asQ1B_sAMac,44
68
- qcanvas-1.0.12.dev3.dist-info/RECORD,,
72
+ qcanvas-1.2.0a0.dist-info/METADATA,sha256=LgyN2I1DImidV4JuLlK53bshAe6BvW4T1GbbGVu0W74,1794
73
+ qcanvas-1.2.0a0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
74
+ qcanvas-1.2.0a0.dist-info/entry_points.txt,sha256=46VbnhQ9w2CYdfhYcPfWgjXYHjsKshu0asQ1B_sAMac,44
75
+ qcanvas-1.2.0a0.dist-info/RECORD,,