qcanvas 1.2.1__py3-none-any.whl → 2.0.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/__init__.py +54 -0
- qcanvas/app.py +93 -0
- qcanvas/backend_connectors/frontend_resource_manager.py +13 -5
- qcanvas/backend_connectors/qcanvas_task_master.py +2 -2
- qcanvas/icons/__init__.py +5 -5
- qcanvas/icons/_icon_type.py +1 -1
- qcanvas/icons/icons.qrc +47 -43
- qcanvas/icons/rc_icons.py +1298 -1197
- qcanvas/settings/__init__.py +6 -0
- qcanvas/{util/settings → settings}/_client_settings.py +4 -4
- qcanvas/settings/_course_settings.py +54 -0
- qcanvas/{util/settings → settings}/_mapped_setting.py +2 -5
- qcanvas/{util/settings → settings}/_ui_settings.py +5 -5
- qcanvas/theme.py +101 -0
- qcanvas/ui/course_viewer/content_tree.py +9 -12
- qcanvas/ui/course_viewer/course_tree/_course_icon_generator.py +3 -3
- qcanvas/ui/course_viewer/course_tree/course_tree.py +9 -8
- qcanvas/ui/course_viewer/course_viewer.py +35 -43
- qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tab.py +107 -29
- qcanvas/ui/course_viewer/tabs/assignment_tab/assignment_tree.py +4 -4
- qcanvas/ui/course_viewer/tabs/constants.py +1 -0
- qcanvas/ui/course_viewer/tabs/content_tab.py +33 -39
- qcanvas/ui/course_viewer/tabs/file_tab/file_tab.py +4 -4
- qcanvas/ui/course_viewer/tabs/file_tab/file_tree.py +7 -10
- qcanvas/ui/course_viewer/tabs/file_tab/pages_file_tree.py +6 -7
- qcanvas/ui/course_viewer/tabs/mail_tab/mail_tab.py +50 -27
- qcanvas/ui/course_viewer/tabs/mail_tab/mail_tree.py +7 -8
- qcanvas/ui/course_viewer/tabs/page_tab/page_tab.py +3 -3
- qcanvas/ui/course_viewer/tabs/page_tab/page_tree.py +5 -5
- qcanvas/ui/course_viewer/tabs/resource_rich_browser.py +15 -21
- qcanvas/ui/course_viewer/tree_widget_data_item.py +1 -1
- qcanvas/ui/memory_tree/_tree_memory.py +45 -42
- qcanvas/ui/memory_tree/memory_tree_widget.py +22 -18
- qcanvas/ui/memory_tree/memory_tree_widget_item.py +3 -3
- qcanvas/ui/qcanvas_window/__init__.py +1 -0
- qcanvas/ui/{main_ui → qcanvas_window}/course_viewer_container.py +10 -10
- qcanvas/ui/{main_ui → qcanvas_window}/options/auto_download_resources_option.py +5 -5
- qcanvas/ui/{main_ui → qcanvas_window}/options/quick_sync_option.py +7 -6
- qcanvas/ui/{main_ui → qcanvas_window}/options/sync_on_start_option.py +7 -6
- qcanvas/ui/{main_ui → qcanvas_window}/options/theme_selection_menu.py +10 -10
- qcanvas/ui/{main_ui → qcanvas_window}/qcanvas_window.py +42 -36
- qcanvas/ui/{main_ui → qcanvas_window}/status_bar_progress_display.py +5 -6
- qcanvas/ui/qml_components/AttachmentsList.ui.qml +15 -0
- qcanvas/ui/qml_components/AttachmentsListDelegate.qml +77 -0
- qcanvas/ui/qml_components/AttachmentsListModel.qml +19 -0
- qcanvas/ui/qml_components/AttachmentsPane.qml +12 -0
- qcanvas/ui/qml_components/CommentsList.ui.qml +15 -0
- qcanvas/ui/qml_components/CommentsListDelegate.ui.qml +118 -0
- qcanvas/ui/qml_components/CommentsListModel.qml +56 -0
- qcanvas/ui/qml_components/CommentsPane.qml +12 -0
- qcanvas/ui/qml_components/DarkTheme.qml +12 -0
- qcanvas/ui/qml_components/DecoratedText.ui.qml +44 -0
- qcanvas/ui/qml_components/LightTheme.qml +11 -0
- qcanvas/ui/qml_components/Spacer.ui.qml +7 -0
- qcanvas/ui/qml_components/ThemedRectangle.qml +37 -0
- qcanvas/ui/qml_components/__init__.py +3 -0
- qcanvas/ui/qml_components/attachments_pane.py +72 -0
- qcanvas/ui/qml_components/comments_pane.py +85 -0
- qcanvas/ui/qml_components/qml_bridge_types.py +95 -0
- qcanvas/ui/qml_components/qml_pane.py +22 -0
- qcanvas/ui/setup/setup_checker.py +1 -1
- qcanvas/ui/setup/setup_dialog.py +27 -10
- qcanvas/util/auto_downloader.py +9 -7
- qcanvas/util/basic_fonts.py +2 -2
- qcanvas/util/context_dict.py +12 -0
- qcanvas/util/file_icons.py +11 -19
- qcanvas/util/layouts.py +5 -7
- qcanvas/util/paths.py +17 -6
- qcanvas/util/qurl_util.py +1 -1
- qcanvas/util/ui_tools.py +118 -8
- qcanvas/util/url_checker.py +1 -1
- {qcanvas-1.2.1.dist-info → qcanvas-2.0.0.dist-info}/METADATA +13 -11
- qcanvas-2.0.0.dist-info/RECORD +91 -0
- {qcanvas-1.2.1.dist-info → qcanvas-2.0.0.dist-info}/WHEEL +1 -1
- qcanvas-2.0.0.dist-info/entry_points.txt +3 -0
- qcanvas/app_start/__init__.py +0 -59
- qcanvas/icons/_update_icons.py +0 -89
- qcanvas/icons/dark/actions/exit.svg +0 -3
- qcanvas/icons/dark/actions/mark_all_read.svg +0 -3
- qcanvas/icons/dark/actions/open_downloads.svg +0 -3
- qcanvas/icons/dark/actions/quick_login.svg +0 -3
- qcanvas/icons/dark/actions/sync.svg +0 -3
- qcanvas/icons/dark/branding/logo_transparent.svg +0 -303
- qcanvas/icons/dark/options/auto_download.svg +0 -3
- qcanvas/icons/dark/options/theme.svg +0 -3
- qcanvas/icons/dark/tabs/assignments.svg +0 -3
- qcanvas/icons/dark/tabs/mail.svg +0 -3
- qcanvas/icons/dark/tabs/pages.svg +0 -3
- qcanvas/icons/dark/tree_items/assignment.svg +0 -3
- qcanvas/icons/dark/tree_items/mail.svg +0 -3
- qcanvas/icons/dark/tree_items/module.svg +0 -3
- qcanvas/icons/dark/tree_items/page.svg +0 -3
- qcanvas/icons/light/actions/exit.svg +0 -3
- qcanvas/icons/light/actions/mark_all_read.svg +0 -3
- qcanvas/icons/light/actions/open_downloads.svg +0 -3
- qcanvas/icons/light/actions/quick_login.svg +0 -3
- qcanvas/icons/light/actions/sync.svg +0 -3
- qcanvas/icons/light/branding/logo_transparent.svg +0 -304
- qcanvas/icons/light/options/auto_download.svg +0 -3
- qcanvas/icons/light/options/ignore_old.svg +0 -3
- qcanvas/icons/light/options/include_videos.svg +0 -3
- qcanvas/icons/light/options/theme.svg +0 -3
- qcanvas/icons/light/tabs/assignments.svg +0 -3
- qcanvas/icons/light/tabs/mail.svg +0 -3
- qcanvas/icons/light/tabs/pages.svg +0 -3
- qcanvas/icons/light/tree_items/assignment.svg +0 -3
- qcanvas/icons/light/tree_items/mail.svg +0 -3
- qcanvas/icons/light/tree_items/module.svg +0 -3
- qcanvas/icons/light/tree_items/page.svg +0 -3
- qcanvas/icons/universal/branding/main_icon.svg +0 -325
- qcanvas/icons/universal/downloads/download_failed.svg +0 -23
- qcanvas/icons/universal/downloads/downloaded.svg +0 -23
- qcanvas/icons/universal/downloads/not_downloaded.svg +0 -23
- qcanvas/icons/universal/downloads/unknown.svg +0 -6
- qcanvas/icons/universal/tabs/assignments_new_content.svg +0 -3
- qcanvas/icons/universal/tabs/mail_new_content.svg +0 -3
- qcanvas/icons/universal/tabs/pages_new_content.svg +0 -3
- qcanvas/icons/universal/tree_items/semester.svg +0 -108
- qcanvas/run.py +0 -54
- qcanvas/ui/course_viewer/tabs/util.py +0 -11
- qcanvas/ui/main_ui/__init__.py +0 -0
- qcanvas/util/settings/__init__.py +0 -9
- qcanvas/util/themes/__init__.py +0 -2
- qcanvas/util/themes/_colour_scheme_helper.py +0 -38
- qcanvas/util/themes/_selected_theme.py +0 -10
- qcanvas/util/themes/_theme_changed_event.py +0 -17
- qcanvas/util/themes/_theme_changer.py +0 -86
- qcanvas-1.2.1.dist-info/RECORD +0 -118
- qcanvas-1.2.1.dist-info/entry_points.txt +0 -3
- /qcanvas/ui/{main_ui → qcanvas_window}/options/__init__.py +0 -0
|
@@ -1,31 +1,33 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import *
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
3
|
+
from libqcanvas import db
|
|
4
|
+
from libqcanvas.net.resources.download.resource_manager import ResourceManager
|
|
5
|
+
from libqcanvas.net.sync.sync_receipt import SyncReceipt
|
|
6
|
+
from PySide6.QtCore import Slot
|
|
7
|
+
from PySide6.QtWidgets import (
|
|
8
|
+
QGridLayout,
|
|
9
|
+
QLabel,
|
|
10
|
+
QWidget,
|
|
11
|
+
QLayout,
|
|
12
|
+
)
|
|
9
13
|
|
|
10
14
|
from qcanvas.ui.course_viewer.content_tree import ContentTree
|
|
11
15
|
from qcanvas.ui.course_viewer.tabs.resource_rich_browser import ResourceRichBrowser
|
|
12
|
-
from qcanvas.util.basic_fonts import bold_font
|
|
13
|
-
from qcanvas.util.ui_tools import make_truncatable
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
import qcanvas.util.ui_tools as ui
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
_logger = logging.getLogger(__name__)
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
class ContentTab(QWidget):
|
|
21
23
|
@classmethod
|
|
22
|
-
def create_from_receipt(
|
|
24
|
+
def create_from_receipt[T: ContentTab](
|
|
23
25
|
cls: T,
|
|
24
26
|
*,
|
|
25
27
|
course: db.Course,
|
|
26
28
|
sync_receipt: SyncReceipt,
|
|
27
29
|
downloader: ResourceManager,
|
|
28
|
-
) ->
|
|
30
|
+
) -> type[T]:
|
|
29
31
|
return cls(course=course, sync_receipt=sync_receipt, downloader=downloader)
|
|
30
32
|
|
|
31
33
|
def __init__(
|
|
@@ -36,49 +38,41 @@ class ContentTab(QWidget):
|
|
|
36
38
|
downloader: ResourceManager,
|
|
37
39
|
):
|
|
38
40
|
super().__init__()
|
|
39
|
-
self.
|
|
41
|
+
self.content_grid = QGridLayout()
|
|
40
42
|
self._placeholder_text = title_placeholder_text
|
|
41
43
|
self._title_label = self._create_title_label()
|
|
42
|
-
self.
|
|
44
|
+
self._use_info_grid = False
|
|
45
|
+
self._info_grid = QWidget()
|
|
46
|
+
self._info_grid.hide()
|
|
43
47
|
self._viewer = ResourceRichBrowser(downloader=downloader)
|
|
44
48
|
self._explorer = explorer
|
|
45
49
|
|
|
50
|
+
self.setLayout(self.content_grid)
|
|
46
51
|
self._setup_layout()
|
|
47
52
|
self._explorer.item_selected.connect(self._item_selected)
|
|
48
53
|
|
|
49
54
|
def enable_info_grid(self) -> None:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
grid_widget = QWidget()
|
|
54
|
-
grid_widget.setLayout(grid_layout)
|
|
55
|
-
grid_widget.hide()
|
|
56
|
-
|
|
57
|
-
self._info_grid = grid_widget
|
|
58
|
-
self._content_vbox.insertWidget(1, grid_widget)
|
|
55
|
+
self._info_grid.setLayout(self.setup_info_grid())
|
|
56
|
+
self._use_info_grid = True
|
|
59
57
|
|
|
60
58
|
def _create_title_label(self) -> QLabel:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
return ui.label(
|
|
60
|
+
self._placeholder_text,
|
|
61
|
+
font=ui.font(point_size=12, bold=True),
|
|
62
|
+
allow_truncation=True,
|
|
63
|
+
)
|
|
65
64
|
|
|
66
|
-
def setup_info_grid(self) ->
|
|
65
|
+
def setup_info_grid(self) -> QLayout:
|
|
67
66
|
"""
|
|
68
67
|
Override this if you need an info grid
|
|
69
68
|
"""
|
|
70
69
|
raise NotImplementedError()
|
|
71
70
|
|
|
72
71
|
def _setup_layout(self) -> None:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
self.
|
|
77
|
-
self._content_vbox.addWidget(self._viewer)
|
|
78
|
-
|
|
79
|
-
parent_layout.addLayout(self._content_vbox)
|
|
80
|
-
|
|
81
|
-
self.setLayout(parent_layout)
|
|
72
|
+
self.content_grid.addWidget(self._explorer, 0, 0, 4, 1)
|
|
73
|
+
self.content_grid.addWidget(self._title_label, 0, 1)
|
|
74
|
+
self.content_grid.addWidget(self._info_grid, 1, 1)
|
|
75
|
+
self.content_grid.addWidget(self._viewer, 2, 1)
|
|
82
76
|
|
|
83
77
|
def reload(self, course: db.Course, *, sync_receipt: SyncReceipt) -> None:
|
|
84
78
|
self._explorer.reload(course, sync_receipt=sync_receipt)
|
|
@@ -95,7 +89,7 @@ class ContentTab(QWidget):
|
|
|
95
89
|
self._title_label.setText(item.name)
|
|
96
90
|
self._viewer.show_content(item)
|
|
97
91
|
|
|
98
|
-
if self.
|
|
92
|
+
if self._use_info_grid:
|
|
99
93
|
self._info_grid.show()
|
|
100
94
|
self.update_info_grid(item)
|
|
101
95
|
|
|
@@ -106,5 +100,5 @@ class ContentTab(QWidget):
|
|
|
106
100
|
self._title_label.setText(self._placeholder_text)
|
|
107
101
|
self._viewer.show_blank(completely_blank=True)
|
|
108
102
|
|
|
109
|
-
if self.
|
|
103
|
+
if self._use_info_grid:
|
|
110
104
|
self._info_grid.hide()
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from
|
|
3
|
+
from libqcanvas import db
|
|
4
|
+
from libqcanvas.net.resources.download.resource_manager import ResourceManager
|
|
5
|
+
from libqcanvas.net.sync.sync_receipt import SyncReceipt
|
|
6
|
+
from PySide6.QtWidgets import QHBoxLayout, QWidget
|
|
7
7
|
|
|
8
8
|
from qcanvas.ui.course_viewer.tabs.file_tab.pages_file_tree import PagesFileTree
|
|
9
9
|
from qcanvas.util.layouts import layout
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from
|
|
2
|
+
from abc import ABC
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
4
|
+
from libqcanvas import db
|
|
5
|
+
from libqcanvas.net.resources.download.resource_manager import ResourceManager
|
|
6
|
+
from libqcanvas.net.sync.sync_receipt import SyncReceipt
|
|
7
|
+
from PySide6.QtCore import QPoint, Qt, Slot
|
|
8
|
+
from PySide6.QtWidgets import QHeaderView, QMenu, QTreeWidgetItem
|
|
9
9
|
|
|
10
10
|
from qcanvas.ui.course_viewer.content_tree import ContentTree
|
|
11
11
|
from qcanvas.ui.course_viewer.tree_widget_data_item import (
|
|
@@ -19,10 +19,7 @@ from qcanvas.util.ui_tools import create_qaction
|
|
|
19
19
|
_logger = logging.getLogger(__name__)
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class FileTree(ContentTree[db.Course]):
|
|
22
|
+
class FileTree(ContentTree[db.Course], ABC):
|
|
26
23
|
@classmethod
|
|
27
24
|
def create_from_receipt(
|
|
28
25
|
cls,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Sequence
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
from
|
|
4
|
+
from libqcanvas import db
|
|
5
|
+
from libqcanvas.net.resources.download.resource_manager import ResourceManager
|
|
6
|
+
from libqcanvas.net.sync.sync_receipt import SyncReceipt
|
|
7
7
|
|
|
8
8
|
from qcanvas.ui.course_viewer.tabs.file_tab.file_tree import FileTree
|
|
9
9
|
from qcanvas.ui.memory_tree import MemoryTreeWidgetItem
|
|
@@ -12,7 +12,6 @@ _logger = logging.getLogger(__name__)
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class PagesFileTree(FileTree):
|
|
15
|
-
|
|
16
15
|
def __init__(self, tree_name: str, *, resource_manager: ResourceManager):
|
|
17
16
|
super().__init__(
|
|
18
17
|
tree_name=f"{tree_name}.pages", resource_manager=resource_manager
|
|
@@ -24,14 +23,14 @@ class PagesFileTree(FileTree):
|
|
|
24
23
|
widgets = []
|
|
25
24
|
|
|
26
25
|
for group in data.modules: # type: db.Module
|
|
27
|
-
if
|
|
26
|
+
if not group.pages:
|
|
28
27
|
continue
|
|
29
28
|
|
|
30
29
|
# Init group_widget lazily to prevent creating pointless tree widgets
|
|
31
30
|
group_widget: MemoryTreeWidgetItem | None = None
|
|
32
31
|
items_in_group = set()
|
|
33
32
|
|
|
34
|
-
for item in group.
|
|
33
|
+
for item in group.pages:
|
|
35
34
|
resource_widgets = []
|
|
36
35
|
|
|
37
36
|
for resource in item.resources: # type: db.Resource
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import override
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from
|
|
4
|
+
from PySide6.QtCore import Qt
|
|
5
|
+
from libqcanvas import db
|
|
6
|
+
from libqcanvas.net.sync.sync_receipt import SyncReceipt
|
|
7
|
+
from libqcanvas.util import as_local
|
|
8
|
+
from PySide6.QtWidgets import QLabel, QLayout, QMainWindow, QDockWidget
|
|
8
9
|
|
|
10
|
+
from qcanvas.backend_connectors import FrontendResourceManager
|
|
9
11
|
from qcanvas.ui.course_viewer.tabs.content_tab import ContentTab
|
|
10
12
|
from qcanvas.ui.course_viewer.tabs.mail_tab.mail_tree import MailTree
|
|
11
|
-
from qcanvas.ui.course_viewer.tabs.
|
|
12
|
-
|
|
13
|
-
from qcanvas.
|
|
13
|
+
from qcanvas.ui.course_viewer.tabs.constants import date_strftime_format
|
|
14
|
+
import qcanvas.util.ui_tools as ui
|
|
15
|
+
from qcanvas.ui.qml_components import AttachmentsPane
|
|
14
16
|
|
|
15
17
|
_logger = logging.getLogger(__name__)
|
|
16
18
|
|
|
@@ -22,38 +24,59 @@ class MailTab(ContentTab):
|
|
|
22
24
|
*,
|
|
23
25
|
course: db.Course,
|
|
24
26
|
sync_receipt: SyncReceipt,
|
|
25
|
-
downloader:
|
|
27
|
+
downloader: FrontendResourceManager,
|
|
26
28
|
):
|
|
29
|
+
self._main_container = QMainWindow()
|
|
30
|
+
|
|
27
31
|
super().__init__(
|
|
28
32
|
explorer=MailTree.create_from_receipt(course, sync_receipt=sync_receipt),
|
|
29
33
|
title_placeholder_text="No mail selected",
|
|
30
34
|
downloader=downloader,
|
|
31
35
|
)
|
|
32
36
|
|
|
37
|
+
self._main_container.setCentralWidget(self._viewer)
|
|
38
|
+
self._files_pane = AttachmentsPane(downloader)
|
|
39
|
+
self._files_dock = ui.dock_widget(
|
|
40
|
+
widget=self._files_pane,
|
|
41
|
+
title="Attachments",
|
|
42
|
+
name="attachments",
|
|
43
|
+
min_size=ui.size(150, 100),
|
|
44
|
+
features=QDockWidget.DockWidgetFeature.DockWidgetMovable,
|
|
45
|
+
)
|
|
46
|
+
self._main_container.addDockWidget(
|
|
47
|
+
Qt.DockWidgetArea.TopDockWidgetArea, self._files_dock
|
|
48
|
+
)
|
|
33
49
|
self._date_sent_label = QLabel("")
|
|
34
50
|
self._sender_label = QLabel("")
|
|
35
51
|
|
|
36
52
|
self.enable_info_grid()
|
|
37
53
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
],
|
|
45
|
-
[
|
|
46
|
-
bold_label("Date:"),
|
|
47
|
-
self._date_sent_label,
|
|
48
|
-
],
|
|
49
|
-
]
|
|
54
|
+
@override
|
|
55
|
+
def _setup_layout(self) -> None:
|
|
56
|
+
super()._setup_layout()
|
|
57
|
+
self.content_grid.replaceWidget(
|
|
58
|
+
self._viewer,
|
|
59
|
+
self._main_container,
|
|
50
60
|
)
|
|
51
61
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
def setup_info_grid(self) -> QLayout:
|
|
63
|
+
return ui.form_layout(
|
|
64
|
+
{"From": self._sender_label, "Date": self._date_sent_label},
|
|
65
|
+
)
|
|
56
66
|
|
|
57
|
-
def update_info_grid(self, mail: db.
|
|
58
|
-
self._date_sent_label.setText(
|
|
67
|
+
def update_info_grid(self, mail: db.Message) -> None:
|
|
68
|
+
self._date_sent_label.setText(
|
|
69
|
+
as_local(mail.creation_date).strftime(date_strftime_format)
|
|
70
|
+
)
|
|
59
71
|
self._sender_label.setText(mail.sender_name)
|
|
72
|
+
|
|
73
|
+
if mail.attachments:
|
|
74
|
+
self._files_pane.load_files(mail.attachments)
|
|
75
|
+
self._files_dock.show()
|
|
76
|
+
else:
|
|
77
|
+
self._files_dock.hide()
|
|
78
|
+
|
|
79
|
+
@override
|
|
80
|
+
def _show_blank(self) -> None:
|
|
81
|
+
super()._show_blank()
|
|
82
|
+
self._files_dock.hide()
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Sequence
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
from
|
|
4
|
+
from libqcanvas import db
|
|
5
|
+
from libqcanvas.net.sync.sync_receipt import SyncReceipt
|
|
6
|
+
from PySide6.QtWidgets import QHeaderView
|
|
7
7
|
|
|
8
8
|
from qcanvas import icons
|
|
9
9
|
from qcanvas.ui.course_viewer.content_tree import ContentTree
|
|
@@ -13,11 +13,10 @@ _logger = logging.getLogger(__name__)
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class MailTree(ContentTree[db.Course]):
|
|
16
|
-
|
|
17
16
|
def __init__(self, course_id: str):
|
|
18
17
|
super().__init__(
|
|
19
18
|
tree_name=f"course.{course_id}.mail",
|
|
20
|
-
emit_selection_signal_for_type=db.
|
|
19
|
+
emit_selection_signal_for_type=db.Message,
|
|
21
20
|
)
|
|
22
21
|
|
|
23
22
|
self.ui_setup(
|
|
@@ -37,7 +36,7 @@ class MailTree(ContentTree[db.Course]):
|
|
|
37
36
|
) -> Sequence[TreeWidgetDataItem]:
|
|
38
37
|
widgets = []
|
|
39
38
|
|
|
40
|
-
for message in course.messages: # type: db.
|
|
39
|
+
for message in course.messages: # type: db.Message
|
|
41
40
|
message_widget = self._create_mail_widget(message, sync_receipt)
|
|
42
41
|
message_widget.setIcon(0, icons.tree_items.mail)
|
|
43
42
|
widgets.append(message_widget)
|
|
@@ -45,7 +44,7 @@ class MailTree(ContentTree[db.Course]):
|
|
|
45
44
|
return widgets
|
|
46
45
|
|
|
47
46
|
def _create_mail_widget(
|
|
48
|
-
self, message: db.
|
|
47
|
+
self, message: db.Message, sync_receipt: SyncReceipt
|
|
49
48
|
) -> TreeWidgetDataItem:
|
|
50
49
|
message_widget = TreeWidgetDataItem(
|
|
51
50
|
id=message.id,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
from
|
|
5
|
-
from
|
|
3
|
+
from libqcanvas import db
|
|
4
|
+
from libqcanvas.net.resources.download.resource_manager import ResourceManager
|
|
5
|
+
from libqcanvas.net.sync.sync_receipt import SyncReceipt
|
|
6
6
|
|
|
7
7
|
from qcanvas.ui.course_viewer.tabs.content_tab import ContentTab
|
|
8
8
|
from qcanvas.ui.course_viewer.tabs.page_tab.page_tree import PageTree
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from typing import Sequence
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
from
|
|
4
|
+
from libqcanvas import db
|
|
5
|
+
from libqcanvas.net.sync.sync_receipt import SyncReceipt
|
|
6
|
+
from PySide6.QtCore import Qt
|
|
7
7
|
|
|
8
8
|
from qcanvas import icons
|
|
9
9
|
from qcanvas.ui.course_viewer.content_tree import ContentTree
|
|
@@ -17,7 +17,7 @@ class PageTree(ContentTree[db.Course]):
|
|
|
17
17
|
def __init__(self, course_id: str):
|
|
18
18
|
super().__init__(
|
|
19
19
|
tree_name=f"course.{course_id}.modules",
|
|
20
|
-
emit_selection_signal_for_type=db.
|
|
20
|
+
emit_selection_signal_for_type=db.Page,
|
|
21
21
|
)
|
|
22
22
|
|
|
23
23
|
self.ui_setup(
|
|
@@ -52,7 +52,7 @@ class PageTree(ContentTree[db.Course]):
|
|
|
52
52
|
return module_widget
|
|
53
53
|
|
|
54
54
|
def _create_page_widget(
|
|
55
|
-
self, page: db.
|
|
55
|
+
self, page: db.Page, sync_receipt: SyncReceipt
|
|
56
56
|
) -> TreeWidgetDataItem:
|
|
57
57
|
page_widget = TreeWidgetDataItem(id=page.id, data=page, strings=[page.name])
|
|
58
58
|
page_widget.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable)
|
|
@@ -2,25 +2,23 @@ import html
|
|
|
2
2
|
import logging
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
from libqcanvas import db
|
|
6
6
|
from bs4 import BeautifulSoup, Tag
|
|
7
|
+
from libqcanvas.net.resources.extracting.no_extractor_error import NoExtractorError
|
|
8
|
+
from libqcanvas.util import is_link_invisible
|
|
9
|
+
from PySide6.QtCore import QUrl, Slot
|
|
10
|
+
from PySide6.QtGui import QDesktopServices
|
|
11
|
+
from PySide6.QtWidgets import QTextBrowser
|
|
7
12
|
from qasync import asyncSlot
|
|
8
|
-
from qcanvas_backend.net.resources.download.resource_manager import ResourceManager
|
|
9
|
-
from qcanvas_backend.net.resources.extracting.no_extractor_error import NoExtractorError
|
|
10
|
-
from qcanvas_backend.util import is_link_invisible
|
|
11
|
-
from qtpy.QtCore import QUrl, Slot
|
|
12
|
-
from qtpy.QtGui import QDesktopServices
|
|
13
|
-
from qtpy.QtWidgets import QTextBrowser
|
|
14
13
|
|
|
15
14
|
from qcanvas.backend_connectors import FrontendResourceManager
|
|
16
15
|
from qcanvas.util.html_cleaner import clean_up_html
|
|
17
|
-
from qcanvas.util.qurl_util import file_url
|
|
18
16
|
|
|
19
17
|
_logger = logging.getLogger(__name__)
|
|
20
18
|
|
|
21
19
|
|
|
22
20
|
class ResourceRichBrowser(QTextBrowser):
|
|
23
|
-
def __init__(self, downloader:
|
|
21
|
+
def __init__(self, downloader: FrontendResourceManager):
|
|
24
22
|
super().__init__()
|
|
25
23
|
self._downloader = downloader
|
|
26
24
|
self._content: Optional[db.CourseContentItem] = None
|
|
@@ -30,9 +28,8 @@ class ResourceRichBrowser(QTextBrowser):
|
|
|
30
28
|
self.setOpenLinks(False)
|
|
31
29
|
self.anchorClicked.connect(self._open_url)
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
self._downloader.download_failed.connect(self._download_updated)
|
|
31
|
+
self._downloader.download_finished.connect(self._download_updated)
|
|
32
|
+
self._downloader.download_failed.connect(self._download_updated)
|
|
36
33
|
|
|
37
34
|
def show_blank(self, completely_blank: bool = False) -> None:
|
|
38
35
|
if completely_blank:
|
|
@@ -43,14 +40,14 @@ class ResourceRichBrowser(QTextBrowser):
|
|
|
43
40
|
self._content = None
|
|
44
41
|
self._current_content_resources.clear()
|
|
45
42
|
|
|
46
|
-
def show_content(self, page: db.
|
|
43
|
+
def show_content(self, page: db.AnyContentItem) -> None:
|
|
47
44
|
if page.body is None:
|
|
48
45
|
self.show_blank()
|
|
49
46
|
else:
|
|
50
47
|
self._collect_resources(page)
|
|
51
48
|
self._show_page_content(page)
|
|
52
49
|
|
|
53
|
-
def _collect_resources(self, page: db.
|
|
50
|
+
def _collect_resources(self, page: db.AnyContentItem):
|
|
54
51
|
self._current_content_resources = {
|
|
55
52
|
resource.id: resource for resource in page.resources
|
|
56
53
|
}
|
|
@@ -137,16 +134,13 @@ class ResourceRichBrowser(QTextBrowser):
|
|
|
137
134
|
resource = self._current_content_resources[resource_id]
|
|
138
135
|
|
|
139
136
|
try:
|
|
140
|
-
await self._downloader.
|
|
137
|
+
await self._downloader.download_and_open(resource)
|
|
141
138
|
except Exception as e:
|
|
142
139
|
_logger.warning(
|
|
143
140
|
"Download of resource id=%s failed", resource_id, exc_info=e
|
|
144
141
|
)
|
|
145
142
|
return
|
|
146
143
|
|
|
147
|
-
resource_path = file_url(self._downloader.resource_download_location(resource))
|
|
148
|
-
QDesktopServices.openUrl(resource_path)
|
|
149
|
-
|
|
150
144
|
@Slot(db.Resource)
|
|
151
145
|
def _download_updated(self, resource: db.Resource) -> None:
|
|
152
146
|
if self._content is not None and resource.id in self._current_content_resources:
|
|
@@ -163,8 +157,8 @@ class ResourceRichBrowser(QTextBrowser):
|
|
|
163
157
|
_logger.warning(
|
|
164
158
|
"Resource has diverged from current loaded data, applying bandaid fix"
|
|
165
159
|
)
|
|
166
|
-
self._current_content_resources[
|
|
167
|
-
resource.
|
|
168
|
-
|
|
160
|
+
self._current_content_resources[
|
|
161
|
+
resource.id
|
|
162
|
+
].download_state = resource.download_state
|
|
169
163
|
|
|
170
164
|
self._show_page_content(self._content)
|
|
@@ -1,49 +1,41 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import logging
|
|
2
3
|
from pathlib import Path
|
|
3
|
-
from typing import *
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from aiofile import async_open
|
|
6
6
|
|
|
7
7
|
from qcanvas.util import paths
|
|
8
8
|
|
|
9
9
|
_logger = logging.getLogger(__name__)
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
_state_db = LightDB(str(_storage_path()))
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class _TreeState(Model, table="trees", db=_state_db):
|
|
22
|
-
tree_name: str
|
|
23
|
-
collapsed_items: List[str] = []
|
|
24
|
-
|
|
12
|
+
class TreeMemory:
|
|
13
|
+
def __init__(self, tree_name: str):
|
|
14
|
+
self._tree_name = tree_name
|
|
15
|
+
self._loaded = False
|
|
16
|
+
self._collapsed_items: set[str] = set()
|
|
25
17
|
|
|
26
|
-
def
|
|
27
|
-
|
|
18
|
+
def load(self, force: bool = False):
|
|
19
|
+
if force or not self._loaded:
|
|
20
|
+
self._loaded = True
|
|
28
21
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
state.collapsed_items = []
|
|
33
|
-
# Important or instances will get duplicated data in some cases
|
|
34
|
-
state.save()
|
|
35
|
-
return state
|
|
36
|
-
else:
|
|
37
|
-
return state
|
|
22
|
+
if not self._storage_path.exists():
|
|
23
|
+
# Nothing to do
|
|
24
|
+
return
|
|
38
25
|
|
|
26
|
+
# fixme this blocks the event loop, but using aiofile will require significant changes to other
|
|
27
|
+
# parts of the code to accommodate these methods now being async.
|
|
28
|
+
# this will really only have any noticeable effect on slow disks, and only ever happens once anyway.
|
|
29
|
+
with open(self._storage_path, "rt") as file:
|
|
30
|
+
lines = file.read().splitlines()
|
|
31
|
+
_logger.debug("Tree % loaded %s", self._tree_name, lines)
|
|
32
|
+
self._collapsed_items.update(lines)
|
|
39
33
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
self._tree_name = tree_name
|
|
43
|
-
self._state = _get_or_create_state(tree_name)
|
|
34
|
+
async def save(self):
|
|
35
|
+
assert self._loaded, "Memory is not loaded yet"
|
|
44
36
|
|
|
45
|
-
|
|
46
|
-
|
|
37
|
+
async with async_open(self._storage_path, "wt") as file:
|
|
38
|
+
await file.write("\n".join(self._collapsed_items))
|
|
47
39
|
|
|
48
40
|
def expanded(self, node_id: str) -> None:
|
|
49
41
|
self.set_expanded(node_id, True)
|
|
@@ -52,16 +44,27 @@ class TreeMemory:
|
|
|
52
44
|
self.set_expanded(node_id, False)
|
|
53
45
|
|
|
54
46
|
def set_expanded(self, node_id: str, expanded: bool) -> None:
|
|
55
|
-
|
|
47
|
+
self.load()
|
|
56
48
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
self.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
49
|
+
if expanded and node_id in self._collapsed_items:
|
|
50
|
+
self._collapsed_items.remove(node_id)
|
|
51
|
+
else:
|
|
52
|
+
self._collapsed_items.add(node_id)
|
|
53
|
+
|
|
54
|
+
# hack: avoid blocking the event loop
|
|
55
|
+
asyncio.create_task(self.save())
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def collapsed_ids(self) -> set[str]:
|
|
59
|
+
assert self._loaded, "Memory not loaded yet"
|
|
60
|
+
return self._collapsed_items
|
|
64
61
|
|
|
65
62
|
@property
|
|
66
|
-
def
|
|
67
|
-
|
|
63
|
+
def _storage_path(self) -> Path:
|
|
64
|
+
path = (
|
|
65
|
+
paths.data_storage()
|
|
66
|
+
/ "tree_state"
|
|
67
|
+
/ f"{self._tree_name}_collapsed_items.txt"
|
|
68
|
+
)
|
|
69
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
70
|
+
return path
|