Browse Source

Adds rough first version of rating stars

It's not fully polished just yet

CURA-6013
Jaime van Kessel 6 years ago
parent
commit
f4220da550

+ 1 - 1
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),

+ 101 - 0
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 + ")"
+            }
+        }
+    }
+}

+ 10 - 0
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

+ 7 - 1
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.

+ 38 - 15
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
 

+ 11 - 0
resources/themes/cura-light/icons/star_empty.svg

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="14px" height="13px" viewBox="0 0 14 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
+    <title>Star Copy 8</title>
+    <desc>Created with Sketch.</desc>
+    <g id="Toolbox-VIP-material" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Marketplace-hover-" transform="translate(-140.000000, -457.000000)" stroke="#666666">
+            <path d="M150.450431,468.749111 L149.791458,464.907 L152.582915,462.186001 L148.725216,461.625444 L147,458.129776 L145.274784,461.625444 L141.417085,462.186001 L144.208542,464.907 L143.549569,468.749111 L147,466.935112 L150.450431,468.749111 Z" id="Star-Copy-8"></path>
+        </g>
+    </g>
+</svg>

+ 11 - 0
resources/themes/cura-light/icons/star_filled.svg

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="14px" height="13px" viewBox="0 0 14 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
+    <title>Star Copy 7</title>
+    <desc>Created with Sketch.</desc>
+    <g id="Toolbox-VIP-material" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Marketplace-hover-" transform="translate(-127.000000, -457.000000)" fill="#666666" stroke="#666666">
+            <path d="M137.450431,468.749111 L136.791458,464.907 L139.582915,462.186001 L135.725216,461.625444 L134,458.129776 L132.274784,461.625444 L128.417085,462.186001 L131.208542,464.907 L130.549569,468.749111 L134,466.935112 L137.450431,468.749111 Z" id="Star-Copy-7"></path>
+        </g>
+    </g>
+</svg>

+ 1 - 0
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],