Fix merge conflicts
|
@ -6,7 +6,7 @@ from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty
|
|||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Message import Message
|
||||
from cura import CuraConstants
|
||||
from cura import UltimakerCloudAuthentication
|
||||
|
||||
from cura.OAuth2.AuthorizationService import AuthorizationService
|
||||
from cura.OAuth2.Models import OAuth2Settings
|
||||
|
@ -38,7 +38,7 @@ class Account(QObject):
|
|||
self._logged_in = False
|
||||
|
||||
self._callback_port = 32118
|
||||
self._oauth_root = CuraConstants.CuraCloudAccountAPIRoot
|
||||
self._oauth_root = UltimakerCloudAuthentication.CuraCloudAccountAPIRoot
|
||||
|
||||
self._oauth_settings = OAuth2Settings(
|
||||
OAUTH_SERVER_URL= self._oauth_root,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import Tuple, Optional, TYPE_CHECKING
|
||||
from typing import Tuple, Optional, TYPE_CHECKING, Dict, Any
|
||||
|
||||
from cura.Backups.BackupsManager import BackupsManager
|
||||
|
||||
|
@ -24,12 +24,12 @@ class Backups:
|
|||
## Create a new back-up using the BackupsManager.
|
||||
# \return Tuple containing a ZIP file with the back-up data and a dict
|
||||
# with metadata about the back-up.
|
||||
def createBackup(self) -> Tuple[Optional[bytes], Optional[dict]]:
|
||||
def createBackup(self) -> Tuple[Optional[bytes], Optional[Dict[str, Any]]]:
|
||||
return self.manager.createBackup()
|
||||
|
||||
## Restore a back-up using the BackupsManager.
|
||||
# \param zip_file A ZIP file containing the actual back-up data.
|
||||
# \param meta_data Some metadata needed for restoring a back-up, like the
|
||||
# Cura version number.
|
||||
def restoreBackup(self, zip_file: bytes, meta_data: dict) -> None:
|
||||
def restoreBackup(self, zip_file: bytes, meta_data: Dict[str, Any]) -> None:
|
||||
return self.manager.restoreBackup(zip_file, meta_data)
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
#
|
||||
# This file contains all constant values in Cura
|
||||
#
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
# -------------
|
||||
# Cura Versions
|
||||
# -------------
|
||||
# ---------
|
||||
# Genearl constants used in Cura
|
||||
# ---------
|
||||
DEFAULT_CURA_DISPLAY_NAME = "Ultimaker Cura"
|
||||
DEFAULT_CURA_VERSION = "master"
|
||||
DEFAULT_CURA_BUILD_TYPE = ""
|
||||
DEFAULT_CURA_DEBUG_MODE = False
|
||||
DEFAULT_CURA_SDK_VERSION = "5.0.0"
|
||||
DEFAULT_CURA_SDK_VERSION = "6.0.0"
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraAppDisplayName # type: ignore
|
||||
|
@ -35,26 +34,3 @@ try:
|
|||
from cura.CuraVersion import CuraSDKVersion # type: ignore
|
||||
except ImportError:
|
||||
CuraSDKVersion = DEFAULT_CURA_SDK_VERSION
|
||||
|
||||
|
||||
# ---------
|
||||
# Cloud API
|
||||
# ---------
|
||||
DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" # type: str
|
||||
DEFAULT_CLOUD_API_VERSION = "1" # type: str
|
||||
DEFAULT_CLOUD_ACCOUNT_API_ROOT = "https://account.ultimaker.com" # type: str
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraCloudAPIRoot # type: ignore
|
||||
except ImportError:
|
||||
CuraCloudAPIRoot = DEFAULT_CLOUD_API_ROOT
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraCloudAPIVersion # type: ignore
|
||||
except ImportError:
|
||||
CuraCloudAPIVersion = DEFAULT_CLOUD_API_VERSION
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraCloudAccountAPIRoot # type: ignore
|
||||
except ImportError:
|
||||
CuraCloudAccountAPIRoot = DEFAULT_CLOUD_ACCOUNT_API_ROOT
|
|
@ -117,7 +117,7 @@ from cura.ObjectsModel import ObjectsModel
|
|||
from cura.PrinterOutputDevice import PrinterOutputDevice
|
||||
from cura.PrinterOutput.NetworkMJPGImage import NetworkMJPGImage
|
||||
|
||||
from cura import CuraConstants
|
||||
from cura import ApplicationMetadata
|
||||
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
from UM.Decorators import override
|
||||
|
@ -167,11 +167,11 @@ class CuraApplication(QtApplication):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(name = "cura",
|
||||
app_display_name = CuraConstants.CuraAppDisplayName,
|
||||
version = CuraConstants.CuraVersion,
|
||||
api_version = CuraConstants.CuraSDKVersion,
|
||||
buildtype = CuraConstants.CuraBuildType,
|
||||
is_debug_mode = CuraConstants.CuraDebugMode,
|
||||
app_display_name = ApplicationMetadata.CuraAppDisplayName,
|
||||
version = ApplicationMetadata.CuraVersion,
|
||||
api_version = ApplicationMetadata.CuraSDKVersion,
|
||||
buildtype = ApplicationMetadata.CuraBuildType,
|
||||
is_debug_mode = ApplicationMetadata.CuraDebugMode,
|
||||
tray_icon_name = "cura-icon-32.png",
|
||||
**kwargs)
|
||||
|
||||
|
@ -957,7 +957,7 @@ class CuraApplication(QtApplication):
|
|||
engine.rootContext().setContextProperty("CuraApplication", self)
|
||||
engine.rootContext().setContextProperty("PrintInformation", self._print_information)
|
||||
engine.rootContext().setContextProperty("CuraActions", self._cura_actions)
|
||||
engine.rootContext().setContextProperty("CuraSDKVersion", CuraConstants.CuraSDKVersion)
|
||||
engine.rootContext().setContextProperty("CuraSDKVersion", ApplicationMetadata.CuraSDKVersion)
|
||||
|
||||
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
|
||||
|
||||
|
|
|
@ -8,3 +8,4 @@ CuraDebugMode = True if "@_cura_debugmode@" == "ON" else False
|
|||
CuraSDKVersion = "@CURA_SDK_VERSION@"
|
||||
CuraCloudAPIRoot = "@CURA_CLOUD_API_ROOT@"
|
||||
CuraCloudAPIVersion = "@CURA_CLOUD_API_VERSION@"
|
||||
CuraCloudAccountAPIRoot = "@CURA_CLOUD_ACCOUNT_API_ROOT@"
|
||||
|
|
24
cura/UltimakerCloudAuthentication.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
# ---------
|
||||
# Constants used for the Cloud API
|
||||
# ---------
|
||||
DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" # type: str
|
||||
DEFAULT_CLOUD_API_VERSION = 1 # type: int
|
||||
DEFAULT_CLOUD_ACCOUNT_API_ROOT = "https://account.ultimaker.com" # type: str
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraCloudAPIRoot # type: ignore
|
||||
except ImportError:
|
||||
CuraCloudAPIRoot = DEFAULT_CLOUD_API_ROOT
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraCloudAPIVersion # type: ignore
|
||||
except ImportError:
|
||||
CuraCloudAPIVersion = DEFAULT_CLOUD_API_VERSION
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraCloudAccountAPIRoot # type: ignore
|
||||
except ImportError:
|
||||
CuraCloudAccountAPIRoot = DEFAULT_CLOUD_ACCOUNT_API_ROOT
|
|
@ -1,14 +1,12 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
import os
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
is_testing = os.getenv('ENV_NAME', "development") == "testing"
|
||||
from .src.DrivePluginExtension import DrivePluginExtension
|
||||
|
||||
# Only load the whole plugin when not running tests as __init__.py is automatically loaded by PyTest
|
||||
if not is_testing:
|
||||
from .src.DrivePluginExtension import DrivePluginExtension
|
||||
|
||||
def getMetaData():
|
||||
def getMetaData():
|
||||
return {}
|
||||
|
||||
def register(app):
|
||||
return {"extension": DrivePluginExtension(app)}
|
||||
|
||||
def register(app):
|
||||
return {"extension": DrivePluginExtension()}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"description": "Backup and restore your configuration.",
|
||||
"version": "1.2.0",
|
||||
"api": 5,
|
||||
"api": 6,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
|
@ -9,56 +11,55 @@ import requests
|
|||
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.Signal import Signal
|
||||
from UM.Signal import Signal, signalemitter
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
from .UploadBackupJob import UploadBackupJob
|
||||
from .Settings import Settings
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
## The DriveApiService is responsible for interacting with the CuraDrive API and Cura's backup handling.
|
||||
@signalemitter
|
||||
class DriveApiService:
|
||||
"""
|
||||
The DriveApiService is responsible for interacting with the CuraDrive API and Cura's backup handling.
|
||||
"""
|
||||
|
||||
GET_BACKUPS_URL = "{}/backups".format(Settings.DRIVE_API_URL)
|
||||
PUT_BACKUP_URL = "{}/backups".format(Settings.DRIVE_API_URL)
|
||||
DELETE_BACKUP_URL = "{}/backups".format(Settings.DRIVE_API_URL)
|
||||
BACKUP_URL = "{}/backups".format(Settings.DRIVE_API_URL)
|
||||
|
||||
# Emit signal when restoring backup started or finished.
|
||||
onRestoringStateChanged = Signal()
|
||||
restoringStateChanged = Signal()
|
||||
|
||||
# Emit signal when creating backup started or finished.
|
||||
onCreatingStateChanged = Signal()
|
||||
creatingStateChanged = Signal()
|
||||
|
||||
def __init__(self, cura_api) -> None:
|
||||
"""Create a new instance of the Drive API service and set the cura_api object."""
|
||||
self._cura_api = cura_api
|
||||
def __init__(self) -> None:
|
||||
self._cura_api = CuraApplication.getInstance().getCuraAPI()
|
||||
|
||||
def getBackups(self) -> List[Dict[str, Any]]:
|
||||
"""Get all backups from the API."""
|
||||
access_token = self._cura_api.account.accessToken
|
||||
if not access_token:
|
||||
Logger.log("w", "Could not get access token.")
|
||||
return []
|
||||
|
||||
backup_list_request = requests.get(self.GET_BACKUPS_URL, headers={
|
||||
backup_list_request = requests.get(self.BACKUP_URL, headers = {
|
||||
"Authorization": "Bearer {}".format(access_token)
|
||||
})
|
||||
if backup_list_request.status_code > 299:
|
||||
|
||||
# HTTP status 300s mean redirection. 400s and 500s are errors.
|
||||
# Technically 300s are not errors, but the use case here relies on "requests" to handle redirects automatically.
|
||||
if backup_list_request.status_code >= 300:
|
||||
Logger.log("w", "Could not get backups list from remote: %s", backup_list_request.text)
|
||||
Message(Settings.translatable_messages["get_backups_error"], title = Settings.MESSAGE_TITLE,
|
||||
lifetime = 10).show()
|
||||
Message(catalog.i18nc("@info:backup_status", "There was an error listing your backups."), title = catalog.i18nc("@info:title", "Backup")).show()
|
||||
return []
|
||||
return backup_list_request.json()["data"]
|
||||
|
||||
def createBackup(self) -> None:
|
||||
"""Create a backup and upload it to CuraDrive cloud storage."""
|
||||
self.onCreatingStateChanged.emit(is_creating=True)
|
||||
self.creatingStateChanged.emit(is_creating = True)
|
||||
|
||||
# Create the backup.
|
||||
backup_zip_file, backup_meta_data = self._cura_api.backups.createBackup()
|
||||
if not backup_zip_file or not backup_meta_data:
|
||||
self.onCreatingStateChanged.emit(is_creating=False, error_message="Could not create backup.")
|
||||
self.creatingStateChanged.emit(is_creating = False, error_message ="Could not create backup.")
|
||||
return
|
||||
|
||||
# Create an upload entry for the backup.
|
||||
|
@ -66,7 +67,7 @@ class DriveApiService:
|
|||
backup_meta_data["description"] = "{}.backup.{}.cura.zip".format(timestamp, backup_meta_data["cura_release"])
|
||||
backup_upload_url = self._requestBackupUpload(backup_meta_data, len(backup_zip_file))
|
||||
if not backup_upload_url:
|
||||
self.onCreatingStateChanged.emit(is_creating=False, error_message="Could not upload backup.")
|
||||
self.creatingStateChanged.emit(is_creating = False, error_message ="Could not upload backup.")
|
||||
return
|
||||
|
||||
# Upload the backup to storage.
|
||||
|
@ -75,35 +76,27 @@ class DriveApiService:
|
|||
upload_backup_job.start()
|
||||
|
||||
def _onUploadFinished(self, job: "UploadBackupJob") -> None:
|
||||
"""
|
||||
Callback handler for the upload job.
|
||||
:param job: The executed job.
|
||||
"""
|
||||
if job.backup_upload_error_message != "":
|
||||
# If the job contains an error message we pass it along so the UI can display it.
|
||||
self.onCreatingStateChanged.emit(is_creating=False, error_message=job.backup_upload_error_message)
|
||||
self.creatingStateChanged.emit(is_creating = False, error_message = job.backup_upload_error_message)
|
||||
else:
|
||||
self.onCreatingStateChanged.emit(is_creating=False)
|
||||
self.creatingStateChanged.emit(is_creating = False)
|
||||
|
||||
def restoreBackup(self, backup: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Restore a previously exported backup from cloud storage.
|
||||
:param backup: A dict containing an entry from the API list response.
|
||||
"""
|
||||
self.onRestoringStateChanged.emit(is_restoring=True)
|
||||
self.restoringStateChanged.emit(is_restoring = True)
|
||||
download_url = backup.get("download_url")
|
||||
if not download_url:
|
||||
# If there is no download URL, we can't restore the backup.
|
||||
return self._emitRestoreError()
|
||||
|
||||
download_package = requests.get(download_url, stream=True)
|
||||
if download_package.status_code != 200:
|
||||
download_package = requests.get(download_url, stream = True)
|
||||
if download_package.status_code >= 300:
|
||||
# Something went wrong when attempting to download the backup.
|
||||
Logger.log("w", "Could not download backup from url %s: %s", download_url, download_package.text)
|
||||
return self._emitRestoreError()
|
||||
|
||||
# We store the file in a temporary path fist to ensure integrity.
|
||||
temporary_backup_file = NamedTemporaryFile(delete=False)
|
||||
temporary_backup_file = NamedTemporaryFile(delete = False)
|
||||
with open(temporary_backup_file.name, "wb") as write_backup:
|
||||
for chunk in download_package:
|
||||
write_backup.write(chunk)
|
||||
|
@ -116,69 +109,59 @@ class DriveApiService:
|
|||
|
||||
# Tell Cura to place the backup back in the user data folder.
|
||||
with open(temporary_backup_file.name, "rb") as read_backup:
|
||||
self._cura_api.backups.restoreBackup(read_backup.read(), backup.get("data"))
|
||||
self.onRestoringStateChanged.emit(is_restoring=False)
|
||||
self._cura_api.backups.restoreBackup(read_backup.read(), backup.get("metadata", {}))
|
||||
self.restoringStateChanged.emit(is_restoring = False)
|
||||
|
||||
def _emitRestoreError(self, error_message: str = Settings.translatable_messages["backup_restore_error_message"]):
|
||||
"""Helper method for emitting a signal when restoring failed."""
|
||||
self.onRestoringStateChanged.emit(
|
||||
is_restoring=False,
|
||||
error_message=error_message
|
||||
)
|
||||
def _emitRestoreError(self) -> None:
|
||||
self.restoringStateChanged.emit(is_restoring = False,
|
||||
error_message = catalog.i18nc("@info:backup_status",
|
||||
"There was an error trying to restore your backup."))
|
||||
|
||||
# Verify the MD5 hash of a file.
|
||||
# \param file_path Full path to the file.
|
||||
# \param known_hash The known MD5 hash of the file.
|
||||
# \return: Success or not.
|
||||
@staticmethod
|
||||
def _verifyMd5Hash(file_path: str, known_hash: str) -> bool:
|
||||
"""
|
||||
Verify the MD5 hash of a file.
|
||||
:param file_path: Full path to the file.
|
||||
:param known_hash: The known MD5 hash of the file.
|
||||
:return: Success or not.
|
||||
"""
|
||||
with open(file_path, "rb") as read_backup:
|
||||
local_md5_hash = base64.b64encode(hashlib.md5(read_backup.read()).digest(), altchars=b"_-").decode("utf-8")
|
||||
local_md5_hash = base64.b64encode(hashlib.md5(read_backup.read()).digest(), altchars = b"_-").decode("utf-8")
|
||||
return known_hash == local_md5_hash
|
||||
|
||||
def deleteBackup(self, backup_id: str) -> bool:
|
||||
"""
|
||||
Delete a backup from the server by ID.
|
||||
:param backup_id: The ID of the backup to delete.
|
||||
:return: Success bool.
|
||||
"""
|
||||
access_token = self._cura_api.account.accessToken
|
||||
if not access_token:
|
||||
Logger.log("w", "Could not get access token.")
|
||||
return False
|
||||
|
||||
delete_backup = requests.delete("{}/{}".format(self.DELETE_BACKUP_URL, backup_id), headers = {
|
||||
delete_backup = requests.delete("{}/{}".format(self.BACKUP_URL, backup_id), headers = {
|
||||
"Authorization": "Bearer {}".format(access_token)
|
||||
})
|
||||
if delete_backup.status_code > 299:
|
||||
if delete_backup.status_code >= 300:
|
||||
Logger.log("w", "Could not delete backup: %s", delete_backup.text)
|
||||
return False
|
||||
return True
|
||||
|
||||
# Request a backup upload slot from the API.
|
||||
# \param backup_metadata: A dict containing some meta data about the backup.
|
||||
# \param backup_size The size of the backup file in bytes.
|
||||
# \return: The upload URL for the actual backup file if successful, otherwise None.
|
||||
def _requestBackupUpload(self, backup_metadata: Dict[str, Any], backup_size: int) -> Optional[str]:
|
||||
"""
|
||||
Request a backup upload slot from the API.
|
||||
:param backup_metadata: A dict containing some meta data about the backup.
|
||||
:param backup_size: The size of the backup file in bytes.
|
||||
:return: The upload URL for the actual backup file if successful, otherwise None.
|
||||
"""
|
||||
access_token = self._cura_api.account.accessToken
|
||||
if not access_token:
|
||||
Logger.log("w", "Could not get access token.")
|
||||
return None
|
||||
|
||||
backup_upload_request = requests.put(self.PUT_BACKUP_URL, json={
|
||||
backup_upload_request = requests.put(self.BACKUP_URL, json = {
|
||||
"data": {
|
||||
"backup_size": backup_size,
|
||||
"metadata": backup_metadata
|
||||
}
|
||||
}, headers={
|
||||
}, headers = {
|
||||
"Authorization": "Bearer {}".format(access_token)
|
||||
})
|
||||
|
||||
if backup_upload_request.status_code > 299:
|
||||
# Any status code of 300 or above indicates an error.
|
||||
if backup_upload_request.status_code >= 300:
|
||||
Logger.log("w", "Could not request backup upload: %s", backup_upload_request.text)
|
||||
return None
|
||||
|
||||
|
|
|
@ -1,22 +1,26 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
|
||||
|
||||
from UM.Extension import Extension
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
from .Settings import Settings
|
||||
from .DriveApiService import DriveApiService
|
||||
from .models.BackupListModel import BackupListModel
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
# The DivePluginExtension provides functionality to backup and restore your Cura configuration to Ultimaker's cloud.
|
||||
class DrivePluginExtension(QObject, Extension):
|
||||
"""
|
||||
The DivePluginExtension provides functionality to backup and restore your Cura configuration to Ultimaker's cloud.
|
||||
"""
|
||||
|
||||
# Signal emitted when the list of backups changed.
|
||||
backupsChanged = pyqtSignal()
|
||||
|
@ -32,170 +36,127 @@ class DrivePluginExtension(QObject, Extension):
|
|||
|
||||
DATE_FORMAT = "%d/%m/%Y %H:%M:%S"
|
||||
|
||||
def __init__(self, application):
|
||||
super(DrivePluginExtension, self).__init__()
|
||||
|
||||
# Re-usable instance of application.
|
||||
self._application = application
|
||||
def __init__(self) -> None:
|
||||
QObject.__init__(self, None)
|
||||
Extension.__init__(self)
|
||||
|
||||
# Local data caching for the UI.
|
||||
self._drive_window = None # type: Optional[QObject]
|
||||
self._backups_list_model = BackupListModel()
|
||||
self._backups = [] # type: List[Dict[str, Any]]
|
||||
self._is_restoring_backup = False
|
||||
self._is_creating_backup = False
|
||||
|
||||
# Initialize services.
|
||||
self._preferences = self._application.getPreferences()
|
||||
self._cura_api = self._application.getCuraAPI()
|
||||
self._drive_api_service = DriveApiService(self._cura_api)
|
||||
preferences = CuraApplication.getInstance().getPreferences()
|
||||
self._drive_api_service = DriveApiService()
|
||||
|
||||
# Attach signals.
|
||||
self._cura_api.account.loginStateChanged.connect(self._onLoginStateChanged)
|
||||
self._drive_api_service.onRestoringStateChanged.connect(self._onRestoringStateChanged)
|
||||
self._drive_api_service.onCreatingStateChanged.connect(self._onCreatingStateChanged)
|
||||
CuraApplication.getInstance().getCuraAPI().account.loginStateChanged.connect(self._onLoginStateChanged)
|
||||
self._drive_api_service.restoringStateChanged.connect(self._onRestoringStateChanged)
|
||||
self._drive_api_service.creatingStateChanged.connect(self._onCreatingStateChanged)
|
||||
|
||||
# Register preferences.
|
||||
self._preferences.addPreference(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, False)
|
||||
self._preferences.addPreference(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY, datetime.now()
|
||||
.strftime(self.DATE_FORMAT))
|
||||
preferences.addPreference(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, False)
|
||||
preferences.addPreference(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY,
|
||||
datetime.now().strftime(self.DATE_FORMAT))
|
||||
|
||||
# Register menu items.
|
||||
self._updateMenuItems()
|
||||
# Register the menu item
|
||||
self.addMenuItem(catalog.i18nc("@item:inmenu", "Manage backups"), self.showDriveWindow)
|
||||
|
||||
# Make auto-backup on boot if required.
|
||||
self._application.engineCreatedSignal.connect(self._autoBackup)
|
||||
CuraApplication.getInstance().engineCreatedSignal.connect(self._autoBackup)
|
||||
|
||||
def showDriveWindow(self) -> None:
|
||||
"""Show the Drive UI popup window."""
|
||||
if not self._drive_window:
|
||||
self._drive_window = self.createDriveWindow()
|
||||
plugin_dir_path = CuraApplication.getInstance().getPluginRegistry().getPluginPath("CuraDrive")
|
||||
path = os.path.join(plugin_dir_path, "src", "qml", "main.qml")
|
||||
self._drive_window = CuraApplication.getInstance().createQmlComponent(path, {"CuraDrive": self})
|
||||
self.refreshBackups()
|
||||
if self._drive_window:
|
||||
self._drive_window.show()
|
||||
|
||||
def createDriveWindow(self) -> Optional["QObject"]:
|
||||
"""
|
||||
Create an instance of the Drive UI popup window.
|
||||
:return: The popup window object.
|
||||
"""
|
||||
path = os.path.join(os.path.dirname(__file__), "qml", "main.qml")
|
||||
return self._application.createQmlComponent(path, {"CuraDrive": self})
|
||||
|
||||
def _updateMenuItems(self) -> None:
|
||||
"""Update the menu items."""
|
||||
self.addMenuItem(Settings.translatable_messages["extension_menu_entry"], self.showDriveWindow)
|
||||
|
||||
def _autoBackup(self) -> None:
|
||||
"""Automatically make a backup on boot if enabled."""
|
||||
if self._preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY) and self._lastBackupTooLongAgo():
|
||||
preferences = CuraApplication.getInstance().getPreferences()
|
||||
if preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY) and self._isLastBackupTooLongAgo():
|
||||
self.createBackup()
|
||||
|
||||
def _lastBackupTooLongAgo(self) -> bool:
|
||||
"""Check if the last backup was longer than 1 day ago."""
|
||||
def _isLastBackupTooLongAgo(self) -> bool:
|
||||
current_date = datetime.now()
|
||||
last_backup_date = self._getLastBackupDate()
|
||||
date_diff = current_date - last_backup_date
|
||||
return date_diff.days > 1
|
||||
|
||||
def _getLastBackupDate(self) -> "datetime":
|
||||
"""Get the last backup date as datetime object."""
|
||||
last_backup_date = self._preferences.getValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY)
|
||||
preferences = CuraApplication.getInstance().getPreferences()
|
||||
last_backup_date = preferences.getValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY)
|
||||
return datetime.strptime(last_backup_date, self.DATE_FORMAT)
|
||||
|
||||
def _storeBackupDate(self) -> None:
|
||||
"""Store the current date as last backup date."""
|
||||
backup_date = datetime.now().strftime(self.DATE_FORMAT)
|
||||
self._preferences.setValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY, backup_date)
|
||||
preferences = CuraApplication.getInstance().getPreferences()
|
||||
preferences.setValue(Settings.AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY, backup_date)
|
||||
|
||||
def _onLoginStateChanged(self, logged_in: bool = False) -> None:
|
||||
"""Callback handler for changes in the login state."""
|
||||
if logged_in:
|
||||
self.refreshBackups()
|
||||
|
||||
def _onRestoringStateChanged(self, is_restoring: bool = False, error_message: str = None) -> None:
|
||||
"""Callback handler for changes in the restoring state."""
|
||||
self._is_restoring_backup = is_restoring
|
||||
self.restoringStateChanged.emit()
|
||||
if error_message:
|
||||
Message(error_message, title = Settings.MESSAGE_TITLE, lifetime = 5).show()
|
||||
Message(error_message, title = catalog.i18nc("@info:title", "Backup")).show()
|
||||
|
||||
def _onCreatingStateChanged(self, is_creating: bool = False, error_message: str = None) -> None:
|
||||
"""Callback handler for changes in the creation state."""
|
||||
self._is_creating_backup = is_creating
|
||||
self.creatingStateChanged.emit()
|
||||
if error_message:
|
||||
Message(error_message, title = Settings.MESSAGE_TITLE, lifetime = 5).show()
|
||||
Message(error_message, title = catalog.i18nc("@info:title", "Backup")).show()
|
||||
else:
|
||||
self._storeBackupDate()
|
||||
if not is_creating:
|
||||
if not is_creating and not error_message:
|
||||
# We've finished creating a new backup, to the list has to be updated.
|
||||
self.refreshBackups()
|
||||
|
||||
@pyqtSlot(bool, name = "toggleAutoBackup")
|
||||
def toggleAutoBackup(self, enabled: bool) -> None:
|
||||
"""Enable or disable the auto-backup feature."""
|
||||
self._preferences.setValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, enabled)
|
||||
self.preferencesChanged.emit()
|
||||
preferences = CuraApplication.getInstance().getPreferences()
|
||||
preferences.setValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY, enabled)
|
||||
|
||||
@pyqtProperty(bool, notify = preferencesChanged)
|
||||
def autoBackupEnabled(self) -> bool:
|
||||
"""Check if auto-backup is enabled or not."""
|
||||
return bool(self._preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY))
|
||||
preferences = CuraApplication.getInstance().getPreferences()
|
||||
return bool(preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY))
|
||||
|
||||
@pyqtProperty(QObject, notify = backupsChanged)
|
||||
def backups(self) -> BackupListModel:
|
||||
"""
|
||||
Get a list of the backups.
|
||||
:return: The backups as Qt List Model.
|
||||
"""
|
||||
return self._backups_list_model
|
||||
@pyqtProperty("QVariantList", notify = backupsChanged)
|
||||
def backups(self) -> List[Dict[str, Any]]:
|
||||
return self._backups
|
||||
|
||||
@pyqtSlot(name = "refreshBackups")
|
||||
def refreshBackups(self) -> None:
|
||||
"""
|
||||
Forcefully refresh the backups list.
|
||||
"""
|
||||
self._backups_list_model.loadBackups(self._drive_api_service.getBackups())
|
||||
self._backups = self._drive_api_service.getBackups()
|
||||
self.backupsChanged.emit()
|
||||
|
||||
@pyqtProperty(bool, notify = restoringStateChanged)
|
||||
def isRestoringBackup(self) -> bool:
|
||||
"""
|
||||
Get the current restoring state.
|
||||
:return: Boolean if we are restoring or not.
|
||||
"""
|
||||
return self._is_restoring_backup
|
||||
|
||||
@pyqtProperty(bool, notify = creatingStateChanged)
|
||||
def isCreatingBackup(self) -> bool:
|
||||
"""
|
||||
Get the current creating state.
|
||||
:return: Boolean if we are creating or not.
|
||||
"""
|
||||
return self._is_creating_backup
|
||||
|
||||
@pyqtSlot(str, name = "restoreBackup")
|
||||
def restoreBackup(self, backup_id: str) -> None:
|
||||
"""
|
||||
Download and restore a backup by ID.
|
||||
:param backup_id: The ID of the backup.
|
||||
"""
|
||||
index = self._backups_list_model.find("backup_id", backup_id)
|
||||
backup = self._backups_list_model.getItem(index)
|
||||
for backup in self._backups:
|
||||
if backup.get("backup_id") == backup_id:
|
||||
self._drive_api_service.restoreBackup(backup)
|
||||
return
|
||||
Logger.log("w", "Unable to find backup with the ID %s", backup_id)
|
||||
|
||||
@pyqtSlot(name = "createBackup")
|
||||
def createBackup(self) -> None:
|
||||
"""
|
||||
Create a new backup.
|
||||
"""
|
||||
self._drive_api_service.createBackup()
|
||||
|
||||
@pyqtSlot(str, name = "deleteBackup")
|
||||
def deleteBackup(self, backup_id: str) -> None:
|
||||
"""
|
||||
Delete a backup by ID.
|
||||
:param backup_id: The ID of the backup.
|
||||
"""
|
||||
self._drive_api_service.deleteBackup(backup_id)
|
||||
self.refreshBackups()
|
||||
|
|
|
@ -1,37 +1,13 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
from UM import i18nCatalog
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from cura import CuraConstants
|
||||
from cura import UltimakerCloudAuthentication
|
||||
|
||||
|
||||
class Settings:
|
||||
"""
|
||||
Keeps the application settings.
|
||||
"""
|
||||
# Keeps the plugin settings.
|
||||
DRIVE_API_VERSION = 1
|
||||
DRIVE_API_URL = "{}/cura-drive/v{}".format(CuraConstants.CuraCloudAPIRoot, str(DRIVE_API_VERSION))
|
||||
DRIVE_API_URL = "{}/cura-drive/v{}".format(UltimakerCloudAuthentication.CuraCloudAPIRoot, str(DRIVE_API_VERSION))
|
||||
|
||||
AUTO_BACKUP_ENABLED_PREFERENCE_KEY = "cura_drive/auto_backup_enabled"
|
||||
AUTO_BACKUP_LAST_DATE_PREFERENCE_KEY = "cura_drive/auto_backup_date"
|
||||
|
||||
I18N_CATALOG_ID = "cura"
|
||||
I18N_CATALOG = i18nCatalog(I18N_CATALOG_ID)
|
||||
|
||||
MESSAGE_TITLE = I18N_CATALOG.i18nc("@info:title", "Backups"),
|
||||
|
||||
# Translatable messages for the entire plugin.
|
||||
translatable_messages = {
|
||||
|
||||
# Menu items.
|
||||
"extension_menu_entry": I18N_CATALOG.i18nc("@item:inmenu", "Manage backups"),
|
||||
|
||||
# Notification messages.
|
||||
"backup_failed": I18N_CATALOG.i18nc("@info:backup_status", "There was an error while creating your backup."),
|
||||
"uploading_backup": I18N_CATALOG.i18nc("@info:backup_status", "Uploading your backup..."),
|
||||
"uploading_backup_success": I18N_CATALOG.i18nc("@info:backup_status", "Your backup has finished uploading."),
|
||||
"uploading_backup_error": I18N_CATALOG.i18nc("@info:backup_status",
|
||||
"There was an error while uploading your backup."),
|
||||
"get_backups_error": I18N_CATALOG.i18nc("@info:backup_status", "There was an error listing your backups."),
|
||||
"backup_restore_error_message": I18N_CATALOG.i18nc("@info:backup_status",
|
||||
"There was an error trying to restore your backup.")
|
||||
}
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import requests
|
||||
|
||||
from UM.Job import Job
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
|
||||
from .Settings import Settings
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
class UploadBackupJob(Job):
|
||||
"""
|
||||
This job is responsible for uploading the backup file to cloud storage.
|
||||
As it can take longer than some other tasks, we schedule this using a Cura Job.
|
||||
"""
|
||||
MESSAGE_TITLE = catalog.i18nc("@info:title", "Backups")
|
||||
|
||||
# This job is responsible for uploading the backup file to cloud storage.
|
||||
# As it can take longer than some other tasks, we schedule this using a Cura Job.
|
||||
def __init__(self, signed_upload_url: str, backup_zip: bytes) -> None:
|
||||
super().__init__()
|
||||
self._signed_upload_url = signed_upload_url
|
||||
|
@ -22,18 +24,18 @@ class UploadBackupJob(Job):
|
|||
self.backup_upload_error_message = ""
|
||||
|
||||
def run(self) -> None:
|
||||
Message(Settings.translatable_messages["uploading_backup"], title = Settings.MESSAGE_TITLE,
|
||||
lifetime = 10).show()
|
||||
upload_message = Message(catalog.i18nc("@info:backup_status", "Uploading your backup..."), title = self.MESSAGE_TITLE, progress = -1)
|
||||
upload_message.show()
|
||||
|
||||
backup_upload = requests.put(self._signed_upload_url, data = self._backup_zip)
|
||||
if backup_upload.status_code not in (200, 201):
|
||||
upload_message.hide()
|
||||
|
||||
if backup_upload.status_code >= 300:
|
||||
self.backup_upload_error_message = backup_upload.text
|
||||
Logger.log("w", "Could not upload backup file: %s", backup_upload.text)
|
||||
Message(Settings.translatable_messages["uploading_backup_error"], title = Settings.MESSAGE_TITLE,
|
||||
lifetime = 10).show()
|
||||
Message(catalog.i18nc("@info:backup_status", "There was an error while uploading your backup."), title = self.MESSAGE_TITLE).show()
|
||||
else:
|
||||
self._upload_success = True
|
||||
Message(Settings.translatable_messages["uploading_backup_success"], title = Settings.MESSAGE_TITLE,
|
||||
lifetime = 10).show()
|
||||
Message(catalog.i18nc("@info:backup_status", "Your backup has finished uploading."), title = self.MESSAGE_TITLE).show()
|
||||
|
||||
self.finished.emit(self)
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
from typing import Any, List, Dict
|
||||
|
||||
from UM.Qt.ListModel import ListModel
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
|
||||
class BackupListModel(ListModel):
|
||||
"""
|
||||
The BackupListModel transforms the backups data that came from the server so it can be served to the Qt UI.
|
||||
"""
|
||||
|
||||
def __init__(self, parent = None) -> None:
|
||||
super().__init__(parent)
|
||||
self.addRoleName(Qt.UserRole + 1, "backup_id")
|
||||
self.addRoleName(Qt.UserRole + 2, "download_url")
|
||||
self.addRoleName(Qt.UserRole + 3, "generated_time")
|
||||
self.addRoleName(Qt.UserRole + 4, "md5_hash")
|
||||
self.addRoleName(Qt.UserRole + 5, "data")
|
||||
|
||||
def loadBackups(self, data: List[Dict[str, Any]]) -> None:
|
||||
"""
|
||||
Populate the model with server data.
|
||||
:param data:
|
||||
"""
|
||||
items = []
|
||||
for backup in data:
|
||||
# We do this loop because we only want to append these specific fields.
|
||||
# Without this, ListModel will break.
|
||||
items.append({
|
||||
"backup_id": backup["backup_id"],
|
||||
"download_url": backup["download_url"],
|
||||
"generated_time": backup["generated_time"],
|
||||
"md5_hash": backup["md5_hash"],
|
||||
"data": backup["metadata"]
|
||||
})
|
||||
self.setItems(items)
|
|
@ -1,67 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
import UM 1.1 as UM
|
||||
|
||||
Button
|
||||
{
|
||||
id: button
|
||||
property alias cursorShape: mouseArea.cursorShape
|
||||
property var iconSource: ""
|
||||
property var busy: false
|
||||
property var color: UM.Theme.getColor("primary")
|
||||
property var hoverColor: UM.Theme.getColor("primary_hover")
|
||||
property var disabledColor: color
|
||||
property var textColor: UM.Theme.getColor("button_text")
|
||||
property var textHoverColor: UM.Theme.getColor("button_text_hover")
|
||||
property var textDisabledColor: textColor
|
||||
property var textFont: UM.Theme.getFont("action_button")
|
||||
|
||||
contentItem: RowLayout
|
||||
{
|
||||
Icon
|
||||
{
|
||||
id: buttonIcon
|
||||
iconSource: button.iconSource
|
||||
width: 16 * screenScaleFactor
|
||||
color: button.hovered ? button.textHoverColor : button.textColor
|
||||
visible: button.iconSource != "" && !loader.visible
|
||||
}
|
||||
|
||||
Icon
|
||||
{
|
||||
id: loader
|
||||
iconSource: "../images/loading.gif"
|
||||
width: 16 * screenScaleFactor
|
||||
color: button.hovered ? button.textHoverColor : button.textColor
|
||||
visible: button.busy
|
||||
animated: true
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: buttonText
|
||||
text: button.text
|
||||
color: button.enabled ? (button.hovered ? button.textHoverColor : button.textColor): button.textDisabledColor
|
||||
font: button.textFont
|
||||
visible: button.text != ""
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle
|
||||
{
|
||||
color: button.enabled ? (button.hovered ? button.hoverColor : button.color) : button.disabledColor
|
||||
}
|
||||
|
||||
MouseArea
|
||||
{
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
onPressed: mouse.accepted = false
|
||||
hoverEnabled: true
|
||||
cursorShape: button.enabled ? (hovered ? Qt.PointingHandCursor : Qt.ArrowCursor) : Qt.ForbiddenCursor
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
import UM 1.3 as UM
|
||||
|
||||
CheckBox
|
||||
{
|
||||
id: checkbox
|
||||
hoverEnabled: true
|
||||
|
||||
property var label: ""
|
||||
|
||||
indicator: Rectangle {
|
||||
implicitWidth: 30 * screenScaleFactor
|
||||
implicitHeight: 30 * screenScaleFactor
|
||||
x: 0
|
||||
y: Math.round(parent.height / 2 - height / 2)
|
||||
color: UM.Theme.getColor("sidebar")
|
||||
border.color: UM.Theme.getColor("text")
|
||||
|
||||
Rectangle {
|
||||
width: 14 * screenScaleFactor
|
||||
height: 14 * screenScaleFactor
|
||||
x: 8 * screenScaleFactor
|
||||
y: 8 * screenScaleFactor
|
||||
color: UM.Theme.getColor("primary")
|
||||
visible: checkbox.checked
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Label {
|
||||
anchors
|
||||
{
|
||||
left: checkbox.indicator.right
|
||||
leftMargin: 5 * screenScaleFactor
|
||||
}
|
||||
text: catalog.i18nc("@checkbox:description", "Auto Backup")
|
||||
color: UM.Theme.getColor("text")
|
||||
renderType: Text.NativeRendering
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
ActionToolTip
|
||||
{
|
||||
text: checkbox.label
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
import UM 1.1 as UM
|
||||
|
||||
ToolTip
|
||||
{
|
||||
id: tooltip
|
||||
visible: parent.hovered
|
||||
opacity: 0.9
|
||||
delay: 500
|
||||
|
||||
background: Rectangle
|
||||
{
|
||||
color: UM.Theme.getColor("sidebar")
|
||||
border.color: UM.Theme.getColor("primary")
|
||||
border.width: 1 * screenScaleFactor
|
||||
}
|
||||
|
||||
contentItem: Label
|
||||
{
|
||||
text: tooltip.text
|
||||
color: UM.Theme.getColor("text")
|
||||
font: UM.Theme.getFont("very_small")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
|
@ -1,15 +1,20 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
import UM 1.1 as UM
|
||||
|
||||
ListView
|
||||
ScrollView
|
||||
{
|
||||
property alias model: backupList.model
|
||||
width: parent.width
|
||||
ListView
|
||||
{
|
||||
id: backupList
|
||||
width: parent.width
|
||||
clip: true
|
||||
delegate: Item
|
||||
{
|
||||
width: parent.width
|
||||
|
@ -21,11 +26,12 @@ ListView
|
|||
width: parent.width
|
||||
}
|
||||
|
||||
Divider
|
||||
Rectangle
|
||||
{
|
||||
width: parent.width
|
||||
anchors.top: backupListItem.bottom
|
||||
id: divider
|
||||
color: UM.Theme.getColor("lining")
|
||||
height: UM.Theme.getSize("default_lining").height
|
||||
}
|
||||
}
|
||||
}
|
||||
ScrollBar.vertical: RightSideScrollBar {}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
import "../components"
|
||||
|
||||
|
@ -13,30 +16,31 @@ RowLayout
|
|||
width: parent.width
|
||||
property bool showInfoButton: false
|
||||
|
||||
ActionButton
|
||||
Cura.PrimaryButton
|
||||
{
|
||||
id: infoButton
|
||||
text: catalog.i18nc("@button", "Want more?")
|
||||
iconSource: "../images/info.svg"
|
||||
iconSource: UM.Theme.getIcon("info")
|
||||
onClicked: Qt.openUrlExternally("https://goo.gl/forms/QACEP8pP3RV60QYG2")
|
||||
visible: backupListFooter.showInfoButton
|
||||
}
|
||||
|
||||
ActionButton
|
||||
Cura.PrimaryButton
|
||||
{
|
||||
id: createBackupButton
|
||||
text: catalog.i18nc("@button", "Backup Now")
|
||||
iconSource: "../images/backup.svg"
|
||||
enabled: !CuraDrive.isCreatingBackup && !CuraDrive.isRestoringBackup
|
||||
iconSource: UM.Theme.getIcon("plus")
|
||||
enabled: !CuraDrive.isCreatingBackup && !CuraDrive.isRestoringBackup && !backupListFooter.showInfoButton
|
||||
onClicked: CuraDrive.createBackup()
|
||||
busy: CuraDrive.isCreatingBackup
|
||||
}
|
||||
|
||||
ActionCheckBox
|
||||
Cura.CheckBoxWithTooltip
|
||||
{
|
||||
id: autoBackupEnabled
|
||||
checked: CuraDrive.autoBackupEnabled
|
||||
onClicked: CuraDrive.toggleAutoBackup(autoBackupEnabled.checked)
|
||||
label: catalog.i18nc("@checkbox:description", "Automatically create a backup each day that Cura is started.")
|
||||
text: catalog.i18nc("@checkbox:description", "Auto Backup")
|
||||
tooltip: catalog.i18nc("@checkbox:description", "Automatically create a backup each day that Cura is started.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Dialogs 1.1
|
||||
|
||||
import UM 1.1 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Item
|
||||
{
|
||||
|
@ -25,60 +28,58 @@ Item
|
|||
RowLayout
|
||||
{
|
||||
id: dataRow
|
||||
spacing: UM.Theme.getSize("default_margin").width * 2
|
||||
spacing: UM.Theme.getSize("wide_margin").width
|
||||
width: parent.width
|
||||
height: 50 * screenScaleFactor
|
||||
|
||||
ActionButton
|
||||
UM.SimpleButton
|
||||
{
|
||||
color: "transparent"
|
||||
hoverColor: "transparent"
|
||||
textColor: UM.Theme.getColor("text")
|
||||
textHoverColor: UM.Theme.getColor("primary")
|
||||
iconSource: "../images/info.svg"
|
||||
width: UM.Theme.getSize("section_icon").width
|
||||
height: UM.Theme.getSize("section_icon").height
|
||||
color: UM.Theme.getColor("small_button_text")
|
||||
hoverColor: UM.Theme.getColor("small_button_text_hover")
|
||||
iconSource: UM.Theme.getIcon("info")
|
||||
onClicked: backupListItem.showDetails = !backupListItem.showDetails
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
text: new Date(model["generated_time"]).toLocaleString(UM.Preferences.getValue("general/language"))
|
||||
text: new Date(modelData.generated_time).toLocaleString(UM.Preferences.getValue("general/language"))
|
||||
color: UM.Theme.getColor("text")
|
||||
elide: Text.ElideRight
|
||||
Layout.minimumWidth: 100 * screenScaleFactor
|
||||
Layout.maximumWidth: 500 * screenScaleFactor
|
||||
Layout.fillWidth: true
|
||||
font: UM.Theme.getFont("default")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
text: model["data"]["description"]
|
||||
text: modelData.metadata.description
|
||||
color: UM.Theme.getColor("text")
|
||||
elide: Text.ElideRight
|
||||
Layout.minimumWidth: 100 * screenScaleFactor
|
||||
Layout.maximumWidth: 500 * screenScaleFactor
|
||||
Layout.fillWidth: true
|
||||
font: UM.Theme.getFont("default")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
ActionButton
|
||||
Cura.SecondaryButton
|
||||
{
|
||||
text: catalog.i18nc("@button", "Restore")
|
||||
color: "transparent"
|
||||
hoverColor: "transparent"
|
||||
textColor: UM.Theme.getColor("text")
|
||||
textHoverColor: UM.Theme.getColor("text_link")
|
||||
enabled: !CuraDrive.isCreatingBackup && !CuraDrive.isRestoringBackup
|
||||
onClicked: confirmRestoreDialog.visible = true
|
||||
}
|
||||
|
||||
ActionButton
|
||||
UM.SimpleButton
|
||||
{
|
||||
color: "transparent"
|
||||
hoverColor: "transparent"
|
||||
textColor: UM.Theme.getColor("setting_validation_error")
|
||||
textHoverColor: UM.Theme.getColor("setting_validation_error")
|
||||
iconSource: "../images/delete.svg"
|
||||
width: UM.Theme.getSize("message_close").width
|
||||
height: UM.Theme.getSize("message_close").height
|
||||
color: UM.Theme.getColor("small_button_text")
|
||||
hoverColor: UM.Theme.getColor("small_button_text_hover")
|
||||
iconSource: UM.Theme.getIcon("cross1")
|
||||
onClicked: confirmDeleteDialog.visible = true
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +87,7 @@ Item
|
|||
BackupListItemDetails
|
||||
{
|
||||
id: backupDetails
|
||||
backupDetailsData: model
|
||||
backupDetailsData: modelData
|
||||
width: parent.width
|
||||
visible: parent.showDetails
|
||||
anchors.top: dataRow.bottom
|
||||
|
@ -98,7 +99,7 @@ Item
|
|||
title: catalog.i18nc("@dialog:title", "Delete Backup")
|
||||
text: catalog.i18nc("@dialog:info", "Are you sure you want to delete this backup? This cannot be undone.")
|
||||
standardButtons: StandardButton.Yes | StandardButton.No
|
||||
onYes: CuraDrive.deleteBackup(model["backup_id"])
|
||||
onYes: CuraDrive.deleteBackup(modelData.backup_id)
|
||||
}
|
||||
|
||||
MessageDialog
|
||||
|
@ -107,6 +108,6 @@ Item
|
|||
title: catalog.i18nc("@dialog:title", "Restore Backup")
|
||||
text: catalog.i18nc("@dialog:info", "You will need to restart Cura before your backup is restored. Do you want to close Cura now?")
|
||||
standardButtons: StandardButton.Yes | StandardButton.No
|
||||
onYes: CuraDrive.restoreBackup(model["backup_id"])
|
||||
onYes: CuraDrive.restoreBackup(modelData.backup_id)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
|
@ -9,53 +11,53 @@ ColumnLayout
|
|||
{
|
||||
id: backupDetails
|
||||
width: parent.width
|
||||
spacing: 10 * screenScaleFactor
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
property var backupDetailsData
|
||||
|
||||
// Cura version
|
||||
BackupListItemDetailsRow
|
||||
{
|
||||
iconSource: "../images/cura.svg"
|
||||
iconSource: UM.Theme.getIcon("application")
|
||||
label: catalog.i18nc("@backuplist:label", "Cura Version")
|
||||
value: backupDetailsData["data"]["cura_release"]
|
||||
value: backupDetailsData.metadata.cura_release
|
||||
}
|
||||
|
||||
// Machine count.
|
||||
BackupListItemDetailsRow
|
||||
{
|
||||
iconSource: "../images/printer.svg"
|
||||
iconSource: UM.Theme.getIcon("printer_single")
|
||||
label: catalog.i18nc("@backuplist:label", "Machines")
|
||||
value: backupDetailsData["data"]["machine_count"]
|
||||
value: backupDetailsData.metadata.machine_count
|
||||
}
|
||||
|
||||
// Meterial count.
|
||||
// Material count
|
||||
BackupListItemDetailsRow
|
||||
{
|
||||
iconSource: "../images/material.svg"
|
||||
iconSource: UM.Theme.getIcon("category_material")
|
||||
label: catalog.i18nc("@backuplist:label", "Materials")
|
||||
value: backupDetailsData["data"]["material_count"]
|
||||
value: backupDetailsData.metadata.material_count
|
||||
}
|
||||
|
||||
// Meterial count.
|
||||
// Profile count.
|
||||
BackupListItemDetailsRow
|
||||
{
|
||||
iconSource: "../images/profile.svg"
|
||||
iconSource: UM.Theme.getIcon("settings")
|
||||
label: catalog.i18nc("@backuplist:label", "Profiles")
|
||||
value: backupDetailsData["data"]["profile_count"]
|
||||
value: backupDetailsData.metadata.profile_count
|
||||
}
|
||||
|
||||
// Meterial count.
|
||||
// Plugin count.
|
||||
BackupListItemDetailsRow
|
||||
{
|
||||
iconSource: "../images/plugin.svg"
|
||||
iconSource: UM.Theme.getIcon("plugin")
|
||||
label: catalog.i18nc("@backuplist:label", "Plugins")
|
||||
value: backupDetailsData["data"]["plugin_count"]
|
||||
value: backupDetailsData.metadata.plugin_count
|
||||
}
|
||||
|
||||
// Spacer.
|
||||
Item
|
||||
{
|
||||
width: parent.width
|
||||
height: 10 * screenScaleFactor
|
||||
height: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
|
@ -11,42 +13,40 @@ RowLayout
|
|||
width: parent.width
|
||||
height: 40 * screenScaleFactor
|
||||
|
||||
property var iconSource
|
||||
property var label
|
||||
property var value
|
||||
property alias iconSource: icon.source
|
||||
property alias label: detailName.text
|
||||
property alias value: detailValue.text
|
||||
|
||||
// Spacing.
|
||||
Item
|
||||
{
|
||||
width: 40 * screenScaleFactor
|
||||
}
|
||||
|
||||
Icon
|
||||
UM.RecolorImage
|
||||
{
|
||||
id: icon
|
||||
width: 18 * screenScaleFactor
|
||||
iconSource: detailsRow.iconSource
|
||||
height: width
|
||||
source: ""
|
||||
color: UM.Theme.getColor("text")
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
text: detailsRow.label
|
||||
id: detailName
|
||||
color: UM.Theme.getColor("text")
|
||||
elide: Text.ElideRight
|
||||
Layout.minimumWidth: 50 * screenScaleFactor
|
||||
Layout.maximumWidth: 100 * screenScaleFactor
|
||||
Layout.fillWidth: true
|
||||
font: UM.Theme.getFont("default")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
text: detailsRow.value
|
||||
id: detailValue
|
||||
color: UM.Theme.getColor("text")
|
||||
elide: Text.ElideRight
|
||||
Layout.minimumWidth: 50 * screenScaleFactor
|
||||
Layout.maximumWidth: 100 * screenScaleFactor
|
||||
Layout.fillWidth: true
|
||||
font: UM.Theme.getFont("default")
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
import QtQuick 2.7
|
||||
|
||||
import UM 1.3 as UM
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: divider
|
||||
color: UM.Theme.getColor("lining")
|
||||
height: UM.Theme.getSize("default_lining").height
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
Item
|
||||
{
|
||||
id: icon
|
||||
width: parent.height
|
||||
height: width
|
||||
property var color: "transparent"
|
||||
property var iconSource
|
||||
property bool animated: false
|
||||
|
||||
Image
|
||||
{
|
||||
id: iconImage
|
||||
width: parent.height
|
||||
height: width
|
||||
smooth: true
|
||||
source: icon.iconSource
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
antialiasing: true
|
||||
visible: !icon.animated
|
||||
}
|
||||
|
||||
AnimatedImage
|
||||
{
|
||||
id: animatedIconImage
|
||||
width: parent.height
|
||||
height: width
|
||||
smooth: true
|
||||
antialiasing: true
|
||||
source: "../images/loading.gif"
|
||||
visible: icon.animated
|
||||
}
|
||||
|
||||
ColorOverlay
|
||||
{
|
||||
anchors.fill: iconImage
|
||||
source: iconImage
|
||||
color: icon.color
|
||||
antialiasing: true
|
||||
visible: !icon.animated
|
||||
}
|
||||
|
||||
ColorOverlay
|
||||
{
|
||||
anchors.fill: animatedIconImage
|
||||
source: animatedIconImage
|
||||
color: icon.color
|
||||
antialiasing: true
|
||||
visible: icon.animated
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
ScrollBar
|
||||
{
|
||||
active: true
|
||||
size: parent.height
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
}
|
Before Width: | Height: | Size: 3 KiB |
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="1024px" height="1183px" viewBox="0 0 1024 1183" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 39.1 (31720) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Polygon 18</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Home" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" fill-opacity="0.2">
|
||||
<g id="1440px-home" transform="translate(-86.000000, -30.000000)" fill="#D1D9DB">
|
||||
<polygon id="Polygon-18" points="598 30 1110 325.603338 1110 916.810013 598 1212.41335 86 916.810013 86 325.603338"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 734 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="white">
|
||||
<path d="M16.5 11.3h-5.2v5.2H8.7v-5.2H3.5V8.7h5.2V3.5h2.6v5.2h5.2z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 154 B |
|
@ -1,7 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
|
||||
<switch>
|
||||
<g>
|
||||
<path d="M11.07 3L3 11.071V27h15.931L27 18.93V3H11.07zm10.175 8.235h-6.071c-2.02.013-3.016 1.414-3.016 3.115 0 1.702.996 3.125 3.016 3.136h6.071v3.433h-6.071c-3.996 0-6.419-2.743-6.419-6.568 0-3.826 2.423-6.548 6.419-6.548h6.071v3.432z"/>
|
||||
</g>
|
||||
</switch>
|
||||
</svg>
|
Before Width: | Height: | Size: 370 B |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 13 KiB |
|
@ -1,7 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 13" fill="red">
|
||||
<switch>
|
||||
<g>
|
||||
<path d="M13 2.23L8.73 6.5 13 10.77l-2.135 2.134-4.269-4.269-4.27 4.269L.191 10.77l4.27-4.27-4.27-4.27L2.326.096l4.27 4.269L10.865.096z"/>
|
||||
</g>
|
||||
</switch>
|
||||
</svg>
|
Before Width: | Height: | Size: 281 B |
|
@ -1,7 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
|
||||
<switch>
|
||||
<g>
|
||||
<path d="M21.718 10.969V7.758H8.451L7.014 5.222h-5.83v19.436h21.211l6.422-13.69-7.099.001zm-1.098 0H8.958L3.043 23.56h-.761V6.321h4.056l1.437 2.535H20.62v2.113z"/>
|
||||
</g>
|
||||
</switch>
|
||||
</svg>
|
Before Width: | Height: | Size: 295 B |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10" fill="white">
|
||||
<path d="m 8.5727081,5.62578 v 2.97725 q 0,0.16127 -0.1178498,0.27912 Q 8.3370085,9 8.1757405,9 H 5.7939349 V 6.61819 H 4.2060646 V 9 H 1.824259 Q 1.6629909,9 1.5451412,8.88215 1.4272914,8.7643 1.4272914,8.60303 V 5.62578 q 0,-0.006 0.0031,-0.0186 0.0031,-0.0124 0.0031,-0.0186 L 4.9999998,2.64852 8.5665054,5.58856 q 0.0062,0.0124 0.0062,0.0372 z M 9.955892,5.19779 9.5713297,5.65679 q -0.049621,0.0558 -0.130255,0.0682 h -0.018608 q -0.080634,0 -0.130255,-0.0434 L 4.9999998,2.10269 0.70778771,5.6816 Q 0.63335631,5.7312 0.55892486,5.725 0.47829087,5.7126 0.42866987,5.6568 L 0.04410752,5.1978 Q -0.00551343,5.1358 6.8917799e-4,5.05204 0.00689178,4.96834 0.06891799,4.91869 L 4.5286008,1.20331 q 0.1984838,-0.16127 0.471399,-0.16127 0.2729153,0 0.471399,0.16127 L 6.9848377,2.46864 V 1.25913 q 0,-0.0868 0.055824,-0.14266 0.055824,-0.0558 0.1426602,-0.0558 h 1.1909028 q 0.086837,0 0.1426602,0.0558 0.055824,0.0558 0.055824,0.14266 V 3.7898 l 1.3583734,1.12888 q 0.062026,0.0496 0.068229,0.13335 0.0062,0.0837 -0.043418,0.14576 z" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.5 15C11.641 15 15 11.643 15 7.5 15 3.358 11.641 0 7.5 0 3.358 0 0 3.358 0 7.5 0 11.643 3.358 15 7.5 15ZM8.6 12.369L6.472 12.369 6.472 4.57 8.6 4.57 8.6 12.369ZM7.541 1.514C8.313 1.514 8.697 1.861 8.697 2.553 8.697 2.885 8.6 3.141 8.409 3.325 8.216 3.509 7.926 3.601 7.541 3.601 6.767 3.601 6.382 3.252 6.382 2.553 6.382 1.861 6.767 1.514 7.541 1.514Z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 499 B |
Before Width: | Height: | Size: 1.6 KiB |
|
@ -1,7 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
|
||||
<switch>
|
||||
<g>
|
||||
<path d="M2.995 1H5.67v24H2.995zm21.33 0H27v24h-2.675zM8.992 3.284c-.368 0-.669.224-.669.5v18.433c0 .276.3.5.669.5.369 0 .669-.224.669-.5V3.784c0-.276-.299-.5-.669-.5m4.003 0c-.368 0-.669.224-.669.5v18.433c0 .276.3.5.669.5.371 0 .669-.224.669-.5V3.784c0-.276-.298-.5-.669-.5m4.004 0c-.371 0-.669.224-.669.5v24.451c0 .277.298.5.669.5.368 0 .669-.223.669-.5V3.784c0-.276-.301-.5-.669-.5m4.003 0c-.368 0-.669.224-.669.5v18.433c0 .276.3.5.669.5.37 0 .669-.224.669-.5V3.784c-.001-.276-.3-.5-.669-.5"/>
|
||||
</g>
|
||||
</switch>
|
||||
</svg>
|
Before Width: | Height: | Size: 628 B |
|
@ -1,7 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
|
||||
<switch>
|
||||
<g>
|
||||
<path d="M22.32 19.715l-6.96-6.96 6.96-6.958v4.541H27V3H10.999L3 11.001V27h15.998L27 19.001v-3.828h-4.68z"/>
|
||||
</g>
|
||||
</switch>
|
||||
</svg>
|
Before Width: | Height: | Size: 240 B |
Before Width: | Height: | Size: 8.1 KiB |
|
@ -1,14 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 49 (51002) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>icn_singlePrinter</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Visual" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Printer-status-icon" transform="translate(-217.000000, -176.000000)" fill="#000000">
|
||||
<g id="icn_singlePrinter" transform="translate(217.000000, 176.000000)">
|
||||
<path d="M2,13 L14,13 L14,15 L2,15 L2,13 L2,13 Z M2,3 L14,3 L14,5 L2,5 L2,3 L2,3 Z M0,14 L2,14 L2,16 L0,16 L0,14 L0,14 Z M14,14 L16,14 L16,16 L14,16 L14,14 L14,14 Z M0,0 L2,0 L2,14 L0,14 L0,0 L0,0 Z M14,0 L16,0 L16,14 L14,14 L14,0 L14,0 Z M6,5 L10,5 L10,6 L6,6 L6,5 L6,5 Z M7,6 L9,6 L9,8 L7,8 L7,6 L7,6 Z M2,0 L14,0 L14,2 L2,2 L2,0 L2,0 Z M3,12 L13,12 L13,13 L3,13 L3,12 L3,12 Z" id="Rectangle-185-Copy-5-Copy-4"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1,3 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 13">
|
||||
<path d="M12.96 5.778c-.021-.182-.234-.32-.419-.32-.595 0-1.124-.35-1.346-.89a1.45 1.45 0 0 1 .364-1.608.361.361 0 0 0 .04-.49 6.43 6.43 0 0 0-1.03-1.04.362.362 0 0 0-.494.04c-.388.43-1.084.589-1.621.364A1.444 1.444 0 0 1 7.576.423a.36.36 0 0 0-.32-.38A6.485 6.485 0 0 0 5.795.04a.362.362 0 0 0-.321.372 1.447 1.447 0 0 1-.89 1.387c-.532.217-1.223.06-1.61-.366a.362.362 0 0 0-.49-.041A6.46 6.46 0 0 0 1.43 2.43a.362.362 0 0 0 .04.493 1.44 1.44 0 0 1 .363 1.622c-.225.534-.78.879-1.415.879a.354.354 0 0 0-.375.319A6.51 6.51 0 0 0 .04 7.222c.02.183.24.32.426.32a1.426 1.426 0 0 1 1.338.89c.227.554.08 1.2-.364 1.608a.36.36 0 0 0-.04.49c.303.384.65.734 1.029 1.04.149.12.365.103.495-.04.389-.43 1.084-.589 1.62-.364.561.235.914.802.88 1.41a.36.36 0 0 0 .318.38 6.44 6.44 0 0 0 1.463.005.362.362 0 0 0 .322-.373 1.445 1.445 0 0 1 .889-1.386c.535-.218 1.223-.058 1.61.366.128.14.34.157.49.041a6.476 6.476 0 0 0 1.052-1.04.361.361 0 0 0-.039-.493 1.44 1.44 0 0 1-.364-1.622c.22-.527.755-.88 1.33-.88l.08.001a.362.362 0 0 0 .38-.319c.058-.488.058-.985.003-1.478zM6.51 8.682a2.17 2.17 0 0 1-2.168-2.168A2.17 2.17 0 0 1 6.51 4.346a2.17 2.17 0 0 1 2.168 2.168A2.17 2.17 0 0 1 6.51 8.682z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.2 KiB |
|
@ -1,7 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250">
|
||||
<switch>
|
||||
<g>
|
||||
<path d="M-.113 31.935c8.904 4.904 17.81 9.802 26.709 14.714 2.507 1.384 4.993 2.807 7.868 4.426C57.24 23.782 85.983 7.847 121.495 5.371c27.659-1.928 53.113 5.077 76.006 20.788 44.611 30.614 63.473 86.919 46.099 137.829-17.883 52.399-66.749 82.265-113.69 81.745v-36.688c25.195-1.141 46.785-10.612 63.364-30.267 11.82-14.013 18.14-30.322 19.349-48.541 2.323-34.992-19.005-68.519-51.909-81.916-32.223-13.12-70.379-4.319-93 22.01l31.263 18.198c-1.545 1.07-2.387 1.747-3.312 2.281a81656.22 81656.22 0 0 1-92.099 53.112c-1.128.65-2.448.967-3.679 1.439V31.935z"/>
|
||||
</g>
|
||||
</switch>
|
||||
</svg>
|
Before Width: | Height: | Size: 691 B |
|
@ -1,4 +1,6 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Window 2.2
|
||||
|
@ -14,18 +16,18 @@ Window
|
|||
id: curaDriveDialog
|
||||
minimumWidth: Math.round(UM.Theme.getSize("modal_window_minimum").width)
|
||||
minimumHeight: Math.round(UM.Theme.getSize("modal_window_minimum").height)
|
||||
maximumWidth: minimumWidth * 1.2
|
||||
maximumHeight: minimumHeight * 1.2
|
||||
maximumWidth: Math.round(minimumWidth * 1.2)
|
||||
maximumHeight: Math.round(minimumHeight * 1.2)
|
||||
width: minimumWidth
|
||||
height: minimumHeight
|
||||
color: UM.Theme.getColor("sidebar")
|
||||
color: UM.Theme.getColor("main_background")
|
||||
title: catalog.i18nc("@title:window", "Cura Backups")
|
||||
|
||||
// Globally available.
|
||||
UM.I18nCatalog
|
||||
{
|
||||
id: catalog
|
||||
name: "cura_drive"
|
||||
name: "cura"
|
||||
}
|
||||
|
||||
WelcomePage
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
|
@ -12,11 +14,11 @@ Item
|
|||
{
|
||||
id: backupsPage
|
||||
anchors.fill: parent
|
||||
anchors.margins: UM.Theme.getSize("default_margin").width * 3
|
||||
anchors.margins: UM.Theme.getSize("wide_margin").width
|
||||
|
||||
ColumnLayout
|
||||
{
|
||||
spacing: UM.Theme.getSize("default_margin").height * 2
|
||||
spacing: UM.Theme.getSize("wide_margin").height
|
||||
width: parent.width
|
||||
anchors.fill: parent
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Window 2.2
|
||||
|
@ -8,12 +10,14 @@ import Cura 1.1 as Cura
|
|||
|
||||
import "../components"
|
||||
|
||||
|
||||
Column
|
||||
{
|
||||
id: welcomePage
|
||||
spacing: UM.Theme.getSize("wide_margin").height
|
||||
width: parent.width
|
||||
topPadding: 150 * screenScaleFactor
|
||||
height: childrenRect.height
|
||||
anchors.centerIn: parent
|
||||
|
||||
Image
|
||||
{
|
||||
|
@ -38,11 +42,15 @@ Column
|
|||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
ActionButton
|
||||
Cura.PrimaryButton
|
||||
{
|
||||
id: loginButton
|
||||
onClicked: Cura.API.account.login()
|
||||
text: catalog.i18nc("@button", "Sign In")
|
||||
width: UM.Theme.getSize("account_button").width
|
||||
height: UM.Theme.getSize("account_button").height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: catalog.i18nc("@button", "Sign in")
|
||||
onClicked: Cura.API.account.login()
|
||||
fixedWidthMode: true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,8 +16,8 @@ from UM.Extension import Extension
|
|||
from UM.i18n import i18nCatalog
|
||||
from UM.Version import Version
|
||||
|
||||
import cura
|
||||
from cura import CuraConstants
|
||||
from cura import ApplicationMetadata
|
||||
from cura import UltimakerCloudAuthentication
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
from .AuthorsModel import AuthorsModel
|
||||
|
@ -31,17 +31,14 @@ i18n_catalog = i18nCatalog("cura")
|
|||
|
||||
## The Toolbox class is responsible of communicating with the server through the API
|
||||
class Toolbox(QObject, Extension):
|
||||
DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" # type: str
|
||||
DEFAULT_CLOUD_API_VERSION = 1 # type: int
|
||||
|
||||
def __init__(self, application: CuraApplication) -> None:
|
||||
super().__init__()
|
||||
|
||||
self._application = application # type: CuraApplication
|
||||
|
||||
self._sdk_version = CuraConstants.CuraSDKVersion # type: Union[str, int]
|
||||
self._cloud_api_version = CuraConstants.CuraCloudAPIVersion # type: int
|
||||
self._cloud_api_root = CuraConstants.CuraCloudAPIRoot # type: str
|
||||
self._sdk_version = ApplicationMetadata.CuraSDKVersion # type: Union[str, int]
|
||||
self._cloud_api_version = UltimakerCloudAuthentication.CuraCloudAPIVersion # type: int
|
||||
self._cloud_api_root = UltimakerCloudAuthentication.CuraCloudAPIRoot # type: str
|
||||
self._api_url = None # type: Optional[str]
|
||||
|
||||
# Network:
|
||||
|
|
|
@ -9,7 +9,7 @@ from PyQt5.QtCore import QUrl
|
|||
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
|
||||
|
||||
from UM.Logger import Logger
|
||||
from cura import CuraConstants
|
||||
from cura import UltimakerCloudAuthentication
|
||||
from cura.API import Account
|
||||
from .MeshUploader import MeshUploader
|
||||
from ..Models import BaseModel
|
||||
|
@ -30,7 +30,7 @@ CloudApiClientModel = TypeVar("Model", bound = BaseModel)
|
|||
class CloudApiClient:
|
||||
|
||||
# The cloud URL to use for this remote cluster.
|
||||
ROOT_PATH = CuraConstants.CuraCloudAPIRoot
|
||||
ROOT_PATH = UltimakerCloudAuthentication.CuraCloudAPIRoot
|
||||
CLUSTER_API_ROOT = "{}/connect/v1".format(ROOT_PATH)
|
||||
CURA_API_ROOT = "{}/cura/v1".format(ROOT_PATH)
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
"display_name": "Cura Backups",
|
||||
"description": "Backup and restore your configuration.",
|
||||
"package_version": "1.2.0",
|
||||
"sdk_version": 5,
|
||||
"sdk_version": 6,
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "UltimakerPackages",
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
import QtGraphicalEffects 1.0 // For the dropshadow
|
||||
|
||||
import UM 1.1 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
|
@ -30,6 +31,7 @@ Button
|
|||
property color outlineDisabledColor: outlineColor
|
||||
property alias shadowColor: shadow.color
|
||||
property alias shadowEnabled: shadow.visible
|
||||
property alias busy: busyIndicator.visible
|
||||
|
||||
property alias toolTipContentAlignment: tooltip.contentAlignment
|
||||
|
||||
|
@ -55,7 +57,7 @@ Button
|
|||
width: visible ? height : 0
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
color: button.hovered ? button.textHoverColor : button.textColor
|
||||
color: button.enabled ? (button.hovered ? button.textHoverColor : button.textColor) : button.textDisabledColor
|
||||
visible: source != "" && !button.isIconOnRightSide
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
@ -117,4 +119,16 @@ Button
|
|||
id: tooltip
|
||||
visible: button.hovered
|
||||
}
|
||||
|
||||
BusyIndicator
|
||||
{
|
||||
id: busyIndicator
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
width: height
|
||||
height: parent.height
|
||||
|
||||
visible: false
|
||||
}
|
||||
}
|
|
@ -194,7 +194,7 @@ Column
|
|||
shortcut: "Ctrl+P"
|
||||
onTriggered:
|
||||
{
|
||||
if (prepareButton.enabled)
|
||||
if (sliceButton.enabled)
|
||||
{
|
||||
sliceOrStopSlicing()
|
||||
}
|
||||
|
|
63
resources/qml/CheckBoxWithTooltip.qml
Normal file
|
@ -0,0 +1,63 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.1
|
||||
|
||||
import UM 1.3 as UM
|
||||
|
||||
CheckBox
|
||||
{
|
||||
id: checkbox
|
||||
hoverEnabled: true
|
||||
|
||||
property alias tooltip: tooltip.text
|
||||
|
||||
indicator: Rectangle
|
||||
{
|
||||
implicitWidth: UM.Theme.getSize("checkbox").width
|
||||
implicitHeight: UM.Theme.getSize("checkbox").height
|
||||
x: 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: UM.Theme.getColor("main_background")
|
||||
radius: UM.Theme.getSize("checkbox_radius").width
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: checkbox.hovered ? UM.Theme.getColor("checkbox_border_hover") : UM.Theme.getColor("checkbox_border")
|
||||
|
||||
UM.RecolorImage
|
||||
{
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: Math.round(parent.width / 2.5)
|
||||
height: Math.round(parent.height / 2.5)
|
||||
sourceSize.height: width
|
||||
color: UM.Theme.getColor("checkbox_mark")
|
||||
source: UM.Theme.getIcon("check")
|
||||
opacity: checkbox.checked
|
||||
Behavior on opacity { NumberAnimation { duration: 100; } }
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Label
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: checkbox.indicator.right
|
||||
leftMargin: UM.Theme.getSize("narrow_margin").width
|
||||
}
|
||||
text: checkbox.text
|
||||
color: UM.Theme.getColor("checkbox_text")
|
||||
font: UM.Theme.getFont("default")
|
||||
renderType: Text.NativeRendering
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
ToolTip
|
||||
{
|
||||
id: tooltip
|
||||
text: ""
|
||||
delay: 500
|
||||
visible: text != "" && checkbox.hovered
|
||||
}
|
||||
}
|
|
@ -15,4 +15,5 @@ ViewsSelector 1.0 ViewsSelector.qml
|
|||
ToolbarButton 1.0 ToolbarButton.qml
|
||||
SettingView 1.0 SettingView.qml
|
||||
ProfileMenu 1.0 ProfileMenu.qml
|
||||
CheckBoxWithTooltip 1.0 CheckBoxWithTooltip.qml
|
||||
ToolTip 1.0 ToolTip.qml
|
|
@ -478,7 +478,7 @@ QtObject
|
|||
color: (control.hovered || control._hovered) ? Theme.getColor("checkbox_hover") : (control.enabled ? Theme.getColor("checkbox") : Theme.getColor("checkbox_disabled"))
|
||||
Behavior on color { ColorAnimation { duration: 50; } }
|
||||
|
||||
radius: control.exclusiveGroup ? Math.round(Theme.getSize("checkbox").width / 2) : UM.Theme.getSize("checkbox_radius").width
|
||||
radius: control.exclusiveGroup ? Math.round(Theme.getSize("checkbox").width / 2) : Theme.getSize("checkbox_radius").width
|
||||
|
||||
border.width: Theme.getSize("default_lining").width
|
||||
border.color: (control.hovered || control._hovered) ? Theme.getColor("checkbox_border_hover") : Theme.getColor("checkbox_border")
|
||||
|
|