diff --git a/cura/API/Account.py b/cura/API/Account.py
index 397e220478..be77a6307b 100644
--- a/cura/API/Account.py
+++ b/cura/API/Account.py
@@ -44,7 +44,7 @@ class Account(QObject):
OAUTH_SERVER_URL= self._oauth_root,
CALLBACK_PORT=self._callback_port,
CALLBACK_URL="http://localhost:{}/callback".format(self._callback_port),
- CLIENT_ID="um---------------ultimaker_cura_drive_plugin",
+ CLIENT_ID="um----------------------------ultimaker_cura",
CLIENT_SCOPES="account.user.read drive.backup.read drive.backup.write packages.download packages.rating.read packages.rating.write",
AUTH_DATA_PREFERENCE_KEY="general/ultimaker_auth_data",
AUTH_SUCCESS_REDIRECT="{}/app/auth-success".format(self._oauth_root),
diff --git a/plugins/Toolbox/resources/qml/RatingWidget.qml b/plugins/Toolbox/resources/qml/RatingWidget.qml
new file mode 100644
index 0000000000..424f6c91c4
--- /dev/null
+++ b/plugins/Toolbox/resources/qml/RatingWidget.qml
@@ -0,0 +1,101 @@
+import QtQuick 2.2
+import QtQuick.Controls 2.0
+import UM 1.0 as UM
+
+Item
+{
+ id: ratingWidget
+
+ property real rating: 0
+ property int indexHovered: -1
+ property string packageId: ""
+ property int numRatings: 0
+ property int userRating: 0
+ width: contentRow.width
+ height: contentRow.height
+ MouseArea
+ {
+ id: mouseArea
+ anchors.fill: parent
+ hoverEnabled: ratingWidget.enabled
+ acceptedButtons: Qt.NoButton
+ onExited:
+ {
+ ratingWidget.indexHovered = -1
+ }
+
+ Row
+ {
+ id: contentRow
+ height: childrenRect.height
+ Repeater
+ {
+ model: 5 // We need to get 5 stars
+ Button
+ {
+ id: control
+ hoverEnabled: true
+ onHoveredChanged:
+ {
+ if(hovered)
+ {
+ indexHovered = index
+ }
+ }
+
+ property bool isStarFilled:
+ {
+ // If the entire widget is hovered, override the actual rating.
+ if(ratingWidget.indexHovered >= 0)
+ {
+ return indexHovered >= index
+ }
+
+ if(ratingWidget.userRating > 0)
+ {
+ return userRating >= index +1
+ }
+
+ return rating >= index + 1
+ }
+
+ contentItem: Item {}
+ height: UM.Theme.getSize("rating_star").height
+ width: UM.Theme.getSize("rating_star").width
+ background: UM.RecolorImage
+ {
+ source: UM.Theme.getIcon(control.isStarFilled ? "star_filled" : "star_empty")
+
+ // Unfilled stars should always have the default color. Only filled stars should change on hover
+ color:
+ {
+ if(!enabled)
+ {
+ return "#5a5a5a"
+ }
+ if((ratingWidget.indexHovered >= 0 || ratingWidget.userRating > 0) && isStarFilled)
+ {
+ return UM.Theme.getColor("primary")
+ }
+ return "#5a5a5a"
+ }
+ }
+ onClicked:
+ {
+ if(userRating == 0)
+ {
+ //User didn't vote yet, locally fake it
+ numRatings += 1
+ }
+ userRating = index + 1 // Fake local data
+ toolbox.ratePackage(ratingWidget.packageId, index + 1)
+ }
+ }
+ }
+ Label
+ {
+ text: "(" + numRatings + ")"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/Toolbox/resources/qml/ToolboxDownloadsGridTile.qml b/plugins/Toolbox/resources/qml/ToolboxDownloadsGridTile.qml
index cee3f0fd20..a72411ef4b 100644
--- a/plugins/Toolbox/resources/qml/ToolboxDownloadsGridTile.qml
+++ b/plugins/Toolbox/resources/qml/ToolboxDownloadsGridTile.qml
@@ -84,6 +84,16 @@ Item
color: UM.Theme.getColor("text_medium")
font: UM.Theme.getFont("default")
}
+
+ RatingWidget
+ {
+ visible: model.type == "plugin"
+ packageId: model.id
+ rating: model.average_rating != undefined ? model.average_rating : 0
+ numRatings: model.num_ratings != undefined ? model.num_ratings : 0
+ userRating: model.user_rating
+ enabled: installedPackages != 0
+ }
}
}
MouseArea
diff --git a/plugins/Toolbox/src/PackagesModel.py b/plugins/Toolbox/src/PackagesModel.py
index bcc02955a2..b3c388bc7c 100644
--- a/plugins/Toolbox/src/PackagesModel.py
+++ b/plugins/Toolbox/src/PackagesModel.py
@@ -41,6 +41,9 @@ class PackagesModel(ListModel):
self.addRoleName(Qt.UserRole + 20, "links")
self.addRoleName(Qt.UserRole + 21, "website")
self.addRoleName(Qt.UserRole + 22, "login_required")
+ self.addRoleName(Qt.UserRole + 23, "average_rating")
+ self.addRoleName(Qt.UserRole + 24, "num_ratings")
+ self.addRoleName(Qt.UserRole + 25, "user_rating")
# List of filters for queries. The result is the union of the each list of results.
self._filter = {} # type: Dict[str, str]
@@ -101,7 +104,10 @@ class PackagesModel(ListModel):
"tags": package["tags"] if "tags" in package else [],
"links": links_dict,
"website": package["website"] if "website" in package else None,
- "login_required": "login-required" in package.get("tags", [])
+ "login_required": "login-required" in package.get("tags", []),
+ "average_rating": package.get("rating", {}).get("average", 0),
+ "num_ratings": package.get("rating", {}).get("count", 0),
+ "user_rating": package.get("rating", {}).get("user", 0)
})
# Filter on all the key-word arguments.
diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py
index ab975548ce..7e35f5d1f4 100644
--- a/plugins/Toolbox/src/Toolbox.py
+++ b/plugins/Toolbox/src/Toolbox.py
@@ -22,7 +22,8 @@ from cura.CuraApplication import CuraApplication
from .AuthorsModel import AuthorsModel
from .PackagesModel import PackagesModel
-
+from cura.CuraVersion import CuraVersion
+from cura.API import CuraAPI
if TYPE_CHECKING:
from cura.Settings.GlobalStack import GlobalStack
@@ -50,17 +51,10 @@ class Toolbox(QObject, Extension):
self._download_progress = 0 # type: float
self._is_downloading = False # type: bool
self._network_manager = None # type: Optional[QNetworkAccessManager]
- self._request_header = [
- b"User-Agent",
- str.encode(
- "%s/%s (%s %s)" % (
- self._application.getApplicationName(),
- self._application.getVersion(),
- platform.system(),
- platform.machine(),
- )
- )
- ]
+ self._request_headers = [] # type: List[Tuple(bytes, bytes)]
+ self._updateRequestHeader()
+
+
self._request_urls = {} # type: Dict[str, QUrl]
self._to_update = [] # type: List[str] # Package_ids that are waiting to be updated
self._old_plugin_ids = set() # type: Set[str]
@@ -115,6 +109,7 @@ class Toolbox(QObject, Extension):
self._restart_dialog_message = "" # type: str
self._application.initializationFinished.connect(self._onAppInitialized)
+ self._application.getCuraAPI().account.loginStateChanged.connect(self._updateRequestHeader)
# Signals:
# --------------------------------------------------------------------------
@@ -134,12 +129,38 @@ class Toolbox(QObject, Extension):
showLicenseDialog = pyqtSignal()
uninstallVariablesChanged = pyqtSignal()
+ def _updateRequestHeader(self):
+ self._request_headers = [
+ (b"User-Agent",
+ str.encode(
+ "%s/%s (%s %s)" % (
+ self._application.getApplicationName(),
+ self._application.getVersion(),
+ platform.system(),
+ platform.machine(),
+ )
+ ))
+ ]
+ access_token = self._application.getCuraAPI().account.accessToken
+ if access_token:
+ self._request_headers.append((b"Authorization", "Bearer {}".format(access_token).encode()))
+
def _resetUninstallVariables(self) -> None:
self._package_id_to_uninstall = None # type: Optional[str]
self._package_name_to_uninstall = ""
self._package_used_materials = [] # type: List[Tuple[GlobalStack, str, str]]
self._package_used_qualities = [] # type: List[Tuple[GlobalStack, str, str]]
+ @pyqtSlot(str, int)
+ def ratePackage(self, package_id: str, rating: int) -> None:
+ url = QUrl("{base_url}/packages/{package_id}/ratings".format(base_url=self._api_url, package_id = package_id))
+
+ self._rate_request = QNetworkRequest(url)
+ for header_name, header_value in self._request_headers:
+ cast(QNetworkRequest, self._rate_request).setRawHeader(header_name, header_value)
+ data = "{\"data\": {\"cura_version\": \"%s\", \"rating\": %i}}" % (Version(CuraVersion), rating)
+ self._rate_reply = cast(QNetworkAccessManager, self._network_manager).put(self._rate_request, data.encode())
+
@pyqtSlot(result = str)
def getLicenseDialogPluginName(self) -> str:
return self._license_dialog_plugin_name
@@ -563,7 +584,8 @@ class Toolbox(QObject, Extension):
def _makeRequestByType(self, request_type: str) -> None:
Logger.log("i", "Requesting %s metadata from server.", request_type)
request = QNetworkRequest(self._request_urls[request_type])
- request.setRawHeader(*self._request_header)
+ for header_name, header_value in self._request_headers:
+ request.setRawHeader(header_name, header_value)
if self._network_manager:
self._network_manager.get(request)
@@ -578,7 +600,8 @@ class Toolbox(QObject, Extension):
if hasattr(QNetworkRequest, "RedirectPolicyAttribute"):
# Patch for Qt 5.9+
cast(QNetworkRequest, self._download_request).setAttribute(QNetworkRequest.RedirectPolicyAttribute, True)
- cast(QNetworkRequest, self._download_request).setRawHeader(*self._request_header)
+ for header_name, header_value in self._request_headers:
+ cast(QNetworkRequest, self._download_request).setRawHeader(header_name, header_value)
self._download_reply = cast(QNetworkAccessManager, self._network_manager).get(self._download_request)
self.setDownloadProgress(0)
self.setIsDownloading(True)
@@ -660,7 +683,7 @@ class Toolbox(QObject, Extension):
else:
self.setViewPage("errored")
self.resetDownload()
- else:
+ elif reply.operation() == QNetworkAccessManager.PutOperation:
# Ignore any operation that is not a get operation
pass
diff --git a/resources/themes/cura-light/icons/star_empty.svg b/resources/themes/cura-light/icons/star_empty.svg
new file mode 100644
index 0000000000..39b5791e91
--- /dev/null
+++ b/resources/themes/cura-light/icons/star_empty.svg
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/resources/themes/cura-light/icons/star_filled.svg b/resources/themes/cura-light/icons/star_filled.svg
new file mode 100644
index 0000000000..d4e161f6c6
--- /dev/null
+++ b/resources/themes/cura-light/icons/star_filled.svg
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json
index 2d7e92be4d..8d8f5dd718 100644
--- a/resources/themes/cura-light/theme.json
+++ b/resources/themes/cura-light/theme.json
@@ -394,6 +394,7 @@
"section": [0.0, 2.2],
"section_icon": [1.6, 1.6],
"section_icon_column": [2.8, 0.0],
+ "rating_star": [1.0, 1.0],
"setting": [25.0, 1.8],
"setting_control": [10.0, 2.0],