Merge branch 'dk_printables_squash'

This commit is contained in:
Lukas Matena 2024-11-11 16:13:18 +01:00
commit 96e10b487d
38 changed files with 2749 additions and 1299 deletions

View File

@ -0,0 +1,106 @@
<!doctype html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<meta content='width=device-width, initial-scale=1.0' name='viewport' />
<title>Connect-Slicer integration</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
background-color: #f6f6f6;
color: #333;
}
.reload-button {
display: inline-block;
padding: 12px 24px;
font-size: 16px;
color: black;
background-color: white;
border: 2px solid black;
border-radius: 5px;
cursor: pointer;
text-transform: uppercase;
font-weight: bold;
transition: background-color 0.3s ease, color 0.3s ease;
}
.reload-button:hover {
background-color: black;
color: white;
}
.reload-button:focus {
outline: none;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
</style>
</head>
<body>
<!-- This page is not served anywhere, only to be copy 'n' pasted into Slicer as a fallback loading screen. -->
<div id='loading-screen' style='width: 100%; height: 100%'>
<div style='position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center'>
<p>
<svg style='width: 80px'
viewBox='-250 -250 500 500' xmlns='http://www.w3.org/2000/svg'>
<style type='text/css'>
.filament {
stroke: #FF0033;
}
.spool {
stroke: #231F20;
fill: none;
}
.filament line {
stroke-linecap: round;
}
</style>
<defs>
<pattern class='spool' height='100' id='hexagon' patternTransform='scale(0.875)' patternUnits='userSpaceOnUse'
viewBox='-0.866025 -1.5 1.73205 3'
width='57.735'
>
<polygon fill='none' points='0,1 0.866025,0.5 0.866025,-0.5 0,-1 -0.866025,-0.5 -0.866025,0.5'
stroke-width='0.34' />
<line stroke-width='0.34' x1='0' x2='0' y1='1' y2='1.5'></line>
<line stroke-width='0.34' x1='0' x2='0' y1='-1' y2='-1.5'></line>
</pattern>
</defs>
<g class='filament'>
<circle fill='none' r='145' stroke-width='130'>
<!-- <animate attributeName='r' dur='6s' repeatCount='indefinite' values='145;80;145' />-->
<!-- <animate attributeName='stroke-width' dur='6s' repeatCount='indefinite' values='130;0;130' />-->
</circle>
<line stroke-width='12' x1='204' x2='204' y1='0' y2='245'>
<!-- <animate attributeName='x1' dur='6s' repeatCount='indefinite' values='204;74;204' />-->
<!-- <animate attributeName='x2' dur='6s' repeatCount='indefinite' values='204;74;204' />-->
</line>
</g>
<g class='spool'>
<circle fill='none' r='157' stroke='url(#hexagon)' stroke-width='171' />
<circle r='244' stroke-width='12' />
<circle r='71' stroke-width='18' />
<!-- <animateTransform
attributeName='transform' begin='0s'
dur='6s'
fill='freeze'
repeatCount='indefinite'
type='rotate'
values='0;540;0'
/>-->
</g>
</svg>
</p>
<p style="font-size: 24px;">Something went wrong.</p>
<button class="reload-button" onclick="window._prusaSlicer.postMessage(JSON.stringify({ action: 'RELOAD_HOME_PAGE' }))">Reload</button>
</div>
</div>
</body>
</html>

View File

@ -71,6 +71,7 @@
</g>
</svg>
</p>
<!-- <button style="margin-top: 20px; padding: 10px 20px; font-size: 16px;" onclick="window._prusaSlicer.postMessage(JSON.stringify({ action: 'RELOAD_HOME_PAGE' }))">Reload</button> -->
</div>
</div>
</body>

View File

@ -1,27 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Connection failed</title>
<style>
body {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
}
.container {
text-align: center;
}
</style>
</head>
<body>
<div class="container">
<h1>Connection failed</h1>
<p>Something went wrong.</p>
</div>
</body>
</html>

View File

@ -0,0 +1,82 @@
<!doctype html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<meta content='width=device-width, initial-scale=1.0' name='viewport' />
<title>Connect-Slicer integration</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
background-color: #f6f6f6;
color: #333;
}
</style>
</head>
<body>
<!-- This page is not served anywhere, only to be copy 'n' pasted into Slicer as a fallback loading screen. -->
<div id='loading-screen' style='width: 100%; height: 100%'>
<div style='position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center'>
<p>
<svg style='width: 80px'
viewBox='-250 -250 500 500' xmlns='http://www.w3.org/2000/svg'>
<style type='text/css'>
.filament {
stroke: #FF0033;
}
.spool {
stroke: #231F20;
fill: none;
}
.filament line {
stroke-linecap: round;
}
</style>
<defs>
<pattern class='spool' height='100' id='hexagon' patternTransform='scale(0.875)' patternUnits='userSpaceOnUse'
viewBox='-0.866025 -1.5 1.73205 3'
width='57.735'
>
<polygon fill='none' points='0,1 0.866025,0.5 0.866025,-0.5 0,-1 -0.866025,-0.5 -0.866025,0.5'
stroke-width='0.34' />
<line stroke-width='0.34' x1='0' x2='0' y1='1' y2='1.5'></line>
<line stroke-width='0.34' x1='0' x2='0' y1='-1' y2='-1.5'></line>
</pattern>
</defs>
<g class='filament'>
<circle fill='none' r='145' stroke-width='130'>
<!-- <animate attributeName='r' dur='6s' repeatCount='indefinite' values='145;80;145' />-->
<!-- <animate attributeName='stroke-width' dur='6s' repeatCount='indefinite' values='130;0;130' />-->
</circle>
<line stroke-width='12' x1='204' x2='204' y1='0' y2='245'>
<!-- <animate attributeName='x1' dur='6s' repeatCount='indefinite' values='204;74;204' />-->
<!-- <animate attributeName='x2' dur='6s' repeatCount='indefinite' values='204;74;204' />-->
</line>
</g>
<g class='spool'>
<circle fill='none' r='157' stroke='url(#hexagon)' stroke-width='171' />
<circle r='244' stroke-width='12' />
<circle r='71' stroke-width='18' />
<!-- <animateTransform
attributeName='transform' begin='0s'
dur='6s'
fill='freeze'
repeatCount='indefinite'
type='rotate'
values='0;540;0'
/>-->
</g>
</svg>
</p>
<p style="font-size: 24px;">Something went wrong.</p>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,106 @@
<!doctype html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<meta content='width=device-width, initial-scale=1.0' name='viewport' />
<title>Connect-Slicer integration</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
background-color: #f6f6f6;
color: #333;
}
.reload-button {
display: inline-block;
padding: 12px 24px;
font-size: 16px;
color: black;
background-color: white;
border: 2px solid black;
border-radius: 5px;
cursor: pointer;
text-transform: uppercase;
font-weight: bold;
transition: background-color 0.3s ease, color 0.3s ease;
}
.reload-button:hover {
background-color: black;
color: white;
}
.reload-button:focus {
outline: none;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
</style>
</head>
<body>
<!-- This page is not served anywhere, only to be copy 'n' pasted into Slicer as a fallback loading screen. -->
<div id='loading-screen' style='width: 100%; height: 100%'>
<div style='position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center'>
<p>
<svg style='width: 80px'
viewBox='-250 -250 500 500' xmlns='http://www.w3.org/2000/svg'>
<style type='text/css'>
.filament {
stroke: #FF0033;
}
.spool {
stroke: #231F20;
fill: none;
}
.filament line {
stroke-linecap: round;
}
</style>
<defs>
<pattern class='spool' height='100' id='hexagon' patternTransform='scale(0.875)' patternUnits='userSpaceOnUse'
viewBox='-0.866025 -1.5 1.73205 3'
width='57.735'
>
<polygon fill='none' points='0,1 0.866025,0.5 0.866025,-0.5 0,-1 -0.866025,-0.5 -0.866025,0.5'
stroke-width='0.34' />
<line stroke-width='0.34' x1='0' x2='0' y1='1' y2='1.5'></line>
<line stroke-width='0.34' x1='0' x2='0' y1='-1' y2='-1.5'></line>
</pattern>
</defs>
<g class='filament'>
<circle fill='none' r='145' stroke-width='130'>
<!-- <animate attributeName='r' dur='6s' repeatCount='indefinite' values='145;80;145' />-->
<!-- <animate attributeName='stroke-width' dur='6s' repeatCount='indefinite' values='130;0;130' />-->
</circle>
<line stroke-width='12' x1='204' x2='204' y1='0' y2='245'>
<!-- <animate attributeName='x1' dur='6s' repeatCount='indefinite' values='204;74;204' />-->
<!-- <animate attributeName='x2' dur='6s' repeatCount='indefinite' values='204;74;204' />-->
</line>
</g>
<g class='spool'>
<circle fill='none' r='157' stroke='url(#hexagon)' stroke-width='171' />
<circle r='244' stroke-width='12' />
<circle r='71' stroke-width='18' />
<!-- <animateTransform
attributeName='transform' begin='0s'
dur='6s'
fill='freeze'
repeatCount='indefinite'
type='rotate'
values='0;540;0'
/>-->
</g>
</svg>
</p>
<p style="font-size: 24px;">Something went wrong.</p>
<button class="reload-button" onclick="window.ExternalApp.postMessage(JSON.stringify({ event: 'reloadHomePage' }))">Reload</button>
</div>
</div>
</body>
</html>

View File

@ -62,6 +62,7 @@
</g>
</svg>
</p>
<!-- <button style="margin-top: 20px; padding: 10px 20px; font-size: 16px;" onclick="window.ExternalApp.postMessage(JSON.stringify({ event: 'reloadHomePage' }))">Reload</button> -->
</div>
</div>
</body>

View File

@ -29,6 +29,10 @@ set(SLIC3R_GUI_SOURCES
GUI/UserAccount.hpp
GUI/WebViewDialog.cpp
GUI/WebViewDialog.hpp
GUI/WebViewPanel.cpp
GUI/WebViewPanel.hpp
GUI/ConnectRequestHandler.cpp
GUI/ConnectRequestHandler.hpp
GUI/WebView.cpp
GUI/WebView.hpp
GUI/WebViewPlatformUtils.hpp

View File

@ -27,13 +27,15 @@ ConfigWizardWebViewPage::ConfigWizardWebViewPage(ConfigWizard *parent)
// Create the webview
m_browser_sizer = new wxBoxSizer(wxHORIZONTAL);
m_browser = WebView::CreateWebView(this, p_user_account->generate_login_redirect_url(), {});
m_browser = WebView::webview_new();
if (!m_browser) {
// TRN Config wizard page with a log in page.
wxStaticText* fail_text = new wxStaticText(this, wxID_ANY, _L("Failed to load a web browser. Logging in is not possible in the moment."));
append(fail_text);
return;
}
WebView::webview_create(m_browser, this, p_user_account->generate_login_redirect_url(), {"ExternalApp"});
if (logged) {
// TRN Config wizard page with a log in web.
m_text = new wxStaticText(this, wxID_ANY, format_wxstr(_L("You are logged as %1%."), p_user_account->get_username()));
@ -47,11 +49,13 @@ ConfigWizardWebViewPage::ConfigWizardWebViewPage(ConfigWizard *parent)
append(m_browser_sizer, 1, wxEXPAND);
m_browser_sizer->Show(!logged);
Layout();
// Connect the webview events
Bind(wxEVT_WEBVIEW_ERROR, &ConfigWizardWebViewPage::on_error, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_NAVIGATING, &ConfigWizardWebViewPage::on_navigation_request, this, m_browser->GetId());
Bind(wxEVT_IDLE, &ConfigWizardWebViewPage::on_idle, this);
Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &ConfigWizardWebViewPage::on_script_message, this, m_browser->GetId());
}
bool ConfigWizardWebViewPage::login_changed()
@ -113,7 +117,7 @@ void ConfigWizardWebViewPage::on_idle(wxIdleEvent &WXUNUSED(evt)) {
if (!m_vetoed && m_load_error_page) {
m_load_error_page = false;
m_browser->LoadURL(GUI::format_wxstr(
"file://%1%/web/connection_failed.html",
"file://%1%/web/other_error.html",
boost::filesystem::path(resources_dir()).generic_string()
));
}
@ -125,7 +129,7 @@ void ConfigWizardWebViewPage::on_navigation_request(wxWebViewEvent &evt)
{
wxString url = evt.GetURL();
if (url.starts_with(L"prusaslicer")) {
delete_cookies(m_browser, "https://account.prusa3d.com");
delete_cookies(m_browser, Utils::ServiceConfig::instance().account_url());
delete_cookies(m_browser, "https://accounts.google.com");
delete_cookies(m_browser, "https://appleid.apple.com");
delete_cookies(m_browser, "https://facebook.com");
@ -145,4 +149,11 @@ void ConfigWizardWebViewPage::on_navigation_request(wxWebViewEvent &evt)
}
}
}
void ConfigWizardWebViewPage::on_script_message(wxWebViewEvent& evt)
{
// only reaload button on erro page
m_browser->LoadURL(p_user_account->generate_login_redirect_url());
}
}} // namespace Slic3r::GUI

View File

@ -53,6 +53,7 @@ public:
void on_error(wxWebViewEvent &evt);
void on_navigation_request(wxWebViewEvent &evt);
void on_idle(wxIdleEvent &evt);
void on_script_message(wxWebViewEvent& evt);
void load_error_page();
// returns true if logged in - wizard needs to update repos
bool login_changed();

View File

@ -0,0 +1,135 @@
#include "ConnectRequestHandler.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/UserAccount.hpp"
#include <boost/log/trivial.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
namespace pt = boost::property_tree;
namespace Slic3r::GUI {
ConnectRequestHandler::ConnectRequestHandler()
{
m_actions["REQUEST_LOGIN"] = std::bind(&ConnectRequestHandler::on_connect_action_request_login, this, std::placeholders::_1);
m_actions["REQUEST_CONFIG"] = std::bind(&ConnectRequestHandler::on_connect_action_request_config, this, std::placeholders::_1);
m_actions["WEBAPP_READY"] = std::bind(&ConnectRequestHandler::on_connect_action_webapp_ready,this, std::placeholders::_1);
m_actions["SELECT_PRINTER"] = std::bind(&ConnectRequestHandler::on_connect_action_select_printer, this, std::placeholders::_1);
m_actions["PRINT"] = std::bind(&ConnectRequestHandler::on_connect_action_print, this, std::placeholders::_1);
m_actions["REQUEST_OPEN_IN_BROWSER"] = std::bind(&ConnectRequestHandler::on_connect_action_request_open_in_browser, this, std::placeholders::_1);
m_actions["ERROR"] = std::bind(&ConnectRequestHandler::on_connect_action_error, this, std::placeholders::_1);
m_actions["LOG"] = std::bind(&ConnectRequestHandler::on_connect_action_log, this, std::placeholders::_1);
m_actions["RELOAD_HOME_PAGE"] = std::bind(&ConnectRequestHandler::on_reload_event, this, std::placeholders::_1);
}
ConnectRequestHandler::~ConnectRequestHandler()
{
}
void ConnectRequestHandler::handle_message(const std::string& message)
{
// read msg and choose action
/*
v0:
{"type":"request","detail":{"action":"requestAccessToken"}}
v1:
{"action":"REQUEST_ACCESS_TOKEN"}
*/
std::string action_string;
try {
std::stringstream ss(message);
pt::ptree ptree;
pt::read_json(ss, ptree);
// v1:
if (const auto action = ptree.get_optional<std::string>("action"); action) {
action_string = *action;
}
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Could not parse _prusaConnect message. " << e.what();
return;
}
if (action_string.empty()) {
BOOST_LOG_TRIVIAL(error) << "Recieved invalid message from _prusaConnect (missing action). Message: " << message;
return;
}
assert(m_actions.find(action_string) != m_actions.end()); // this assert means there is a action that has no handling.
if (m_actions.find(action_string) != m_actions.end()) {
m_actions[action_string](message);
}
}
void ConnectRequestHandler::on_connect_action_error(const std::string &message_data)
{
BOOST_LOG_TRIVIAL(error) << "WebView runtime error: " << message_data;
}
void ConnectRequestHandler::resend_config()
{
on_connect_action_request_config({});
}
void ConnectRequestHandler::on_connect_action_log(const std::string& message_data)
{
BOOST_LOG_TRIVIAL(info) << "WebView log: " << message_data;
}
void ConnectRequestHandler::on_connect_action_request_login(const std::string &message_data)
{}
void ConnectRequestHandler::on_connect_action_request_config(const std::string& message_data)
{
/*
accessToken?: string;
clientVersion?: string;
colorMode?: "LIGHT" | "DARK";
language?: ConnectLanguage;
sessionId?: string;
*/
const std::string token = wxGetApp().plater()->get_user_account()->get_access_token();
//const std::string sesh = wxGetApp().plater()->get_user_account()->get_shared_session_key();
const std::string dark_mode = wxGetApp().dark_mode() ? "DARK" : "LIGHT";
wxString language = GUI::wxGetApp().current_language_code();
language = language.SubString(0, 1);
const std::string init_options = GUI::format("{\"accessToken\": \"%4%\",\"clientVersion\": \"%1%\", \"colorMode\": \"%2%\", \"language\": \"%3%\"}", SLIC3R_VERSION, dark_mode, language, token );
wxString script = GUI::format_wxstr("window._prusaConnect_v2.init(%1%)", init_options);
run_script_bridge(script);
}
void ConnectRequestHandler::on_connect_action_request_open_in_browser(const std::string& message_data)
{
try {
std::stringstream ss(message_data);
pt::ptree ptree;
pt::read_json(ss, ptree);
if (const auto url = ptree.get_optional<std::string>("url"); url) {
wxGetApp().open_browser_with_warning_dialog(GUI::from_u8(*url));
}
} catch (const std::exception &e) {
BOOST_LOG_TRIVIAL(error) << "Could not parse _prusaConnect message. " << e.what();
return;
}
}
SourceViewDialog::SourceViewDialog(wxWindow* parent, wxString source) :
wxDialog(parent, wxID_ANY, "Source Code",
wxDefaultPosition, wxSize(700,500),
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, source,
wxDefaultPosition, wxDefaultSize,
wxTE_MULTILINE |
wxTE_RICH |
wxTE_READONLY);
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(text, 1, wxEXPAND);
SetSizer(sizer);
}
} // namespace Slic3r::GUI

View File

@ -0,0 +1,45 @@
#ifndef slic3r_ConnectRequestHandler_hpp_
#define slic3r_ConnectRequestHandler_hpp_
#include <map>
#include <string>
#include <functional>
#include <wx/string.h>
#include <wx/dialog.h>
#include <wx/window.h>
//#define DEBUG_URL_PANEL
namespace Slic3r::GUI {
class ConnectRequestHandler
{
public:
ConnectRequestHandler();
~ConnectRequestHandler();
void handle_message(const std::string& message);
void resend_config();
protected:
// action callbacks stored in m_actions
virtual void on_connect_action_log(const std::string& message_data);
virtual void on_connect_action_error(const std::string& message_data);
virtual void on_connect_action_request_login(const std::string& message_data);
virtual void on_connect_action_request_config(const std::string& message_data);
virtual void on_connect_action_request_open_in_browser(const std::string& message_data);
virtual void on_connect_action_select_printer(const std::string& message_data) = 0;
virtual void on_connect_action_print(const std::string& message_data) = 0;
virtual void on_connect_action_webapp_ready(const std::string& message_data) = 0;
virtual void on_reload_event(const std::string& message_data) = 0;
virtual void run_script_bridge(const wxString &script) = 0;
std::map<std::string, std::function<void(const std::string&)>> m_actions;
};
class SourceViewDialog : public wxDialog
{
public:
SourceViewDialog(wxWindow* parent, wxString source);
};
} // namespace Slic3r::GUI
#endif /* slic3r_ConnectRequestHandler_hpp_ */

View File

@ -9,6 +9,7 @@
#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
#include <curl/curl.h>
namespace Slic3r {
namespace GUI {
@ -72,16 +73,31 @@ std::string filename_from_url(const std::string& url)
return std::string();
return std::string(url_plain.begin() + slash + 1, url_plain.end());
}
std::string unescape_url(const std::string& unescaped)
{
std::string ret_val;
CURL* curl = curl_easy_init();
if (curl) {
int decodelen;
char* decoded = curl_easy_unescape(curl, unescaped.c_str(), unescaped.size(), &decodelen);
if (decoded) {
ret_val = std::string(decoded);
curl_free(decoded);
}
curl_easy_cleanup(curl);
}
return ret_val;
}
}
Download::Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
Download::Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder, bool load_after)
: m_id(ID)
, m_filename(filename_from_url(url))
, m_dest_folder(dest_folder)
{
assert(boost::filesystem::is_directory(dest_folder));
m_final_path = dest_folder / m_filename;
m_file_get = std::make_shared<FileGet>(ID, std::move(url), m_filename, evt_handler, dest_folder);
m_file_get = std::make_shared<FileGet>(ID, std::move(url), m_filename, evt_handler, dest_folder, load_after);
}
void Download::start()
@ -116,11 +132,6 @@ void Download::resume()
Downloader::Downloader()
: wxEvtHandler()
{
//Bind(EVT_DWNLDR_FILE_COMPLETE, [](const wxCommandEvent& evt) {});
//Bind(EVT_DWNLDR_FILE_PROGRESS, [](const wxCommandEvent& evt) {});
//Bind(EVT_DWNLDR_FILE_ERROR, [](const wxCommandEvent& evt) {});
//Bind(EVT_DWNLDR_FILE_NAME_CHANGE, [](const wxCommandEvent& evt) {});
Bind(EVT_DWNLDR_FILE_COMPLETE, &Downloader::on_complete, this);
Bind(EVT_DWNLDR_FILE_PROGRESS, &Downloader::on_progress, this);
Bind(EVT_DWNLDR_FILE_ERROR, &Downloader::on_error, this);
@ -133,23 +144,18 @@ void Downloader::start_download(const std::string& full_url)
{
assert(m_initialized);
// TODO: There is a misterious slash appearing in recieved msg on windows
#ifdef _WIN32
if (!boost::starts_with(full_url, "prusaslicer://open/?file=")) {
#else
if (!boost::starts_with(full_url, "prusaslicer://open?file=")) {
#endif
BOOST_LOG_TRIVIAL(error) << "Could not start download due to wrong URL: " << full_url;
// TODO: show error?
std::string escaped_url = unescape_url(full_url);
if (boost::starts_with(escaped_url, "prusaslicer://open?file=")) {
escaped_url = escaped_url.substr(24);
}else if (boost::starts_with(escaped_url, "prusaslicer://open/?file=")) {
escaped_url = escaped_url.substr(25);
} else {
BOOST_LOG_TRIVIAL(error) << "Could not start download due to wrong URL: " << full_url;
return;
}
}
size_t id = get_next_id();
// TODO: still same mistery
#ifdef _WIN32
std::string escaped_url = FileGet::escape_url(full_url.substr(25));
#else
std::string escaped_url = FileGet::escape_url(full_url.substr(24));
#endif
if (!boost::starts_with(escaped_url, "https://") || !FileGet::is_subdomain(escaped_url, "printables.com")) {
std::string msg = format(_L("Download won't start. Download URL doesn't point to https://printables.com : %1%"), escaped_url);
BOOST_LOG_TRIVIAL(error) << msg;
@ -158,14 +164,38 @@ void Downloader::start_download(const std::string& full_url)
return;
}
std::string text(escaped_url);
m_downloads.emplace_back(std::make_unique<Download>(id, std::move(escaped_url), this, m_dest_folder));
m_downloads.emplace_back(std::make_unique<Download>(id, std::move(escaped_url), this, m_dest_folder, true));
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
ntf_mngr->push_download_URL_progress_notification(id, m_downloads.back()->get_filename(), std::bind(&Downloader::user_action_callback, this, std::placeholders::_1, std::placeholders::_2));
m_downloads.back()->start();
BOOST_LOG_TRIVIAL(debug) << "started download";
}
void Downloader::start_download_printables(const std::string& url, bool load_after, const std::string& printables_url, GUI_App* app)
{
assert(m_initialized);
size_t id = get_next_id();
if (!boost::starts_with(url, "https://") || !FileGet::is_subdomain(url, "printables.com")) {
std::string msg = format(_L("Download won't start. Download URL doesn't point to https://printables.com : %1%"), url);
BOOST_LOG_TRIVIAL(error) << msg;
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
ntf_mngr->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::RegularNotificationLevel, msg);
return;
}
m_downloads.emplace_back(std::make_unique<Download>(id, url, this, m_dest_folder, load_after));
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
ntf_mngr->push_download_URL_progress_notification_with_printables_link( id
, m_downloads.back()->get_filename()
, printables_url
, std::bind(&Downloader::user_action_callback, this, std::placeholders::_1, std::placeholders::_2)
, std::bind(&GUI_App::open_link_in_printables, app, std::placeholders::_1)
);
m_downloads.back()->start();
}
void Downloader::on_progress(wxCommandEvent& event)
{
size_t id = event.GetInt();
@ -184,14 +214,14 @@ void Downloader::on_error(wxCommandEvent& event)
ntf_mngr->set_download_URL_error(id, into_u8(event.GetString()));
show_error(nullptr, format_wxstr(L"%1%\n%2%", _L("The download has failed") + ":", event.GetString()));
}
void Downloader::on_complete(wxCommandEvent& event)
void Downloader::on_complete(Event<DownloadEventData>& event)
{
// TODO: is this always true? :
// here we open the file itself, notification should get 1.f progress from on progress.
set_download_state(event.GetInt(), DownloadState::DownloadDone);
set_download_state(event.data.id, DownloadState::DownloadDone);
wxArrayString paths;
paths.Add(event.GetString());
wxGetApp().plater()->load_files(paths);
paths.Add(event.data.path);
if (event.data.load_after)
wxGetApp().plater()->load_files(paths);
}
bool Downloader::user_action_callback(DownloaderUserAction action, int id)
{

View File

@ -13,6 +13,7 @@ namespace Slic3r {
namespace GUI {
class NotificationManager;
class GUI_App;
enum DownloadState
{
@ -35,7 +36,7 @@ enum DownloaderUserAction
class Download {
public:
Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder);
Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder, bool load_after);
void start();
void cancel();
void pause();
@ -67,6 +68,8 @@ public:
m_initialized = true;
}
void start_download(const std::string& full_url);
void start_download_printables(const std::string& url, bool load_after, const std::string& printables_url, GUI_App* app);
// cancel = false -> just pause
bool user_action_callback(DownloaderUserAction action, int id);
private:
@ -80,7 +83,7 @@ private:
void on_progress(wxCommandEvent& event);
void on_error(wxCommandEvent& event);
void on_complete(wxCommandEvent& event);
void on_complete(Event<DownloadEventData>& event);
void on_name_change(wxCommandEvent& event);
void on_paused(wxCommandEvent& event);
void on_canceled(wxCommandEvent& event);

View File

@ -23,21 +23,6 @@ namespace GUI {
const size_t DOWNLOAD_MAX_CHUNK_SIZE = 10 * 1024 * 1024;
const size_t DOWNLOAD_SIZE_LIMIT = 1024 * 1024 * 1024;
std::string FileGet::escape_url(const std::string& unescaped)
{
std::string ret_val;
CURL* curl = curl_easy_init();
if (curl) {
int decodelen;
char* decoded = curl_easy_unescape(curl, unescaped.c_str(), unescaped.size(), &decodelen);
if (decoded) {
ret_val = std::string(decoded);
curl_free(decoded);
}
curl_easy_cleanup(curl);
}
return ret_val;
}
bool FileGet::is_subdomain(const std::string& url, const std::string& domain)
{
// domain should be f.e. printables.com (.com including)
@ -86,7 +71,7 @@ unsigned get_current_pid()
}
// int = DOWNLOAD ID; string = file path
wxDEFINE_EVENT(EVT_DWNLDR_FILE_COMPLETE, wxCommandEvent);
wxDEFINE_EVENT(EVT_DWNLDR_FILE_COMPLETE, Event<DownloadEventData>);
// int = DOWNLOAD ID; string = error msg
wxDEFINE_EVENT(EVT_DWNLDR_FILE_ERROR, wxCommandEvent);
// int = DOWNLOAD ID; string = progress percent
@ -112,17 +97,19 @@ struct FileGet::priv
std::atomic_bool m_stopped { false }; // either canceled or paused - download is not running
size_t m_written { 0 };
size_t m_absolute_size { 0 };
priv(int ID, std::string&& url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder);
bool m_load_after;
priv(int ID, std::string&& url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder, bool load_after);
void get_perform();
};
FileGet::priv::priv(int ID, std::string&& url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
FileGet::priv::priv(int ID, std::string&& url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder, bool load_after)
: m_id(ID)
, m_url(std::move(url))
, m_filename(filename)
, m_evt_handler(evt_handler)
, m_dest_folder(dest_folder)
, m_load_after(load_after)
{
}
@ -279,23 +266,8 @@ void FileGet::priv::get_perform()
m_evt_handler->QueueEvent(evt);
})
.on_complete([&](std::string body, unsigned /* http_status */) {
// TODO: perform a body size check
//
//size_t body_size = body.size();
//if (body_size != expected_size) {
// return;
//}
try
{
/*
if (m_written < body.size())
{
// this code should never be entered. As there should be on_progress call after last bit downloaded.
std::string part_for_write = body.substr(m_written);
fwrite(part_for_write.c_str(), 1, part_for_write.size(), file);
}
*/
fclose(file);
boost::filesystem::rename(m_tmp_path, dest_path);
}
@ -309,18 +281,15 @@ void FileGet::priv::get_perform()
m_evt_handler->QueueEvent(evt);
return;
}
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_COMPLETE);
evt->SetString(dest_path.wstring());
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
DownloadEventData event_data = {m_id, dest_path.wstring(), m_load_after};
wxQueueEvent(m_evt_handler, new Event<DownloadEventData>(EVT_DWNLDR_FILE_COMPLETE, event_data));
})
.perform_sync();
}
FileGet::FileGet(int ID, std::string url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
: p(new priv(ID, std::move(url), filename, evt_handler, dest_folder))
FileGet::FileGet(int ID, std::string url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder, bool load_after)
: p(new priv(ID, std::move(url), filename, evt_handler, dest_folder, load_after))
{}
FileGet::FileGet(FileGet&& other) : p(std::move(other.p)) {}

View File

@ -5,6 +5,8 @@
#ifndef slic3r_DownloaderFileGet_hpp_
#define slic3r_DownloaderFileGet_hpp_
#include "Event.hpp"
#include "../Utils/Http.hpp"
#include <memory>
@ -19,7 +21,7 @@ class FileGet : public std::enable_shared_from_this<FileGet> {
private:
struct priv;
public:
FileGet(int ID, std::string url, const std::string& filename, wxEvtHandler* evt_handler,const boost::filesystem::path& dest_folder);
FileGet(int ID, std::string url, const std::string& filename, wxEvtHandler* evt_handler,const boost::filesystem::path& dest_folder, bool load_after);
FileGet(FileGet&& other);
~FileGet();
@ -27,13 +29,18 @@ public:
void cancel();
void pause();
void resume();
static std::string escape_url(const std::string& url);
static bool is_subdomain(const std::string& url, const std::string& domain);
static bool is_subdomain(const std::string& url, const std::string& domain);
private:
std::unique_ptr<priv> p;
};
struct DownloadEventData
{
int id;
wxString path;
bool load_after;
};
// int = DOWNLOAD ID; string = file path
wxDECLARE_EVENT(EVT_DWNLDR_FILE_COMPLETE, wxCommandEvent);
wxDECLARE_EVENT(EVT_DWNLDR_FILE_COMPLETE, Event<DownloadEventData>);
// int = DOWNLOAD ID; string = error msg
wxDECLARE_EVENT(EVT_DWNLDR_FILE_PROGRESS, wxCommandEvent);
// int = DOWNLOAD ID; string = progress percent

View File

@ -103,7 +103,6 @@
#include "WifiConfigDialog.hpp"
#include "UserAccount.hpp"
#include "UserAccountUtils.hpp"
#include "WebViewDialog.hpp"
#include "LoginDialog.hpp" // IWYU pragma: keep
#include "PresetArchiveDatabase.hpp"
@ -4114,6 +4113,50 @@ void GUI_App::show_printer_webview_tab()
}
void GUI_App::printables_download_request(const std::string& download_url, const std::string& model_url)
{
//this->mainframe->select_tab(size_t(0));
//lets always init so if the download dest folder was changed, new dest is used
boost::filesystem::path dest_folder(app_config->get("url_downloader_dest"));
if (dest_folder.empty() || !boost::filesystem::is_directory(dest_folder)) {
std::string msg = _u8L("Could not start URL download. Destination folder is not set. Please choose destination folder in Configuration Wizard.");
BOOST_LOG_TRIVIAL(error) << msg;
show_error(nullptr, msg);
return;
}
m_downloader->init(dest_folder);
m_downloader->start_download_printables(download_url, false, model_url, this);
}
void GUI_App::printables_slice_request(const std::string& download_url, const std::string& model_url)
{
this->mainframe->select_tab(size_t(0));
//lets always init so if the download dest folder was changed, new dest is used
boost::filesystem::path dest_folder(app_config->get("url_downloader_dest"));
if (dest_folder.empty() || !boost::filesystem::is_directory(dest_folder)) {
std::string msg = _u8L("Could not start URL download. Destination folder is not set. Please choose destination folder in Configuration Wizard.");
BOOST_LOG_TRIVIAL(error) << msg;
show_error(nullptr, msg);
return;
}
m_downloader->init(dest_folder);
m_downloader->start_download_printables(download_url, true, model_url, this);
}
void GUI_App::printables_print_request(const std::string& download_url, const std::string& model_url)
{
plater()->printables_to_connect_gcode(Utils::ServiceConfig::instance().printables_url() + model_url);
}
void GUI_App::printables_login_request()
{
plater_->get_user_account()->do_login();
}
void GUI_App::open_link_in_printables(const std::string& url)
{
mainframe->show_printables_tab(url);
}
bool LogGui::ignorred_message(const wxString& msg)
{

View File

@ -436,7 +436,11 @@ public:
void request_project_download(std::string project_id) {}
void request_open_project(std::string project_id) {}
void request_remove_project(std::string project_id) {}
void printables_download_request(const std::string& download_url, const std::string& model_url);
void printables_slice_request(const std::string& download_url, const std::string& model_url);
void printables_print_request(const std::string& download_url, const std::string& model_url);
void printables_login_request();
void open_link_in_printables(const std::string& url);
private:
bool on_init_inner();
void init_app_config();

View File

@ -64,7 +64,7 @@
#include "GalleryDialog.hpp"
#include "NotificationManager.hpp"
#include "Preferences.hpp"
#include "WebViewDialog.hpp"
#include "WebViewPanel.hpp"
#include "UserAccount.hpp"
#ifdef _WIN32
@ -801,14 +801,39 @@ void MainFrame::create_preset_tabs()
add_created_tab(new TabSLAMaterial(m_tabpanel), "resin");
add_created_tab(new TabPrinter(m_tabpanel), wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF ? "printer" : "sla_printer");
m_printables_webview = new PrintablesWebViewPanel(m_tabpanel);
add_printables_webview_tab();
m_connect_webview = new ConnectWebViewPanel(m_tabpanel);
m_printer_webview = new PrinterWebViewPanel(m_tabpanel, L"");
// new created tabs have to be hidden by default
m_connect_webview->Hide();
m_printer_webview->Hide();
}
void MainFrame::on_account_login(const std::string& token)
{
add_connect_webview_tab();
assert (m_printables_webview);
m_printables_webview->login(token);
}
void MainFrame::on_account_will_refresh()
{
m_printables_webview->send_will_refresh();
}
void MainFrame::on_account_did_refresh(const std::string& token)
{
m_printables_webview->send_refreshed_token(token);
}
void MainFrame::on_account_logout()
{
remove_connect_webview_tab();
assert (m_printables_webview);
m_printables_webview->logout();
}
void MainFrame::add_connect_webview_tab()
{
if (m_connect_webview_added) {
@ -819,13 +844,13 @@ void MainFrame::add_connect_webview_tab()
// insert "Connect" tab to position next to "Printer" tab
// order of tabs: Plater - Print Settings - Filaments - Printers - Prusa Connect - Prusa Link
int n = m_tabpanel->FindPage(wxGetApp().get_tab(Preset::TYPE_PRINTER)) + 1;
int n = m_tabpanel->FindPage(m_printables_webview) + 1;
wxWindow* page = m_connect_webview;
const wxString text(L"Prusa Connect");
const std::string bmp_name = "";
bool bSelect = false;
m_tabpanel->InsertNewPage(n, page, text, bmp_name, bSelect);
m_connect_webview->load_default_url_delayed();
m_connect_webview->set_create_browser();
m_connect_webview_added = true;
}
void MainFrame::remove_connect_webview_tab()
@ -839,26 +864,69 @@ void MainFrame::remove_connect_webview_tab()
m_tabpanel->RemovePage(size_t(n));
m_connect_webview_added = false;
m_connect_webview->logout();
m_connect_webview->destroy_browser();
}
void MainFrame::show_connect_tab(const wxString& url)
{
assert(m_connect_webview_added);
if (!m_connect_webview_added) {
return;
}
m_tabpanel->SetSelection(m_tabpanel->FindPage(m_connect_webview));
m_connect_webview->set_load_default_url_on_next_error(true);
m_connect_webview->load_url(url);
}
void MainFrame::show_printables_tab(const std::string& url)
{
if (!m_printables_webview_added) {
return;
}
// we have to set next url first, than show the tab
// printables_tab has to reload on show everytime
// so it is not possible load_url right after show
m_printables_webview->set_load_default_url_on_next_error(true);
m_printables_webview->set_next_show_url(url);
m_tabpanel->SetSelection(m_tabpanel->FindPage(m_printables_webview));
}
void MainFrame::add_printables_webview_tab()
{
if (m_printables_webview_added) {
return;
}
int n = m_tabpanel->FindPage(wxGetApp().get_tab(Preset::TYPE_PRINTER)) + 1;
wxWindow* page = m_printables_webview;
const wxString text(L"Printables");
const std::string bmp_name = "";
m_tabpanel->InsertNewPage(n, page, text, bmp_name, false);
m_printables_webview->set_create_browser();
m_printables_webview_added = true;
}
// no longer needed?
void MainFrame::remove_printables_webview_tab()
{
if (!m_printables_webview_added) {
return;
}
int n = m_tabpanel->FindPage(m_printables_webview);
if (m_tabpanel->GetSelection() == n)
m_tabpanel->SetSelection(0);
m_tabpanel->RemovePage(size_t(n));
m_printables_webview_added = false;
m_printables_webview->destroy_browser();
}
void MainFrame::show_printer_webview_tab(DynamicPrintConfig* dpc)
{
remove_printer_webview_tab();
// if physical printer is selected
if (dpc && dpc->option<ConfigOptionEnum<PrintHostType>>("host_type")->value != htPrusaConnect) {
std::string url = dpc->opt_string("print_host");
if (url.find("http://") != 0 && url.find("https://") != 0) {
url = "http://" + url;
}
// set password / api key
if (dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(dpc->option("printhost_authorization_type"))->value == AuthorizationType::atKeyPassword) {
set_printer_webview_api_key(dpc->opt_string("printhost_apikey"));
@ -866,59 +934,33 @@ void MainFrame::show_printer_webview_tab(DynamicPrintConfig* dpc)
else {
set_printer_webview_credentials(dpc->opt_string("printhost_user"), dpc->opt_string("printhost_password"));
}
// add printer or change url
if (get_printer_webview_tab_added()) {
set_printer_webview_tab_url(from_u8(url));
}
else {
add_printer_webview_tab(from_u8(url));
}
}
// if physical printer isn't selected, so delete page from TopBar
else {
if (m_tabpanel->GetPageText(m_tabpanel->GetSelection()) == _L("Physical Printer"))
select_tab(size_t(0));
remove_printer_webview_tab();
add_printer_webview_tab(from_u8(url));
}
}
void MainFrame::add_printer_webview_tab(const wxString& url)
{
if (m_printer_webview_added) {
set_printer_webview_tab_url(url);
//set_printer_webview_tab_url(url);
return;
}
m_printer_webview_added = true;
// add as the last (rightmost) panel
m_tabpanel->AddNewPage(m_printer_webview, _L("Physical Printer"), "");
m_printer_webview->set_default_url(url);
m_printer_webview->load_default_url_delayed();
m_printer_webview->set_create_browser();
}
void MainFrame::remove_printer_webview_tab()
{
if (!m_printer_webview_added) {
return;
}
if (m_tabpanel->GetPageText(m_tabpanel->GetSelection()) == _L("Physical Printer"))
select_tab(size_t(0));
m_printer_webview_added = false;
m_printer_webview->Hide();
m_tabpanel->RemovePage(m_tabpanel->FindPage(m_printer_webview));
}
void MainFrame::set_printer_webview_tab_url(const wxString& url)
{
if (!m_printer_webview_added) {
add_printer_webview_tab(url);
return;
}
// TODO: this will reset already filled credential when bundle loaded,
// what's the reason of clearing credentials here?
//m_printer_webview->clear();
m_printer_webview->set_default_url(url);
if (m_tabpanel->GetSelection() == m_tabpanel->FindPage(m_printer_webview)) {
m_printer_webview->load_url(url);
} else {
m_printer_webview->load_default_url_delayed();
}
m_printer_webview->destroy_browser();
}
void MainFrame::set_printer_webview_api_key(const std::string& key)
@ -930,6 +972,29 @@ void MainFrame::set_printer_webview_credentials(const std::string& usr, const st
m_printer_webview->set_credentials(usr, psk);
}
bool MainFrame::is_any_webview_selected()
{
int selection = m_tabpanel->GetSelection();
if ( selection == m_tabpanel->FindPage(m_printables_webview))
return true;
if (m_connect_webview_added && selection == m_tabpanel->FindPage(m_connect_webview))
return true;
if (m_printer_webview_added && selection == m_tabpanel->FindPage(m_printer_webview))
return true;
return false;
}
void MainFrame::reload_selected_webview()
{
int selection = m_tabpanel->GetSelection();
if ( selection == m_tabpanel->FindPage(m_printables_webview))
m_printables_webview->do_reload();
if (m_connect_webview_added && selection == m_tabpanel->FindPage(m_connect_webview))
m_connect_webview->do_reload();
if (m_printer_webview_added && selection == m_tabpanel->FindPage(m_printer_webview))
m_printer_webview->do_reload();
}
void Slic3r::GUI::MainFrame::refresh_account_menu(bool avatar/* = false */)
{
// Update User name in TopBar
@ -1193,6 +1258,8 @@ void MainFrame::on_sys_color_changed()
for (Tab* tab : wxGetApp().tabs_list)
tab->sys_color_changed();
if (m_printables_webview)
m_printables_webview->sys_color_changed();
if (m_connect_webview)
m_connect_webview->sys_color_changed();
if (m_printer_webview)
@ -1630,6 +1697,10 @@ void MainFrame::init_menubar_as_editor()
wxFULLSCREEN_NOSTATUSBAR | wxFULLSCREEN_NOBORDER | wxFULLSCREEN_NOCAPTION); },
this, []() { return true; }, [this]() { return this->IsFullScreen(); }, this);
#endif // __APPLE__
viewMenu->AppendSeparator();
append_menu_item(viewMenu, wxID_ANY, _L("&Reload Web Content") + "\tF5", _L("Reload WebView"),
[this](wxCommandEvent&) { reload_selected_webview(); }, "", nullptr, [this]() {return is_any_webview_selected(); }, this);
}
// Help menu

View File

@ -47,6 +47,7 @@ class PreferencesDialog;
class GalleryDialog;
class ConnectWebViewPanel;
class PrinterWebViewPanel;
class PrintablesWebViewPanel;
enum QuickSlice
{
@ -98,10 +99,12 @@ class MainFrame : public DPIFrame
size_t m_last_selected_tab;
Search::OptionsSearcher m_searcher;
ConnectWebViewPanel* m_connect_webview{ nullptr };
bool m_connect_webview_added{ false };
PrinterWebViewPanel* m_printer_webview{ nullptr };
bool m_printer_webview_added{ false };
ConnectWebViewPanel* m_connect_webview{ nullptr };
bool m_connect_webview_added{ false };
PrintablesWebViewPanel* m_printables_webview{ nullptr };
bool m_printables_webview_added{ false };
PrinterWebViewPanel* m_printer_webview{ nullptr };
bool m_printer_webview_added{ false };
std::string get_base_name(const wxString &full_name, const char *extension = nullptr) const;
std::string get_dir_name(const wxString &full_name) const;
@ -124,6 +127,9 @@ class MainFrame : public DPIFrame
bool can_delete_all() const;
bool can_reslice() const;
void add_connect_webview_tab();
void remove_connect_webview_tab();
// MenuBar items changeable in respect to printer technology
enum MenuItems
{ // FFF SLA
@ -214,18 +220,25 @@ public:
void add_to_recent_projects(const wxString& filename);
void technology_changed();
void add_connect_webview_tab();
void remove_connect_webview_tab();
void show_connect_tab(const wxString &url);
void on_account_login(const std::string& token);
void on_account_will_refresh();
void on_account_did_refresh(const std::string& token);
void on_account_logout();
void show_connect_tab(const wxString& url);
void show_printables_tab(const std::string& url);
void add_printables_webview_tab();
void remove_printables_webview_tab();
void show_printer_webview_tab(DynamicPrintConfig* dpc);
void add_printer_webview_tab(const wxString& url);
void remove_printer_webview_tab();
void set_printer_webview_tab_url(const wxString& url);
bool get_printer_webview_tab_added() const { return m_printer_webview_added; }
void set_printer_webview_api_key(const std::string& key);
void set_printer_webview_credentials(const std::string& usr, const std::string& psk);
bool is_any_webview_selected();
void reload_selected_webview();
void refresh_account_menu(bool avatar = false);

View File

@ -936,9 +936,8 @@ void NotificationManager::ProgressBarNotification::render_text(const float win_s
render_cancel_button(win_size_x, win_size_y, win_pos_x, win_pos_y);
render_bar(win_size_x, win_size_y, win_pos_x, win_pos_y);
}
}
void NotificationManager::ProgressBarNotification::render_bar(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f);
@ -1073,6 +1072,51 @@ void NotificationManager::ProgressBarWithCancelNotification::render_bar(const fl
ImGuiPureWrap::text(text.c_str());
}
//------URLDownloadWithPrintablesLinkNotification----------------
void NotificationManager::URLDownloadWithPrintablesLinkNotification::init()
{
PopNotification::init();
//m_lines_count++;
if (m_endlines.empty()) {
m_endlines.push_back(0);
}
m_lines_count = 3;
m_multiline = true;
while (m_endlines.size() < 3)
m_endlines.push_back(m_endlines.back());
if(m_state == EState::Shown)
m_state = EState::NotFading;
}
bool NotificationManager::URLDownloadWithPrintablesLinkNotification::on_text_click()
{
m_hypertext_callback_override(m_hypertext);
return false;
}
void NotificationManager::URLDownloadWithPrintablesLinkNotification::render_text(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
assert(m_multiline);
assert(m_text1.size() >= m_endlines[0] || m_text1.size() >= m_endlines[1]);
if(m_endlines[0] > m_text1.size() || m_endlines[1] > m_text1.size())
return;
// 1 lines text (what doesn't fit, wont show), 1 line hypertext, 1 line bar
ImGui::SetCursorPosX(m_left_indentation);
ImGui::SetCursorPosY(m_line_height / 4);
ImGuiPureWrap::text(m_text1.substr(0, m_endlines[0]).c_str());
ImGui::SetCursorPosX(m_left_indentation);
ImGui::SetCursorPosY(m_line_height + m_line_height / 4);
std::string line = _u8L("Open Printables project page");
//ImGuiPureWrap::text(line.c_str());
render_hypertext(m_left_indentation, m_line_height + m_line_height / 4, line);
if (m_has_cancel_button)
render_cancel_button(win_size_x, win_size_y, win_pos_x, win_pos_y);
render_bar(win_size_x, win_size_y, win_pos_x, win_pos_y);
}
//------URLDownloadNotification----------------
void NotificationManager::URLDownloadNotification::render_close_button(const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
@ -2495,6 +2539,19 @@ void NotificationManager::push_download_URL_progress_notification(size_t id, con
push_notification_data(std::make_unique<NotificationManager::URLDownloadNotification>(data, m_id_provider, m_evt_handler, id, user_action_callback), 0);
}
void NotificationManager::push_download_URL_progress_notification_with_printables_link(size_t id, const std::string& text, const std::string& url, std::function<bool(DownloaderUserAction, int)> user_action_callback, std::function<void(std::string)> hypertext_callback)
{
// If already exists
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::URLDownload && dynamic_cast<URLDownloadNotification*>(notification.get())->get_download_id() == id) {
return;
}
}
// push new one
NotificationData data{ NotificationType::URLDownload, NotificationLevel::ProgressBarNotificationLevel, 30, _u8L("Download") + ": " + text, url };
push_notification_data(std::make_unique<NotificationManager::URLDownloadWithPrintablesLinkNotification>(data, m_id_provider, m_evt_handler, id, user_action_callback, hypertext_callback), 0);
}
void NotificationManager::set_download_URL_progress(size_t id, float percentage)
{
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {

View File

@ -244,6 +244,7 @@ public:
void set_download_progress_percentage(float percentage);
// Download URL progress notif
void push_download_URL_progress_notification(size_t id, const std::string& text, std::function<bool(DownloaderUserAction, int)> user_action_callback);
void push_download_URL_progress_notification_with_printables_link(size_t id, const std::string& text, const std::string& url, std::function<bool(DownloaderUserAction, int)> user_action_callback, std::function<void(std::string)> hypertext_callback);
void set_download_URL_progress(size_t id, float percentage);
void set_download_URL_paused(size_t id);
void set_download_URL_canceled(size_t id);
@ -577,6 +578,22 @@ private:
std::string m_error_message;
};
class URLDownloadWithPrintablesLinkNotification : public URLDownloadNotification
{
public:
URLDownloadWithPrintablesLinkNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, size_t download_id, std::function<bool(DownloaderUserAction, int)> user_action_callback, std::function<void(std::string)> hypertext_callback)
: URLDownloadNotification(n, id_provider, evt_handler, download_id, user_action_callback)
, m_hypertext_callback_override(hypertext_callback)
{
}
protected:
void render_text(const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y) override;
void init() override;
bool on_text_click() override;
std::function<void(std::string)> m_hypertext_callback_override;
};
class PrintHostUploadNotification : public ProgressBarNotification
{
public:

View File

@ -915,12 +915,16 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
this->q->Bind(EVT_OPEN_EXTERNAL_LOGIN_WIZARD, open_external_login);
this->q->Bind(EVT_OPEN_EXTERNAL_LOGIN, open_external_login);
// void on_account_login(const std::string& token);
// void on_account_will_refresh();
// void on_account_did_refresh(const std::string& token);
// void on_account_logout();
this->q->Bind(EVT_UA_LOGGEDOUT, [this](UserAccountSuccessEvent& evt) {
user_account->clear();
std::string text = _u8L("Logged out from Prusa Account.");
this->notification_manager->close_notification_of_type(NotificationType::UserAccountID);
this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::ImportantNotificationLevel, text);
this->main_frame->remove_connect_webview_tab();
this->main_frame->on_account_logout();
this->main_frame->refresh_account_menu(true);
// Update sidebar printer status
sidebar->update_printer_presets_combobox();
@ -937,15 +941,19 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
std::string who = user_account->get_username();
std::string username;
if (user_account->on_user_id_success(evt.data, username)) {
// Do not show notification on refresh.
if (who != username) {
// show notification only on login (not refresh).
std::string text = format(_u8L("Logged to Prusa Account as %1%."), username);
// login notification
this->notification_manager->close_notification_of_type(NotificationType::UserAccountID);
// show connect tab
this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::ImportantNotificationLevel, text);
this->main_frame->on_account_login(user_account->get_access_token());
} else {
// refresh do different operations than on_account_login
this->main_frame->on_account_did_refresh(user_account->get_access_token());
}
this->main_frame->add_connect_webview_tab();
// Update User name in TopBar
this->main_frame->refresh_account_menu();
wxGetApp().update_wizard_login_page();
@ -958,7 +966,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
user_account->clear();
this->notification_manager->close_notification_of_type(NotificationType::UserAccountID);
this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::WarningNotificationLevel, _u8L("Failed to connect to Prusa Account."));
this->main_frame->remove_connect_webview_tab();
this->main_frame->on_account_logout();
// Update User name in TopBar
this->main_frame->refresh_account_menu(true);
// Update sidebar printer status
@ -971,7 +979,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
user_account->clear();
this->notification_manager->close_notification_of_type(NotificationType::UserAccountID);
this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::WarningNotificationLevel, _u8L("Failed to connect to Prusa Account."));
this->main_frame->remove_connect_webview_tab();
this->main_frame->on_account_logout();
// Update User name in TopBar
this->main_frame->refresh_account_menu(true);
// Update sidebar printer status
@ -1032,7 +1040,10 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
this->q->Bind(EVT_UA_REFRESH_TIME, [this](UserAccountTimeEvent& evt) {
this->user_account->set_refresh_time(evt.data);
});
});
this->q->Bind(EVT_UA_ENQUEUED_REFRESH, [this](SimpleEvent& evt) {
this->main_frame->on_account_will_refresh();
});
}
wxGetApp().other_instance_message_handler()->init(this->q);
@ -6006,6 +6017,18 @@ bool load_secret(const std::string& id, const std::string& opt, std::string& usr
#endif // wxUSE_SECRETSTORE
}
}
void Plater::printables_to_connect_gcode(const std::string& url)
{
{
PrintablesConnectUploadDialog dialog(this, url);
if (dialog.ShowModal() != wxID_OK) {
return;
}
}
}
void Plater::connect_gcode()
{
assert(p->user_account->is_logged());

View File

@ -229,6 +229,7 @@ public:
void send_gcode_inner(DynamicPrintConfig* physical_printer_config);
void eject_drive();
void connect_gcode();
void printables_to_connect_gcode(const std::string& url);
std::string get_upload_filename();
void take_snapshot(const std::string &snapshot_name);

View File

@ -31,6 +31,7 @@ wxDEFINE_EVENT(EVT_UA_FAIL, UserAccountFailEvent);
wxDEFINE_EVENT(EVT_UA_RESET, UserAccountFailEvent);
wxDEFINE_EVENT(EVT_UA_PRUSACONNECT_PRINTER_DATA_FAIL, UserAccountFailEvent);
wxDEFINE_EVENT(EVT_UA_REFRESH_TIME, UserAccountTimeEvent);
wxDEFINE_EVENT(EVT_UA_ENQUEUED_REFRESH, SimpleEvent);
void UserActionPost::perform(/*UNUSED*/ wxEvtHandler* evt_handler, /*UNUSED*/ const std::string& access_token, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input) const
{
@ -227,6 +228,7 @@ void UserAccountSession::enqueue_test_with_refresh()
void UserAccountSession::enqueue_refresh(const std::string& body)
{
wxQueueEvent(p_evt_handler, new SimpleEvent(EVT_UA_ENQUEUED_REFRESH));
std::string post_fields;
{
std::lock_guard<std::mutex> lock(m_credentials_mutex);

View File

@ -30,6 +30,7 @@ wxDECLARE_EVENT(EVT_UA_FAIL, UserAccountFailEvent); // Soft fail - clears only a
wxDECLARE_EVENT(EVT_UA_RESET, UserAccountFailEvent); // Hard fail - clears all
wxDECLARE_EVENT(EVT_UA_PRUSACONNECT_PRINTER_DATA_FAIL, UserAccountFailEvent); // Failed to get data for printer to select, soft fail, action does not repeat
wxDECLARE_EVENT(EVT_UA_REFRESH_TIME, UserAccountTimeEvent);
wxDECLARE_EVENT(EVT_UA_ENQUEUED_REFRESH, SimpleEvent);
typedef std::function<void(const std::string& body)> UserActionSuccessFn;
typedef std::function<void(const std::string& body)> UserActionFailFn;

View File

@ -1,13 +1,16 @@
#include "WebView.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/format.hpp"
#include "libslic3r/Platform.hpp"
#include <wx/uri.h>
#include <wx/webview.h>
#include <boost/log/trivial.hpp>
wxWebView* WebView::CreateWebView(wxWindow * parent, const wxString& url, const std::vector<std::string>& message_handlers)
wxWebView* WebView::webview_new()
{
#if wxUSE_WEBVIEW_EDGE
bool backend_available = wxWebView::IsBackendAvailable(wxWebViewBackendEdge);
@ -18,44 +21,40 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, const wxString& url, const
wxWebView* webView = nullptr;
if (backend_available)
webView = wxWebView::New();
if (webView) {
wxString correct_url = url.empty() ? wxString("") : wxURI(url).BuildURI();
#ifdef __WIN32__
webView->SetUserAgent(SLIC3R_APP_FULL_NAME);
webView->Create(parent, wxID_ANY, correct_url, wxDefaultPosition, wxDefaultSize);
//We register the wxfs:// protocol for testing purposes
//webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewArchiveHandler("wxfs")));
//And the memory: file system
//webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewFSHandler("memory")));
#else
// With WKWebView handlers need to be registered before creation
//webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewArchiveHandler("wxfs")));
// And the memory: file system
//webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewFSHandler("memory")));
webView->Create(parent, wxID_ANY, correct_url, wxDefaultPosition, wxDefaultSize);
webView->SetUserAgent(wxString::FromUTF8(SLIC3R_APP_FULL_NAME));
#endif
#ifndef __WIN32__
Slic3r::GUI::wxGetApp().CallAfter([message_handlers, webView] {
#endif
for (const std::string& handler : message_handlers) {
if (!webView->AddScriptMessageHandler(Slic3r::GUI::into_u8(handler))) {
// TODO: dialog to user !!!
//wxLogError("Could not add script message handler");
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Could not add script message handler " << handler;
}
}
#ifndef __WIN32__
});
#endif
webView->EnableContextMenu(false);
} else {
// TODO: dialog to user !!!
if (!webView)
BOOST_LOG_TRIVIAL(error) << "Failed to create wxWebView object.";
}
return webView;
}
void WebView::webview_create(wxWebView* webView, wxWindow *parent, const wxString& url, const std::vector<std::string>& message_handlers)
{
assert(webView);
wxString correct_url = url.empty() ? wxString("") : wxURI(url).BuildURI();
wxString user_agent = Slic3r::GUI::format_wxstr("%1%/%2% (%3%)",SLIC3R_APP_FULL_NAME, SLIC3R_VERSION, Slic3r::platform_to_string(Slic3r::platform()));
#ifdef __WIN32__
webView->SetUserAgent(user_agent);
webView->Create(parent, wxID_ANY, correct_url, wxDefaultPosition, wxDefaultSize, wxNO_BORDER);
//We register the wxfs:// protocol for testing purposes
//webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewArchiveHandler("wxfs")));
//And the memory: file system
//webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewFSHandler("memory")));
#else
// With WKWebView handlers need to be registered before creation
//webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewArchiveHandler("wxfs")));
// And the memory: file system
//webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewFSHandler("memory")));
webView->Create(parent, wxID_ANY, correct_url, wxDefaultPosition, wxDefaultSize);
webView->SetUserAgent(user_agent);
#endif
#ifndef __WIN32__
Slic3r::GUI::wxGetApp().CallAfter([message_handlers, webView] {
#endif
for (const std::string& handler : message_handlers) {
if (!webView->AddScriptMessageHandler(Slic3r::GUI::from_u8(handler))) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Could not add script message handler " << handler;
}
}
#ifndef __WIN32__
});
#endif
webView->EnableContextMenu(false);
}

View File

@ -10,7 +10,10 @@ class wxString;
namespace WebView
{
wxWebView *CreateWebView(wxWindow *parent, const wxString& url, const std::vector<std::string>& message_handlers);
// When using WebView in Panel, it is benfitable to call create only when it is being shown. (lower CPU usage)
// But when using WebView in Dialog, call both.
wxWebView* webview_new();
void webview_create(wxWebView* webview, wxWindow *parent, const wxString& url, const std::vector<std::string>& message_handlers);
};
#endif // !slic3r_GUI_WebView_hpp_

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,13 @@
#ifndef slic3r_WebViewDialog_hpp_
#define slic3r_WebViewDialog_hpp_
//#define DEBUG_URL_PANEL
#include <map>
#include <wx/wx.h>
#include <wx/event.h>
#include "GUI_Utils.hpp"
#include "UserAccountSession.hpp"
#include "ConnectRequestHandler.hpp"
#ifdef DEBUG_URL_PANEL
#include <wx/infobar.h>
@ -22,95 +21,10 @@ wxDECLARE_EVENT(EVT_OPEN_EXTERNAL_LOGIN, wxCommandEvent);
namespace Slic3r {
namespace GUI {
class WebViewPanel : public wxPanel
{
public:
WebViewPanel(wxWindow *parent, const wxString& default_url, const std::vector<std::string>& message_handler_names, const std::string& loading_html = "loading");
virtual ~WebViewPanel();
void load_url(const wxString& url);
void load_default_url_delayed();
void load_error_page();
void on_show(wxShowEvent& evt);
virtual void on_script_message(wxWebViewEvent& evt);
void on_idle(wxIdleEvent& evt);
void on_url(wxCommandEvent& evt);
void on_back_button(wxCommandEvent& evt);
void on_forward_button(wxCommandEvent& evt);
void on_stop_button(wxCommandEvent& evt);
void on_reload_button(wxCommandEvent& evt);
void on_view_source_request(wxCommandEvent& evt);
void on_view_text_request(wxCommandEvent& evt);
void on_tools_clicked(wxCommandEvent& evt);
void on_error(wxWebViewEvent& evt);
void run_script(const wxString& javascript);
void on_run_script_custom(wxCommandEvent& evt);
void on_add_user_script(wxCommandEvent& evt);
void on_set_custom_user_agent(wxCommandEvent& evt);
void on_clear_selection(wxCommandEvent& evt);
void on_delete_selection(wxCommandEvent& evt);
void on_select_all(wxCommandEvent& evt);
void On_enable_context_menu(wxCommandEvent& evt);
void On_enable_dev_tools(wxCommandEvent& evt);
virtual void on_navigation_request(wxWebViewEvent &evt);
wxString get_default_url() const { return m_default_url; }
void set_default_url(const wxString& url) { m_default_url = url; }
virtual void sys_color_changed();
void set_load_default_url_on_next_error(bool val) { m_load_default_url_on_next_error = val; }
protected:
virtual void on_page_will_load();
wxWebView* m_browser { nullptr };
bool m_load_default_url { false };
#ifdef DEBUG_URL_PANEL
wxBoxSizer *bSizer_toolbar;
wxButton * m_button_back;
wxButton * m_button_forward;
wxButton * m_button_stop;
wxButton * m_button_reload;
wxTextCtrl *m_url;
wxButton * m_button_tools;
wxMenu* m_tools_menu;
wxMenuItem* m_script_custom;
wxInfoBar *m_info;
wxStaticText* m_info_text;
wxMenuItem* m_context_menu;
wxMenuItem* m_dev_tools;
#endif
// Last executed JavaScript snippet, for convenience.
wxString m_javascript;
wxString m_response_js;
wxString m_default_url;
std::string m_loading_html;
//DECLARE_EVENT_TABLE()
bool m_load_error_page { false };
bool m_shown { false };
bool m_load_default_url_on_next_error { false };
std::vector<std::string> m_script_message_hadler_names;
};
class WebViewDialog : public DPIDialog
{
public:
WebViewDialog(wxWindow* parent, const wxString& url, const wxString& dialog_name, const wxSize& size, const std::vector<std::string>& message_handler_names, const std::string& loading_html = "loading");
WebViewDialog(wxWindow* parent, const wxString& url, const wxString& dialog_name, const wxSize& size, const std::vector<std::string>& message_handler_names, const std::string& loading_html = "other_loading");
virtual ~WebViewDialog();
virtual void on_show(wxShowEvent& evt) {};
@ -176,87 +90,6 @@ protected:
std::vector<std::string> m_script_message_hadler_names;
};
class ConnectRequestHandler
{
public:
ConnectRequestHandler();
~ConnectRequestHandler();
void handle_message(const std::string& message);
void resend_config();
protected:
// action callbacs stored in m_actions
virtual void on_connect_action_log(const std::string& message_data);
virtual void on_connect_action_error(const std::string& message_data);
virtual void on_connect_action_request_login(const std::string& message_data);
virtual void on_connect_action_request_config(const std::string& message_data);
virtual void on_connect_action_request_open_in_browser(const std::string& message_data);
virtual void on_connect_action_select_printer(const std::string& message_data) = 0;
virtual void on_connect_action_print(const std::string& message_data) = 0;
virtual void on_connect_action_webapp_ready(const std::string& message_data) = 0;
virtual void run_script_bridge(const wxString &script) = 0;
std::map<std::string, std::function<void(const std::string&)>> m_actions;
};
class ConnectWebViewPanel : public WebViewPanel, public ConnectRequestHandler
{
public:
ConnectWebViewPanel(wxWindow* parent);
~ConnectWebViewPanel() override;
void on_script_message(wxWebViewEvent& evt) override;
void logout();
void sys_color_changed() override;
void on_navigation_request(wxWebViewEvent &evt) override;
protected:
void on_connect_action_request_login(const std::string &message_data) override;
void on_connect_action_select_printer(const std::string& message_data) override;
void on_connect_action_print(const std::string& message_data) override;
void on_connect_action_webapp_ready(const std::string& message_data) override {}
void run_script_bridge(const wxString& script) override {run_script(script); }
void on_page_will_load() override;
void on_connect_action_error(const std::string &message_data) override;
private:
static wxString get_login_script(bool refresh);
static wxString get_logout_script();
void on_user_token(UserAccountSuccessEvent& e);
void on_user_logged_out(UserAccountSuccessEvent& e);
bool m_reached_default_url {false};
};
class PrinterWebViewPanel : public WebViewPanel
{
public:
PrinterWebViewPanel(wxWindow* parent, const wxString& default_url);
void on_loaded(wxWebViewEvent& evt);
void send_api_key();
void send_credentials();
void set_api_key(const std::string &key)
{
if (m_api_key != key) {
clear();
m_api_key = key;
}
}
void set_credentials(const std::string &usr, const std::string &psk)
{
if (m_usr != usr || m_psk != psk) {
clear();
m_usr = usr;
m_psk = psk;
}
}
void clear() { m_api_key.clear(); m_usr.clear(); m_psk.clear(); m_api_key_sent = false; }
void sys_color_changed() override;
private:
std::string m_api_key;
std::string m_usr;
std::string m_psk;
bool m_api_key_sent {false};
};
class PrinterPickWebViewDialog : public WebViewDialog, public ConnectRequestHandler
{
public:
@ -271,15 +104,18 @@ protected:
void request_compatible_printers_SLA();
void run_script_bridge(const wxString& script) override { run_script(script); }
void on_dpi_changed(const wxRect &suggested_rect) override;
void on_reload_event(const std::string& message_data) override;
private:
std::string& m_ret_val;
};
class SourceViewDialog : public wxDialog
class PrintablesConnectUploadDialog : public WebViewDialog
{
public:
SourceViewDialog(wxWindow* parent, wxString source);
PrintablesConnectUploadDialog(wxWindow* parent, const std::string url);
protected:
void on_dpi_changed(const wxRect &suggested_rect) override;
};
class LoginWebViewDialog : public WebViewDialog
@ -297,4 +133,4 @@ private:
} // GUI
} // Slic3r
#endif /* slic3r_Tab_hpp_ */
#endif /* slic3r_WebViewDialog_hpp_ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,224 @@
#ifndef slic3r_WebViewPanel_hpp_
#define slic3r_WebViewPanel_hpp_
#include <map>
#include <wx/wx.h>
#include <wx/event.h>
#include "GUI_Utils.hpp"
#include "UserAccountSession.hpp"
#include "ConnectRequestHandler.hpp"
#include "slic3r/Utils/ServiceConfig.hpp"
#ifdef DEBUG_URL_PANEL
#include <wx/infobar.h>
#endif
class wxWebView;
class wxWebViewEvent;
wxDECLARE_EVENT(EVT_OPEN_EXTERNAL_LOGIN, wxCommandEvent);
namespace Slic3r::GUI {
class WebViewPanel : public wxPanel
{
public:
WebViewPanel(wxWindow *parent, const wxString& default_url, const std::vector<std::string>& message_handler_names, const std::string& loading_html, const std::string& error_html, bool do_create);
virtual ~WebViewPanel();
void destroy_browser();
void set_create_browser() {m_do_late_webview_create = true; m_load_default_url = true; }
void load_url(const wxString& url);
void load_default_url_delayed();
void load_error_page();
// Let WebViewPanel do on_show so it can create webview properly
// and load default page
// override after_on_show for more actions in on_show
void on_show(wxShowEvent& evt);
virtual void after_on_show(wxShowEvent& evt) {}
virtual void on_script_message(wxWebViewEvent& evt);
void on_idle(wxIdleEvent& evt);
virtual void on_loaded(wxWebViewEvent& evt);
void on_url(wxCommandEvent& evt);
virtual void on_back_button(wxCommandEvent& evt);
virtual void on_forward_button(wxCommandEvent& evt);
void on_stop_button(wxCommandEvent& evt);
virtual void on_reload_button(wxCommandEvent& evt);
void on_view_source_request(wxCommandEvent& evt);
void on_view_text_request(wxCommandEvent& evt);
void on_tools_clicked(wxCommandEvent& evt);
void on_error(wxWebViewEvent& evt);
void run_script(const wxString& javascript);
void on_run_script_custom(wxCommandEvent& evt);
void on_add_user_script(wxCommandEvent& evt);
void on_set_custom_user_agent(wxCommandEvent& evt);
void on_clear_selection(wxCommandEvent& evt);
void on_delete_selection(wxCommandEvent& evt);
void on_select_all(wxCommandEvent& evt);
void On_enable_context_menu(wxCommandEvent& evt);
void On_enable_dev_tools(wxCommandEvent& evt);
virtual void on_navigation_request(wxWebViewEvent &evt);
virtual wxString get_default_url() const { return m_default_url; }
void set_default_url(const wxString& url) { m_default_url = url; }
virtual void do_reload();
virtual void load_default_url();
virtual void sys_color_changed();
void set_load_default_url_on_next_error(bool val) { m_load_default_url_on_next_error = val; }
protected:
virtual void late_create();
virtual void on_page_will_load();
wxWebView* m_browser { nullptr };
bool m_load_default_url { false };
wxBoxSizer* topsizer;
wxBoxSizer* m_sizer_top;
#ifdef DEBUG_URL_PANEL
wxBoxSizer *bSizer_toolbar;
wxButton * m_button_back;
wxButton * m_button_forward;
wxButton * m_button_stop;
wxButton * m_button_reload;
wxTextCtrl *m_url;
wxButton * m_button_tools;
wxMenu* m_tools_menu;
wxMenuItem* m_script_custom;
wxInfoBar *m_info;
wxStaticText* m_info_text;
wxMenuItem* m_context_menu;
wxMenuItem* m_dev_tools;
#endif
// Last executed JavaScript snippet, for convenience.
wxString m_javascript;
wxString m_response_js;
wxString m_default_url;
bool m_reached_default_url {false};
std::string m_loading_html;
std::string m_error_html;
//DECLARE_EVENT_TABLE()
bool m_load_error_page { false };
bool m_shown { false };
bool m_load_default_url_on_next_error { false };
bool m_do_late_webview_create {false};
std::vector<std::string> m_script_message_hadler_names;
};
class ConnectWebViewPanel : public WebViewPanel, public ConnectRequestHandler
{
public:
ConnectWebViewPanel(wxWindow* parent);
~ConnectWebViewPanel() override;
void on_script_message(wxWebViewEvent& evt) override;
void logout();
void sys_color_changed() override;
void on_navigation_request(wxWebViewEvent &evt) override;
protected:
void late_create() override;
void on_connect_action_request_login(const std::string &message_data) override;
void on_connect_action_select_printer(const std::string& message_data) override;
void on_connect_action_print(const std::string& message_data) override;
void on_connect_action_webapp_ready(const std::string& message_data) override {}
void run_script_bridge(const wxString& script) override {run_script(script); }
void on_page_will_load() override;
void on_connect_action_error(const std::string &message_data) override;
void on_reload_event(const std::string& message_data) override;
private:
static wxString get_login_script(bool refresh);
static wxString get_logout_script();
void on_user_logged_out(UserAccountSuccessEvent& e);
};
class PrinterWebViewPanel : public WebViewPanel
{
public:
PrinterWebViewPanel(wxWindow* parent, const wxString& default_url);
void on_loaded(wxWebViewEvent& evt) override;
void on_script_message(wxWebViewEvent& evt) override;
void send_api_key();
void send_credentials();
void set_api_key(const std::string &key)
{
clear();
m_api_key = key;
}
void set_credentials(const std::string &usr, const std::string &psk)
{
clear();
m_usr = usr;
m_psk = psk;
}
void clear() { m_api_key.clear(); m_usr.clear(); m_psk.clear(); m_api_key_sent = false; }
void sys_color_changed() override;
private:
std::string m_api_key;
std::string m_usr;
std::string m_psk;
bool m_api_key_sent {false};
};
class PrintablesWebViewPanel : public WebViewPanel
{
public:
PrintablesWebViewPanel(wxWindow* parent);
void on_navigation_request(wxWebViewEvent &evt) override;
void on_loaded(wxWebViewEvent& evt) override;
void after_on_show(wxShowEvent& evt) override;
void on_script_message(wxWebViewEvent& evt) override;
void sys_color_changed() override;
void logout(const std::string& override_url = std::string());
void login(const std::string& access_token, const std::string& override_url = std::string());
void send_refreshed_token(const std::string& access_token);
void send_will_refresh();
wxString get_default_url() const override;
void set_next_show_url(const std::string& url) {m_next_show_url = Utils::ServiceConfig::instance().printables_url() + url; }
private:
void handle_message(const std::string& message);
void on_printables_event_access_token_expired(const std::string& message_data);
void on_reload_event(const std::string& message_data);
void on_printables_event_print_gcode(const std::string& message_data);
void on_printables_event_download_file(const std::string& message_data);
void on_printables_event_slice_file(const std::string& message_data);
void on_printables_event_required_login(const std::string& message_data);
void load_default_url() override;
std::string get_url_lang_theme(const wxString& url) const;
void show_download_notification(const std::string& filename);
std::map<std::string, std::function<void(const std::string&)>> m_events;
std::string m_next_show_url;
/*
Eventy Slicer -> Printables
accessTokenWillChange
WebUI zavola event predtim nez udela refresh access tokenu proti Prusa Accountu na Printables to bude znamenat pozastaveni requestu Mobile app muze chtit udelat refresh i bez explicitni predchozi printables zadosti skrz accessTokenExpired event
accessTokenChange
window postMessage JSON stringify { event 'accessTokenChange' token 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVC' }
volani po uspesne rotaci tokenu
historyBack
navigace zpet triggerovana z mobilni aplikace
historyForward
navigace vpred triggerovana z mobilni aplikace
*/
};
} // namespace Slic3r::GUI
#endif /* slic3r_WebViewPanel_hpp_ */

View File

@ -7,5 +7,8 @@ namespace Slic3r::GUI {
void setup_webview_with_credentials(wxWebView* web_view, const std::string& username, const std::string& password);
void remove_webview_credentials(wxWebView* web_view);
void delete_cookies(wxWebView* web_view, const std::string& url);
void add_request_authorization(wxWebView* web_view, const wxString& address, const std::string& token);
void remove_request_authorization(wxWebView* web_view);
void load_request(wxWebView* web_view, const std::string& address, const std::string& token);
}

View File

@ -6,6 +6,7 @@
#include "WebViewPlatformUtils.hpp"
#include <boost/log/trivial.hpp>
#include <libsoup/soup.h>
namespace Slic3r::GUI {
@ -60,7 +61,7 @@ void delete_cookie_callback (GObject* source_object, GAsyncResult* result, void*
{
WebKitCookieManager *cookie_manager = WEBKIT_COOKIE_MANAGER(source_object);
GError* err = nullptr;
gboolean b = webkit_cookie_manager_delete_cookie_finish(cookie_manager, result, &err);
webkit_cookie_manager_delete_cookie_finish(cookie_manager, result, &err);
if (err) {
BOOST_LOG_TRIVIAL(error) << "Error deleting cookies: " << err->message;
g_error_free(err);
@ -107,4 +108,40 @@ void delete_cookies(wxWebView* web_view, const std::string& url)
WebKitCookieManager* cookieManager = webkit_web_context_get_cookie_manager(context);
webkit_cookie_manager_get_cookies(cookieManager, uri, nullptr, (GAsyncReadyCallback)Slic3r::GUI::get_cookie_callback, nullptr);
}
void add_request_authorization(wxWebView* web_view, const wxString& address, const std::string& token)
{
// unused on Linux
assert(true);
}
void remove_request_authorization(wxWebView* web_view)
{
// unused on Linux
assert(true);
}
void load_request(wxWebView* web_view, const std::string& address, const std::string& token)
{
WebKitWebView* native_backend = static_cast<WebKitWebView *>(web_view->GetNativeBackend());
WebKitURIRequest* request = webkit_uri_request_new(address.c_str());
if(!request)
{
BOOST_LOG_TRIVIAL(error) << "load_request failed: request is nullptr. address: " << address;
return;
}
SoupMessageHeaders* soup_headers = webkit_uri_request_get_http_headers(request);
if (!soup_headers)
{
BOOST_LOG_TRIVIAL(error) << "load_request failed: soup_headers is nullptr.";
return;
}
if (!token.empty())
{
soup_message_headers_append(soup_headers, "Authorization", ("External " + token).c_str());
}
// Load the request in the WebView
webkit_web_view_load_request(native_backend, request);
}
}

View File

@ -155,7 +155,27 @@ void delete_cookies(wxWebView* web_view, const std::string& url)
}
}
}];
}
void add_request_authorization(wxWebView* web_view, const wxString& address, const std::string& token)
{
// unused on MacOS
assert(true);
}
void remove_request_authorization(wxWebView* web_view)
{
// unused on MacOS
assert(true);
}
void load_request(wxWebView* web_view, const std::string& address, const std::string& token)
{
WKWebView* backend = static_cast<WKWebView*>(web_view->GetNativeBackend());
NSString *url_string = [NSString stringWithCString:address.c_str() encoding:[NSString defaultCStringEncoding]];
NSString *token_string = [NSString stringWithCString:token.c_str() encoding:[NSString defaultCStringEncoding]];
NSURL *url = [NSURL URLWithString:url_string];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSString *auth_value = [NSString stringWithFormat:@"External %@", token_string];
[request setValue:auth_value forHTTPHeaderField:@"Authorization"];
[backend loadRequest:request];
}
}

View File

@ -135,6 +135,7 @@ void delete_cookies(wxWebView* webview, const std::string& url)
std::string domain = cookie.second.get<std::string>("domain");
// Delete cookie by name and domain
wxString name_and_domain = GUI::format_wxstr(L"{\"name\": \"%1%\", \"domain\": \"%2%\"}", name, domain);
BOOST_LOG_TRIVIAL(debug) << "Deleting cookie: " << name_and_domain;
webView2->CallDevToolsProtocolMethod(L"Network.deleteCookies", name_and_domain.c_str(),
Microsoft::WRL::Callback<ICoreWebView2CallDevToolsProtocolMethodCompletedHandler>(
[](HRESULT errorCode, LPCWSTR resultJson) -> HRESULT { return S_OK; }).Get());
@ -145,6 +146,148 @@ void delete_cookies(wxWebView* webview, const std::string& url)
}
static EventRegistrationToken m_webResourceRequestedTokenForImageBlocking = {};
static wxString filter_patern;
namespace {
void RequestHeadersToLog(ICoreWebView2HttpRequestHeaders* requestHeaders)
{
wxCOMPtr<ICoreWebView2HttpHeadersCollectionIterator> iterator;
requestHeaders->GetIterator(&iterator);
BOOL hasCurrent = FALSE;
BOOST_LOG_TRIVIAL(info) <<"Logging request headers:";
while (SUCCEEDED(iterator->get_HasCurrentHeader(&hasCurrent)) && hasCurrent)
{
wchar_t* name = nullptr;
wchar_t* value = nullptr;
iterator->GetCurrentHeader(&name, &value);
BOOST_LOG_TRIVIAL(debug) <<"name: " << name << L", value: " << value;
if (name) {
CoTaskMemFree(name);
}
if (value) {
CoTaskMemFree(value);
}
BOOL hasNext = FALSE;
iterator->MoveNext(&hasNext);
}
}
}
void add_request_authorization(wxWebView* webview, const wxString& address, const std::string& token)
{
// This function adds a filter so when pattern document is being requested, callback is triggered
// Inside add_WebResourceRequested callback, there is a Authorization header added.
// The filter needs to be removed to stop adding the auth header
ICoreWebView2 *webView2 = static_cast<ICoreWebView2 *>(webview->GetNativeBackend());
if (!webView2) {
return;
}
wxCOMPtr<ICoreWebView2_2> wv2_2;
HRESULT hr = webView2->QueryInterface(IID_PPV_ARGS(&wv2_2));
if (FAILED(hr)) {
return;
}
filter_patern = address + "/*";
webView2->AddWebResourceRequestedFilter( filter_patern.c_str(), COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT);
if (FAILED(webView2->add_WebResourceRequested(
Microsoft::WRL::Callback<ICoreWebView2WebResourceRequestedEventHandler>(
[token](ICoreWebView2 *sender, ICoreWebView2WebResourceRequestedEventArgs *args) {
// Get the web resource request
wxCOMPtr<ICoreWebView2WebResourceRequest> request;
HRESULT hr = args->get_Request(&request);
if (FAILED(hr))
{
BOOST_LOG_TRIVIAL(error) << "Adding request Authorization: Failed to get_Request.";
return S_OK;
}
// Get the request headers
wxCOMPtr<ICoreWebView2HttpRequestHeaders> headers;
hr = request->get_Headers(&headers);
if (FAILED(hr))
{
BOOST_LOG_TRIVIAL(error) << "Adding request Authorization: Failed to get_Headers.";
return S_OK;
}
LPWSTR wideUri = nullptr;
request->get_Uri(&wideUri);
std::wstring ws(wideUri);
std::string val = "External " + token;
// Add or modify the Authorization header
hr = headers->SetHeader(L"Authorization", GUI::from_u8(val).c_str());
BOOST_LOG_TRIVIAL(debug) << "add_WebResourceRequested " << ws;
// This function is only needed for debug purpose
RequestHeadersToLog(headers.Get());
return S_OK;
}
).Get(), &m_webResourceRequestedTokenForImageBlocking
))) {
BOOST_LOG_TRIVIAL(error) << "Adding request Authorization: Failed to add callback.";
}
}
void remove_request_authorization(wxWebView* webview)
{
ICoreWebView2 *webView2 = static_cast<ICoreWebView2 *>(webview->GetNativeBackend());
if (!webView2) {
return;
}
webView2->RemoveWebResourceRequestedFilter(filter_patern.c_str(), COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT);
if(FAILED(webView2->remove_WebResourceRequested( m_webResourceRequestedTokenForImageBlocking))) {
BOOST_LOG_TRIVIAL(error) << "WebView: Failed to remove resources";
}
}
void load_request(wxWebView* web_view, const std::string& address, const std::string& token)
{
// This function should create its own GET request and send it (works on linux)
// For that we would use NavigateWithWebResourceRequest.
// For that we need ICoreWebView2Environment smart pointer.
// Such pointer does exists inside wxWebView edge backend. (wxWebViewEdgeImpl::m_webViewEnvironment)
// But its currently private and not getable. (It wouldn't be such problem to create the getter)
ICoreWebView2 *webView2 = static_cast<ICoreWebView2 *>(web_view->GetNativeBackend());
if (!webView2) {
return;
}
// GetEnviroment does not exists
wxCOMPtr<ICoreWebView2Environment> webViewEnvironment;
//webViewEnvironment = static_cast<ICoreWebView2Environment *>(web_view->GetEnviroment());
if (!webViewEnvironment.Get()) {
return;
}
wxCOMPtr<ICoreWebView2Environment2> webViewEnvironment2;
if (FAILED(webViewEnvironment->QueryInterface(IID_PPV_ARGS(&webViewEnvironment2))))
{
return;
}
wxCOMPtr<ICoreWebView2WebResourceRequest> webResourceRequest;
if (FAILED(webViewEnvironment2->CreateWebResourceRequest(
L"https://www.printables.com/", L"GET", NULL,
L"Content-Type: application/x-www-form-urlencoded", &webResourceRequest)))
{
return;
}
wxCOMPtr<ICoreWebView2_2> wv2_2;
if (FAILED(webView2->QueryInterface(IID_PPV_ARGS(&wv2_2)))) {
return;
}
if (FAILED(wv2_2->NavigateWithWebResourceRequest(webResourceRequest.get())))
{
return;
}
}
} // namespace Slic3r::GUI
#endif // WIN32

View File

@ -12,6 +12,7 @@
// This wrapper also manages implicit conversion from wxString to UTF8 and format_wxstr() variants are provided to format into wxString.
#include <wx/string.h>
#include <ostream>
namespace Slic3r::internal::format {
// Wrapper around wxScopedCharBuffer to indicate that the content is UTF8 formatted.

View File

@ -24,7 +24,9 @@ ServiceConfig::ServiceConfig()
, m_account_url("https://account.prusa3d.com")
, m_account_client_id("oamhmhZez7opFosnwzElIgE2oGgI2iJORSkw587O")
, m_media_url("https://media.printables.com")
, m_preset_repo_url("https://preset-repo-api.prusa3d.com") {
, m_preset_repo_url("https://preset-repo-api.prusa3d.com")
, m_printables_url("https://www.printables.com")
{
#ifdef SLIC3R_REPO_URL
m_preset_repo_url = SLIC3R_REPO_URL;
#endif
@ -34,6 +36,7 @@ ServiceConfig::ServiceConfig()
update_from_env(m_account_client_id, "PRUSA_ACCOUNT_CLIENT_ID");
update_from_env(m_media_url, "PRUSA_MEDIA_URL", true);
update_from_env(m_preset_repo_url, "PRUSA_PRESET_REPO_URL", true);
update_from_env(m_printables_url, "PRUSA_PRINTABLES_URL", true);
}
ServiceConfig& ServiceConfig::instance()

View File

@ -30,6 +30,8 @@ public:
bool webdev_enabled() const { return m_webdev_enabled; }
void set_webdev_enabled(bool enabled) { m_webdev_enabled = enabled; }
const std::string& printables_url() const { return m_printables_url; }
static ServiceConfig& instance();
private:
std::string m_connect_url;
@ -37,6 +39,7 @@ private:
std::string m_account_client_id;
std::string m_media_url;
std::string m_preset_repo_url;
std::string m_printables_url;
bool m_webdev_enabled{false};
};