@ -1,2 +1,2 @@
|
|||||||
name = "yuzu"
|
name = "yuzu"
|
||||||
target_url = "https://raw.githubusercontent.com/yuzu-emu/liftinstall/master/config.windows.v9.toml"
|
target_url = "https://raw.githubusercontent.com/yuzu-emu/liftinstall/master/config.windows.v10.toml"
|
||||||
|
58
config.windows.v10.toml
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
installing_message = "Reminder: yuzu is an <b>experimental</b> emulator. Stuff will break!"
|
||||||
|
hide_advanced = true
|
||||||
|
|
||||||
|
[authentication]
|
||||||
|
# Base64 encoded version of the public key for validating the JWT token. Must be in DER format
|
||||||
|
pub_key_base64 = "MIIBCgKCAQEAs5K6s49JVV9LBMzDrkORsoPSYsv1sCXDtxjp4pn8p0uPSvJAsbNNmdIgCjfSULzbHLM28MblnI4zYP8ZgKtkjdg+Ic5WQbS5iBAkf18zMafpOrotTArLsgZSmUfNYt0SOiN17D+sq/Ov/CKXRM9CttKkEbanBTVqkx7sxsHVbkI6tDvkboSaNeVPHzHlfAbvGrUo5cbAFCB/KnRsoxr+g7jLKTxU1w4xb/pIs91h80AXV/yZPXL6ItPM3/0noIRXjmoeYWf2sFQaFALNB2Kef0p6/hoHYUQP04ZSIL3Q+v13z5X2YJIlI4eLg+iD25QYm9V8oP3+Xro4vd47a0/maQIDAQAB"
|
||||||
|
# URL to authenticate against. This must return a JWT token with their permissions and a custom claim patreonInfo with the following structure
|
||||||
|
# "patreonInfo": { "linked": false, "activeSubscription": false }
|
||||||
|
# If successful, the frontend will use this JWT token as a Bearer Authentication when requesting the binaries to download
|
||||||
|
auth_url = "https://api.yuzu-emu.org/jwt/installer/"
|
||||||
|
[authentication.validation]
|
||||||
|
iss = "citra-core"
|
||||||
|
aud = "installer"
|
||||||
|
|
||||||
|
[[packages]]
|
||||||
|
name = "yuzu Early Access"
|
||||||
|
description = "Preview release with the newest features for the supporters."
|
||||||
|
icon = "thicc_logo_installer__ea_shadow.png"
|
||||||
|
requires_authorization = true
|
||||||
|
# puts a "new" ribbon the package select
|
||||||
|
is_new = true
|
||||||
|
[packages.extended_description]
|
||||||
|
no_action_description = "Thank you for your support!"
|
||||||
|
# Displayed when the package has no authentication for the user
|
||||||
|
need_authentication_description = "Click here to sign in with your yuzu account for Early Access"
|
||||||
|
# Displayed when the package has an authentication, but the user has not linked their account
|
||||||
|
need_link_description = "You are signed in, but you need to link your Patreon account! Click here for more details"
|
||||||
|
# Displayed when the package has an authentication, but the user has not linked their account
|
||||||
|
need_subscription_description = "You are signed in, but you need to link your Patreon account! Click here for more details"
|
||||||
|
# Displayed when the package has an authentication, but the user has not linked their account
|
||||||
|
need_reward_tier_description = "You are signed in, but are not backing an eligible reward tier! Click here for more details"
|
||||||
|
|
||||||
|
[packages.source]
|
||||||
|
name = "patreon"
|
||||||
|
match = "^yuzu-windows-msvc-[0-9]*-[0-9a-f]*.tar.xz$"
|
||||||
|
[packages.source.config]
|
||||||
|
repo = "earlyaccess"
|
||||||
|
[[packages.shortcuts]]
|
||||||
|
name = "yuzu Early Access"
|
||||||
|
relative_path = "yuzu-windows-msvc-early-access/yuzu.exe"
|
||||||
|
description = "Launch yuzu Early Access"
|
||||||
|
|
||||||
|
|
||||||
|
[[packages]]
|
||||||
|
name = "yuzu"
|
||||||
|
description = "Includes frequent updates to yuzu with all the latest reviewed and tested features."
|
||||||
|
icon = "thicc_logo_installer_shadow.png"
|
||||||
|
default = true
|
||||||
|
[packages.source]
|
||||||
|
name = "github"
|
||||||
|
match = "^yuzu-windows-msvc-[0-9]*-[0-9a-f]*.tar.xz$"
|
||||||
|
[packages.source.config]
|
||||||
|
repo = "yuzu-emu/yuzu-mainline"
|
||||||
|
[[packages.shortcuts]]
|
||||||
|
name = "yuzu"
|
||||||
|
relative_path = "yuzu-windows-msvc/yuzu.exe"
|
||||||
|
description = "Launch yuzu"
|
||||||
|
|
@ -1,5 +1,6 @@
|
|||||||
installing_message = "Reminder: yuzu is an <b>experimental</b> emulator. Stuff will break!"
|
installing_message = "Reminder: yuzu is an <b>experimental</b> emulator. Stuff will break!"
|
||||||
hide_advanced = true
|
hide_advanced = true
|
||||||
|
new_tool = "https://github.com/yuzu-emu/liftinstall/releases/download/1.8/yuzu_install.exe"
|
||||||
|
|
||||||
[authentication]
|
[authentication]
|
||||||
# Base64 encoded version of the public key for validating the JWT token. Must be in DER format
|
# Base64 encoded version of the public key for validating the JWT token. Must be in DER format
|
||||||
|
@ -25,6 +25,23 @@ pub struct PackageShortcut {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub relative_path: String,
|
pub relative_path: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub has_desktop_shortcut: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extra description for authentication and authorization state for a package
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct PackageExtendedDescription {
|
||||||
|
#[serde(default)]
|
||||||
|
pub no_action_description: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub need_authentication_description: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub need_link_description: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub need_subscription_description: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub need_reward_tier_description: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes a overview of a individual package.
|
/// Describes a overview of a individual package.
|
||||||
@ -32,6 +49,8 @@ pub struct PackageShortcut {
|
|||||||
pub struct PackageDescription {
|
pub struct PackageDescription {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub icon: Option<String>,
|
||||||
pub default: Option<bool>,
|
pub default: Option<bool>,
|
||||||
pub source: PackageSource,
|
pub source: PackageSource,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@ -41,13 +60,7 @@ pub struct PackageDescription {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub is_new: Option<bool>,
|
pub is_new: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub need_authentication_description: Option<String>,
|
pub extended_description: Option<PackageExtendedDescription>,
|
||||||
#[serde(default)]
|
|
||||||
pub need_link_description: Option<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub need_subscription_description: Option<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub need_reward_tier_description: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration for validating the JWT token
|
/// Configuration for validating the JWT token
|
||||||
|
@ -34,7 +34,10 @@ pub fn file_from_string(file_path: &str) -> Option<(String, &'static [u8])> {
|
|||||||
file_path,
|
file_path,
|
||||||
"/index.html",
|
"/index.html",
|
||||||
"/favicon.ico",
|
"/favicon.ico",
|
||||||
"/img/logo.png",
|
"/img/light_mode_installer_logo.png",
|
||||||
|
"/img/dark_mode_installer_logo.png",
|
||||||
|
"/thicc_logo_installer__ea_shadow.png",
|
||||||
|
"/thicc_logo_installer_shadow.png",
|
||||||
"/img/how-to-open.png",
|
"/img/how-to-open.png",
|
||||||
"/css/app.css",
|
"/css/app.css",
|
||||||
"/css/chunk-vendors.css",
|
"/css/chunk-vendors.css",
|
||||||
|
@ -138,7 +138,7 @@ pub fn validate_token(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Configure validation for audience and issuer if the configuration provides it
|
// Configure validation for audience and issuer if the configuration provides it
|
||||||
let validation = match validation {
|
let mut validation = match validation {
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
let mut valid = Validation::new(Algorithm::RS256);
|
let mut valid = Validation::new(Algorithm::RS256);
|
||||||
valid.iss = v.iss;
|
valid.iss = v.iss;
|
||||||
@ -149,7 +149,8 @@ pub fn validate_token(
|
|||||||
}
|
}
|
||||||
None => Validation::default(),
|
None => Validation::default(),
|
||||||
};
|
};
|
||||||
|
validation.validate_exp = false;
|
||||||
|
validation.validate_nbf = false;
|
||||||
// Verify the JWT token
|
// Verify the JWT token
|
||||||
decode::<JWTClaims>(&body, pub_key.as_slice(), &validation)
|
decode::<JWTClaims>(&body, pub_key.as_slice(), &validation)
|
||||||
.map(|tok| tok.claims)
|
.map(|tok| tok.claims)
|
||||||
|
@ -2,18 +2,15 @@
|
|||||||
//!
|
//!
|
||||||
//! The /api/exit closes down the application.
|
//! The /api/exit closes down the application.
|
||||||
|
|
||||||
use frontend::rest::services::default_future;
|
use frontend::rest::services::Future as InternalFuture;
|
||||||
use frontend::rest::services::Future;
|
use frontend::rest::services::{default_future, Request, Response, WebService};
|
||||||
use frontend::rest::services::Request;
|
|
||||||
use frontend::rest::services::Response;
|
|
||||||
use frontend::rest::services::WebService;
|
|
||||||
|
|
||||||
use hyper::header::ContentType;
|
use hyper::header::ContentType;
|
||||||
use hyper::StatusCode;
|
use hyper::StatusCode;
|
||||||
|
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
pub fn handle(service: &WebService, _req: Request) -> Future {
|
pub fn handle(service: &WebService, _req: Request) -> InternalFuture {
|
||||||
match service.get_framework_write().shutdown() {
|
match service.get_framework_write().shutdown() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
exit(0);
|
exit(0);
|
||||||
|
@ -28,12 +28,17 @@ pub fn handle(service: &WebService, req: Request) -> Future {
|
|||||||
|
|
||||||
let mut to_install = Vec::new();
|
let mut to_install = Vec::new();
|
||||||
let mut path: Option<String> = None;
|
let mut path: Option<String> = None;
|
||||||
|
let mut install_desktop_shortcut= false;
|
||||||
|
|
||||||
// Transform results into just an array of stuff to install
|
// Transform results into just an array of stuff to install
|
||||||
for (key, value) in &results {
|
for (key, value) in &results {
|
||||||
if key == "path" {
|
if key == "path" {
|
||||||
path = Some(value.to_owned());
|
path = Some(value.to_owned());
|
||||||
continue;
|
continue;
|
||||||
|
} else if key == "installDesktopShortcut" {
|
||||||
|
info!("Found installDesktopShortcut {:?}", value);
|
||||||
|
install_desktop_shortcut = value == "true";
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if value == "true" {
|
if value == "true" {
|
||||||
@ -55,7 +60,7 @@ pub fn handle(service: &WebService, req: Request) -> Future {
|
|||||||
framework.set_install_dir(&path);
|
framework.set_install_dir(&path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(v) = framework.install(to_install, &sender, new_install) {
|
if let Err(v) = framework.install(to_install, &sender, new_install, install_desktop_shortcut) {
|
||||||
error!("Install error occurred: {:?}", v);
|
error!("Install error occurred: {:?}", v);
|
||||||
if let Err(v) = sender.send(InstallMessage::Error(v)) {
|
if let Err(v) = sender.send(InstallMessage::Error(v)) {
|
||||||
error!("Failed to send install error: {:?}", v);
|
error!("Failed to send install error: {:?}", v);
|
||||||
|
@ -136,7 +136,7 @@ impl Service for WebService {
|
|||||||
(Method::Get, "/api/config") => config::handle(self, req),
|
(Method::Get, "/api/config") => config::handle(self, req),
|
||||||
(Method::Get, "/api/dark-mode") => dark_mode::handle(self, req),
|
(Method::Get, "/api/dark-mode") => dark_mode::handle(self, req),
|
||||||
(Method::Get, "/api/default-path") => default_path::handle(self, req),
|
(Method::Get, "/api/default-path") => default_path::handle(self, req),
|
||||||
(Method::Get, "/api/exit") => exit::handle(self, req),
|
(Method::Post, "/api/exit") => exit::handle(self, req),
|
||||||
(Method::Get, "/api/packages") => packages::handle(self, req),
|
(Method::Get, "/api/packages") => packages::handle(self, req),
|
||||||
(Method::Get, "/api/installation-status") => installation_status::handle(self, req),
|
(Method::Get, "/api/installation-status") => installation_status::handle(self, req),
|
||||||
(Method::Post, "/api/check-auth") => authentication::handle(self, req),
|
(Method::Post, "/api/check-auth") => authentication::handle(self, req),
|
||||||
|
@ -179,6 +179,7 @@ impl InstallerFramework {
|
|||||||
items: Vec<String>,
|
items: Vec<String>,
|
||||||
messages: &Sender<InstallMessage>,
|
messages: &Sender<InstallMessage>,
|
||||||
fresh_install: bool,
|
fresh_install: bool,
|
||||||
|
create_desktop_shortcuts: bool,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
info!(
|
info!(
|
||||||
"Framework: Installing {:?} to {:?}",
|
"Framework: Installing {:?} to {:?}",
|
||||||
@ -207,6 +208,7 @@ impl InstallerFramework {
|
|||||||
items,
|
items,
|
||||||
uninstall_items,
|
uninstall_items,
|
||||||
fresh_install,
|
fresh_install,
|
||||||
|
create_desktop_shortcuts,
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut tree = DependencyTree::build(task);
|
let mut tree = DependencyTree::build(task);
|
||||||
|
31
src/main.rs
@ -65,11 +65,13 @@ mod tasks;
|
|||||||
use installer::InstallerFramework;
|
use installer::InstallerFramework;
|
||||||
|
|
||||||
use logging::LoggingErrors;
|
use logging::LoggingErrors;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use clap::App;
|
use clap::App;
|
||||||
use clap::Arg;
|
use clap::Arg;
|
||||||
|
|
||||||
use config::BaseAttributes;
|
use config::BaseAttributes;
|
||||||
|
use std::process::{Command, Stdio, exit};
|
||||||
|
|
||||||
static RAW_CONFIG: &'static str = include_str!(concat!(env!("OUT_DIR"), "/bootstrap.toml"));
|
static RAW_CONFIG: &'static str = include_str!(concat!(env!("OUT_DIR"), "/bootstrap.toml"));
|
||||||
|
|
||||||
@ -106,12 +108,12 @@ fn main() {
|
|||||||
|
|
||||||
info!("{} installer", app_name);
|
info!("{} installer", app_name);
|
||||||
|
|
||||||
// Handle self-updating if needed
|
|
||||||
let current_exe = std::env::current_exe().log_expect("Current executable could not be found");
|
let current_exe = std::env::current_exe().log_expect("Current executable could not be found");
|
||||||
let current_path = current_exe
|
let current_path = current_exe
|
||||||
.parent()
|
.parent()
|
||||||
.log_expect("Parent directory of executable could not be found");
|
.log_expect("Parent directory of executable could not be found");
|
||||||
|
|
||||||
|
// Handle self-updating if needed
|
||||||
self_update::perform_swap(¤t_exe, matches.value_of("swap"));
|
self_update::perform_swap(¤t_exe, matches.value_of("swap"));
|
||||||
if let Some(new_matches) = self_update::check_args(reinterpret_app, current_path) {
|
if let Some(new_matches) = self_update::check_args(reinterpret_app, current_path) {
|
||||||
matches = new_matches;
|
matches = new_matches;
|
||||||
@ -119,15 +121,42 @@ fn main() {
|
|||||||
self_update::cleanup(current_path);
|
self_update::cleanup(current_path);
|
||||||
|
|
||||||
// Load in metadata + setup the installer framework
|
// Load in metadata + setup the installer framework
|
||||||
|
let mut fresh_install = false;
|
||||||
let metadata_file = current_path.join("metadata.json");
|
let metadata_file = current_path.join("metadata.json");
|
||||||
let mut framework = if metadata_file.exists() {
|
let mut framework = if metadata_file.exists() {
|
||||||
info!("Using pre-existing metadata file: {:?}", metadata_file);
|
info!("Using pre-existing metadata file: {:?}", metadata_file);
|
||||||
InstallerFramework::new_with_db(config, current_path).log_expect("Unable to parse metadata")
|
InstallerFramework::new_with_db(config, current_path).log_expect("Unable to parse metadata")
|
||||||
} else {
|
} else {
|
||||||
info!("Starting fresh install");
|
info!("Starting fresh install");
|
||||||
|
fresh_install = true;
|
||||||
InstallerFramework::new(config)
|
InstallerFramework::new(config)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// check for existing installs if we are running as a fresh install
|
||||||
|
let installed_path = PathBuf::from(framework.get_default_path().unwrap());
|
||||||
|
if fresh_install && installed_path.join("metadata.json").exists() {
|
||||||
|
info!("Existing install detected! Trying to launch this install instead");
|
||||||
|
// Generate installer path
|
||||||
|
let platform_extension = if cfg!(windows) {
|
||||||
|
"maintenancetool.exe"
|
||||||
|
} else {
|
||||||
|
"maintenancetool"
|
||||||
|
};
|
||||||
|
let existing = installed_path.join(platform_extension).into_os_string().into_string();
|
||||||
|
if existing.is_ok() {
|
||||||
|
info!("Launching {:?}", existing);
|
||||||
|
let success = Command::new(existing.unwrap())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.spawn();
|
||||||
|
if success.is_ok() {
|
||||||
|
exit(0);
|
||||||
|
} else {
|
||||||
|
error!("Unable to start existing yuzu maintenance tool. Launching old one instead");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let is_launcher = if let Some(string) = matches.value_of("launcher") {
|
let is_launcher = if let Some(string) = matches.value_of("launcher") {
|
||||||
framework.is_launcher = true;
|
framework.is_launcher = true;
|
||||||
framework.launcher_path = Some(string.to_string());
|
framework.launcher_path = Some(string.to_string());
|
||||||
|
@ -43,7 +43,8 @@ extern "C" int saveShortcut(
|
|||||||
const wchar_t *description,
|
const wchar_t *description,
|
||||||
const wchar_t *path,
|
const wchar_t *path,
|
||||||
const wchar_t *args,
|
const wchar_t *args,
|
||||||
const wchar_t *workingDir)
|
const wchar_t *workingDir,
|
||||||
|
const wchar_t *exePath)
|
||||||
{
|
{
|
||||||
const char *errStr = NULL;
|
const char *errStr = NULL;
|
||||||
HRESULT h;
|
HRESULT h;
|
||||||
@ -82,6 +83,9 @@ extern "C" int saveShortcut(
|
|||||||
shellLink->SetDescription(description);
|
shellLink->SetDescription(description);
|
||||||
if (path != NULL)
|
if (path != NULL)
|
||||||
shellLink->SetPath(path);
|
shellLink->SetPath(path);
|
||||||
|
// default to using the first icon in the exe (usually correct)
|
||||||
|
if (exePath != NULL)
|
||||||
|
shellLink->SetIconLocation(exePath, 0);
|
||||||
if (args != NULL)
|
if (args != NULL)
|
||||||
shellLink->SetArguments(args);
|
shellLink->SetArguments(args);
|
||||||
if (workingDir != NULL)
|
if (workingDir != NULL)
|
||||||
@ -95,6 +99,10 @@ extern "C" int saveShortcut(
|
|||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify that a new shortcut was created using the shell api
|
||||||
|
SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, shortcutPath, NULL);
|
||||||
|
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, shortcutPath, NULL);
|
||||||
|
|
||||||
persistFile->Release();
|
persistFile->Release();
|
||||||
shellLink->Release();
|
shellLink->Release();
|
||||||
CoUninitialize();
|
CoUninitialize();
|
||||||
@ -160,3 +168,15 @@ extern "C" HRESULT getSystemFolder(wchar_t *out_path)
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" HRESULT getDesktopFolder(wchar_t *out_path)
|
||||||
|
{
|
||||||
|
PWSTR path = NULL;
|
||||||
|
HRESULT result = SHGetKnownFolderPath(FOLDERID_Desktop, 0, NULL, &path);
|
||||||
|
if (result == S_OK)
|
||||||
|
{
|
||||||
|
wcscpy_s(out_path, MAX_PATH + 1, path);
|
||||||
|
CoTaskMemFree(path);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
@ -30,7 +30,7 @@ mod natives {
|
|||||||
HANDLE, PROCESS_QUERY_INFORMATION, PROCESS_TERMINATE, PROCESS_VM_READ,
|
HANDLE, PROCESS_QUERY_INFORMATION, PROCESS_TERMINATE, PROCESS_VM_READ,
|
||||||
};
|
};
|
||||||
|
|
||||||
use widestring::U16CString;
|
use widestring::{U16CString};
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn saveShortcut(
|
pub fn saveShortcut(
|
||||||
@ -39,6 +39,7 @@ mod natives {
|
|||||||
path: *const winapi::ctypes::wchar_t,
|
path: *const winapi::ctypes::wchar_t,
|
||||||
args: *const winapi::ctypes::wchar_t,
|
args: *const winapi::ctypes::wchar_t,
|
||||||
workingDir: *const winapi::ctypes::wchar_t,
|
workingDir: *const winapi::ctypes::wchar_t,
|
||||||
|
exePath: *const winapi::ctypes::wchar_t,
|
||||||
) -> ::std::os::raw::c_int;
|
) -> ::std::os::raw::c_int;
|
||||||
|
|
||||||
pub fn isDarkThemeActive() -> ::std::os::raw::c_uint;
|
pub fn isDarkThemeActive() -> ::std::os::raw::c_uint;
|
||||||
@ -49,6 +50,28 @@ mod natives {
|
|||||||
) -> ::std::os::raw::c_int;
|
) -> ::std::os::raw::c_int;
|
||||||
|
|
||||||
pub fn getSystemFolder(out_path: *mut ::std::os::raw::c_ushort) -> HRESULT;
|
pub fn getSystemFolder(out_path: *mut ::std::os::raw::c_ushort) -> HRESULT;
|
||||||
|
|
||||||
|
pub fn getDesktopFolder(out_path: *mut ::std::os::raw::c_ushort) -> HRESULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Needed here for Windows interop
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
pub fn create_desktop_shortcut(
|
||||||
|
name: &str,
|
||||||
|
description: &str,
|
||||||
|
target: &str,
|
||||||
|
args: &str,
|
||||||
|
working_dir: &str,
|
||||||
|
exe_path: &str,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let mut cmd_path = [0u16; MAX_PATH + 1];
|
||||||
|
let _result = unsafe { getDesktopFolder(cmd_path.as_mut_ptr()) };
|
||||||
|
let source_path = format!(
|
||||||
|
"{}\\{}.lnk",
|
||||||
|
String::from_utf16_lossy(&cmd_path[..count_u16(&cmd_path)]).as_str(),
|
||||||
|
name
|
||||||
|
);
|
||||||
|
create_shortcut_inner(source_path, name, description, target, args, working_dir, exe_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Needed here for Windows interop
|
// Needed here for Windows interop
|
||||||
@ -59,12 +82,27 @@ mod natives {
|
|||||||
target: &str,
|
target: &str,
|
||||||
args: &str,
|
args: &str,
|
||||||
working_dir: &str,
|
working_dir: &str,
|
||||||
|
exe_path: &str,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let source_file = format!(
|
let source_file = format!(
|
||||||
"{}\\Microsoft\\Windows\\Start Menu\\Programs\\{}.lnk",
|
"{}\\Microsoft\\Windows\\Start Menu\\Programs\\{}.lnk",
|
||||||
env::var("APPDATA").log_expect("APPDATA is bad, apparently"),
|
env::var("APPDATA").log_expect("APPDATA is bad, apparently"),
|
||||||
name
|
name
|
||||||
);
|
);
|
||||||
|
create_shortcut_inner(source_file, name, description, target, args, working_dir, exe_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Needed here for Windows interop
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
fn create_shortcut_inner(
|
||||||
|
source_file: String,
|
||||||
|
_name: &str,
|
||||||
|
description: &str,
|
||||||
|
target: &str,
|
||||||
|
args: &str,
|
||||||
|
working_dir: &str,
|
||||||
|
exe_path: &str,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
|
||||||
info!("Generating shortcut @ {:?}", source_file);
|
info!("Generating shortcut @ {:?}", source_file);
|
||||||
|
|
||||||
@ -78,6 +116,8 @@ mod natives {
|
|||||||
U16CString::from_str(args).log_expect("Error while converting to wchar_t");
|
U16CString::from_str(args).log_expect("Error while converting to wchar_t");
|
||||||
let native_working_dir =
|
let native_working_dir =
|
||||||
U16CString::from_str(working_dir).log_expect("Error while converting to wchar_t");
|
U16CString::from_str(working_dir).log_expect("Error while converting to wchar_t");
|
||||||
|
let native_exe_path =
|
||||||
|
U16CString::from_str(exe_path).log_expect("Error while converting to wchar_t");
|
||||||
|
|
||||||
let shortcutResult = unsafe {
|
let shortcutResult = unsafe {
|
||||||
saveShortcut(
|
saveShortcut(
|
||||||
@ -86,6 +126,7 @@ mod natives {
|
|||||||
native_target.as_ptr(),
|
native_target.as_ptr(),
|
||||||
native_args.as_ptr(),
|
native_args.as_ptr(),
|
||||||
native_working_dir.as_ptr(),
|
native_working_dir.as_ptr(),
|
||||||
|
native_exe_path.as_ptr(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -98,6 +139,17 @@ mod natives {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn count_u16(u16str: &[u16]) -> usize {
|
||||||
|
let mut pos = 0;
|
||||||
|
for x in u16str.iter() {
|
||||||
|
if *x == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos += 1;
|
||||||
|
}
|
||||||
|
pos
|
||||||
|
}
|
||||||
|
|
||||||
/// Cleans up the installer
|
/// Cleans up the installer
|
||||||
pub fn burn_on_exit(app_name: &str) {
|
pub fn burn_on_exit(app_name: &str) {
|
||||||
let current_exe = env::current_exe().log_expect("Current executable could not be found");
|
let current_exe = env::current_exe().log_expect("Current executable could not be found");
|
||||||
@ -127,20 +179,13 @@ mod natives {
|
|||||||
let spawn_result: i32 = unsafe {
|
let spawn_result: i32 = unsafe {
|
||||||
let mut cmd_path = [0u16; MAX_PATH + 1];
|
let mut cmd_path = [0u16; MAX_PATH + 1];
|
||||||
let result = getSystemFolder(cmd_path.as_mut_ptr());
|
let result = getSystemFolder(cmd_path.as_mut_ptr());
|
||||||
let mut pos = 0;
|
|
||||||
for x in cmd_path.iter() {
|
|
||||||
if *x == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
pos += 1;
|
|
||||||
}
|
|
||||||
if result != winapi::shared::winerror::S_OK {
|
if result != winapi::shared::winerror::S_OK {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
spawnDetached(
|
spawnDetached(
|
||||||
U16CString::from_str(
|
U16CString::from_str(
|
||||||
format!("{}\\cmd.exe", String::from_utf16_lossy(&cmd_path[..pos])).as_str(),
|
format!("{}\\cmd.exe", String::from_utf16_lossy(&cmd_path[..count_u16(&cmd_path)])).as_str(),
|
||||||
)
|
)
|
||||||
.log_expect("Unable to convert string to wchar_t")
|
.log_expect("Unable to convert string to wchar_t")
|
||||||
.as_ptr(),
|
.as_ptr(),
|
||||||
|
@ -22,7 +22,7 @@ impl Task for CheckAuthorizationTask {
|
|||||||
) -> Result<TaskParamType, String> {
|
) -> Result<TaskParamType, String> {
|
||||||
assert_eq!(input.len(), 1);
|
assert_eq!(input.len(), 1);
|
||||||
|
|
||||||
let params = input.pop().log_expect("Should have input from resolver!");
|
let params = input.pop().log_expect("Check Authorization Task should have input from resolver!");
|
||||||
let (version, file) = match params {
|
let (version, file) = match params {
|
||||||
TaskParamType::File(v, f) => Ok((v, f)),
|
TaskParamType::File(v, f) => Ok((v, f)),
|
||||||
_ => Err("Unexpected TaskParamType in CheckAuthorization: {:?}"),
|
_ => Err("Unexpected TaskParamType in CheckAuthorization: {:?}"),
|
||||||
|
@ -24,7 +24,7 @@ impl Task for DownloadPackageTask {
|
|||||||
) -> Result<TaskParamType, String> {
|
) -> Result<TaskParamType, String> {
|
||||||
assert_eq!(input.len(), 1);
|
assert_eq!(input.len(), 1);
|
||||||
|
|
||||||
let file = input.pop().log_expect("Should have input from resolver!");
|
let file = input.pop().log_expect("Download Package Task should have input from resolver!");
|
||||||
let (version, file, auth) = match file {
|
let (version, file, auth) = match file {
|
||||||
TaskParamType::Authentication(v, f, auth) => (v, f, auth),
|
TaskParamType::Authentication(v, f, auth) => (v, f, auth),
|
||||||
_ => return Err("Unexpected param type to download package".to_string()),
|
_ => return Err("Unexpected param type to download package".to_string()),
|
||||||
|
@ -8,6 +8,7 @@ use tasks::install_global_shortcut::InstallGlobalShortcutsTask;
|
|||||||
use tasks::install_pkg::InstallPackageTask;
|
use tasks::install_pkg::InstallPackageTask;
|
||||||
use tasks::save_executable::SaveExecutableTask;
|
use tasks::save_executable::SaveExecutableTask;
|
||||||
use tasks::uninstall_pkg::UninstallPackageTask;
|
use tasks::uninstall_pkg::UninstallPackageTask;
|
||||||
|
use tasks::launch_installed_on_exit::LaunchOnExitTask;
|
||||||
|
|
||||||
use tasks::Task;
|
use tasks::Task;
|
||||||
use tasks::TaskDependency;
|
use tasks::TaskDependency;
|
||||||
@ -19,6 +20,7 @@ pub struct InstallTask {
|
|||||||
pub items: Vec<String>,
|
pub items: Vec<String>,
|
||||||
pub uninstall_items: Vec<String>,
|
pub uninstall_items: Vec<String>,
|
||||||
pub fresh_install: bool,
|
pub fresh_install: bool,
|
||||||
|
pub create_desktop_shortcuts: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Task for InstallTask {
|
impl Task for InstallTask {
|
||||||
@ -60,7 +62,7 @@ impl Task for InstallTask {
|
|||||||
for item in &self.items {
|
for item in &self.items {
|
||||||
elements.push(TaskDependency::build(
|
elements.push(TaskDependency::build(
|
||||||
TaskOrdering::Pre,
|
TaskOrdering::Pre,
|
||||||
Box::new(InstallPackageTask { name: item.clone() }),
|
Box::new(InstallPackageTask { name: item.clone(), create_desktop_shortcuts: self.create_desktop_shortcuts }),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +76,11 @@ impl Task for InstallTask {
|
|||||||
TaskOrdering::Pre,
|
TaskOrdering::Pre,
|
||||||
Box::new(InstallGlobalShortcutsTask {}),
|
Box::new(InstallGlobalShortcutsTask {}),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
elements.push(TaskDependency::build(
|
||||||
|
TaskOrdering::Post,
|
||||||
|
Box::new(LaunchOnExitTask {})
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
elements
|
elements
|
||||||
|
113
src/tasks/install_desktop_shortcut.rs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
//! Generates shortcuts for a specified file.
|
||||||
|
|
||||||
|
use installer::InstallerFramework;
|
||||||
|
|
||||||
|
use tasks::Task;
|
||||||
|
use tasks::TaskDependency;
|
||||||
|
use tasks::TaskMessage;
|
||||||
|
use tasks::TaskParamType;
|
||||||
|
|
||||||
|
use config::PackageDescription;
|
||||||
|
|
||||||
|
use logging::LoggingErrors;
|
||||||
|
|
||||||
|
use native::create_desktop_shortcut;
|
||||||
|
|
||||||
|
pub struct InstallDesktopShortcutTask {
|
||||||
|
pub name: String,
|
||||||
|
pub should_run: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Task for InstallDesktopShortcutTask {
|
||||||
|
fn execute(
|
||||||
|
&mut self,
|
||||||
|
_: Vec<TaskParamType>,
|
||||||
|
context: &mut InstallerFramework,
|
||||||
|
messenger: &dyn Fn(&TaskMessage),
|
||||||
|
) -> Result<TaskParamType, String> {
|
||||||
|
if !self.should_run {
|
||||||
|
return Ok(TaskParamType::GeneratedShortcuts(Vec::new()));
|
||||||
|
}
|
||||||
|
|
||||||
|
messenger(&TaskMessage::DisplayMessage(
|
||||||
|
&format!("Generating desktop shortcuts for package {:?}...", self.name),
|
||||||
|
0.0,
|
||||||
|
));
|
||||||
|
|
||||||
|
let path = context
|
||||||
|
.install_path
|
||||||
|
.as_ref()
|
||||||
|
.log_expect("No install path specified");
|
||||||
|
|
||||||
|
let starting_dir = path
|
||||||
|
.to_str()
|
||||||
|
.log_expect("Unable to build shortcut metadata (startingdir)");
|
||||||
|
|
||||||
|
let mut installed_files = Vec::new();
|
||||||
|
|
||||||
|
let mut metadata: Option<PackageDescription> = None;
|
||||||
|
for description in &context
|
||||||
|
.config
|
||||||
|
.as_ref()
|
||||||
|
.log_expect("Should have packages by now")
|
||||||
|
.packages
|
||||||
|
{
|
||||||
|
if self.name == description.name {
|
||||||
|
metadata = Some(description.clone());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let package = match metadata {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return Err(format!("Package {:?} could not be found.", self.name)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate installer path
|
||||||
|
let platform_extension = if cfg!(windows) {
|
||||||
|
"maintenancetool.exe"
|
||||||
|
} else {
|
||||||
|
"maintenancetool"
|
||||||
|
};
|
||||||
|
|
||||||
|
for shortcut in package.shortcuts {
|
||||||
|
let tool_path = path.join(platform_extension);
|
||||||
|
let tool_path = tool_path
|
||||||
|
.to_str()
|
||||||
|
.log_expect("Unable to build shortcut metadata (tool)");
|
||||||
|
|
||||||
|
let exe_path = path.join(shortcut.relative_path);
|
||||||
|
let exe_path = exe_path
|
||||||
|
.to_str()
|
||||||
|
.log_expect("Unable to build shortcut metadata (exe)");
|
||||||
|
|
||||||
|
installed_files.push(create_desktop_shortcut(
|
||||||
|
&shortcut.name,
|
||||||
|
&shortcut.description,
|
||||||
|
tool_path,
|
||||||
|
// TODO: Send by list
|
||||||
|
&format!("--launcher \"{}\"", exe_path),
|
||||||
|
&starting_dir,
|
||||||
|
exe_path,
|
||||||
|
)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the installed packages shortcuts information in the database
|
||||||
|
let packages = &mut context.database.packages;
|
||||||
|
for pack in packages {
|
||||||
|
if pack.name == self.name {
|
||||||
|
pack.shortcuts.append(&mut installed_files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(TaskParamType::GeneratedShortcuts(installed_files))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dependencies(&self) -> Vec<TaskDependency> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> String {
|
||||||
|
format!("InstallDesktopShortcutTask (for {:?}, should_run = {:?})", self.name, self.should_run)
|
||||||
|
}
|
||||||
|
}
|
@ -58,6 +58,7 @@ impl Task for InstallGlobalShortcutsTask {
|
|||||||
// TODO: Send by list
|
// TODO: Send by list
|
||||||
"",
|
"",
|
||||||
&starting_dir,
|
&starting_dir,
|
||||||
|
"",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if !shortcut_file.is_empty() {
|
if !shortcut_file.is_empty() {
|
||||||
|
@ -24,9 +24,11 @@ use archives;
|
|||||||
|
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use tasks::install_desktop_shortcut::InstallDesktopShortcutTask;
|
||||||
|
|
||||||
pub struct InstallPackageTask {
|
pub struct InstallPackageTask {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub create_desktop_shortcuts: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Task for InstallPackageTask {
|
impl Task for InstallPackageTask {
|
||||||
@ -66,20 +68,15 @@ impl Task for InstallPackageTask {
|
|||||||
None => return Err(format!("Package {:?} could not be found.", self.name)),
|
None => return Err(format!("Package {:?} could not be found.", self.name)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Grab data from the shortcut generator
|
// Ignore input from the uninstaller - no useful information passed
|
||||||
let shortcuts = input.pop().log_expect("Should have input from resolver!");
|
// If a previous task Breaks, then just early exit
|
||||||
let shortcuts = match shortcuts {
|
match input.pop().log_expect("Install Package Task should have guaranteed output!") {
|
||||||
TaskParamType::GeneratedShortcuts(files) => files,
|
|
||||||
// If the resolver returned early, we need to unwind
|
|
||||||
TaskParamType::Break => return Ok(TaskParamType::None),
|
TaskParamType::Break => return Ok(TaskParamType::None),
|
||||||
_ => return Err("Unexpected shortcuts param type to install package".to_string()),
|
_ => (),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ignore input from the uninstaller - no useful information passed
|
|
||||||
input.pop();
|
|
||||||
|
|
||||||
// Grab data from the resolver
|
// Grab data from the resolver
|
||||||
let data = input.pop().log_expect("Should have input from resolver!");
|
let data = input.pop().log_expect("Install Package Task should have input from resolver!");
|
||||||
let (version, file, data) = match data {
|
let (version, file, data) = match data {
|
||||||
TaskParamType::FileContents(version, file, data) => (version, file, data),
|
TaskParamType::FileContents(version, file, data) => (version, file, data),
|
||||||
_ => return Err("Unexpected file contents param type to install package".to_string()),
|
_ => return Err("Unexpected file contents param type to install package".to_string()),
|
||||||
@ -170,7 +167,7 @@ impl Task for InstallPackageTask {
|
|||||||
context.database.packages.push(LocalInstallation {
|
context.database.packages.push(LocalInstallation {
|
||||||
name: package.name.to_owned(),
|
name: package.name.to_owned(),
|
||||||
version,
|
version,
|
||||||
shortcuts,
|
shortcuts: Vec::new(),
|
||||||
files: installed_files,
|
files: installed_files,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -195,11 +192,18 @@ impl Task for InstallPackageTask {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
TaskDependency::build(
|
TaskDependency::build(
|
||||||
TaskOrdering::Pre,
|
TaskOrdering::Post,
|
||||||
Box::new(InstallShortcutsTask {
|
Box::new(InstallShortcutsTask {
|
||||||
name: self.name.clone(),
|
name: self.name.clone(),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
TaskDependency::build(
|
||||||
|
TaskOrdering::Post,
|
||||||
|
Box::new(InstallDesktopShortcutTask {
|
||||||
|
name: self.name.clone(),
|
||||||
|
should_run: self.create_desktop_shortcuts
|
||||||
|
}),
|
||||||
|
),
|
||||||
TaskDependency::build(TaskOrdering::Post, Box::new(SaveDatabaseTask {})),
|
TaskDependency::build(TaskOrdering::Post, Box::new(SaveDatabaseTask {})),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -83,9 +83,18 @@ impl Task for InstallShortcutsTask {
|
|||||||
// TODO: Send by list
|
// TODO: Send by list
|
||||||
&format!("--launcher \"{}\"", exe_path),
|
&format!("--launcher \"{}\"", exe_path),
|
||||||
&starting_dir,
|
&starting_dir,
|
||||||
|
exe_path,
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the installed packages shortcuts information in the database
|
||||||
|
let packages = &mut context.database.packages;
|
||||||
|
for pack in packages {
|
||||||
|
if pack.name == self.name {
|
||||||
|
pack.shortcuts.append(&mut installed_files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(TaskParamType::GeneratedShortcuts(installed_files))
|
Ok(TaskParamType::GeneratedShortcuts(installed_files))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
74
src/tasks/launch_installed_on_exit.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
//! Configures lift to launch the new package on fresh install after its closed
|
||||||
|
//! If theres multiple launchable packages, then choose the first listed in config
|
||||||
|
//! If there are multiple shortcuts for the first package, then launch the first.
|
||||||
|
|
||||||
|
use installer::InstallerFramework;
|
||||||
|
|
||||||
|
use tasks::Task;
|
||||||
|
use tasks::TaskDependency;
|
||||||
|
use tasks::TaskMessage;
|
||||||
|
use tasks::TaskParamType;
|
||||||
|
|
||||||
|
use config::PackageDescription;
|
||||||
|
|
||||||
|
use logging::LoggingErrors;
|
||||||
|
|
||||||
|
pub struct LaunchOnExitTask {}
|
||||||
|
|
||||||
|
impl Task for LaunchOnExitTask {
|
||||||
|
|
||||||
|
fn execute(
|
||||||
|
&mut self,
|
||||||
|
_: Vec<TaskParamType>,
|
||||||
|
context: &mut InstallerFramework,
|
||||||
|
_: &dyn Fn(&TaskMessage),
|
||||||
|
) -> Result<TaskParamType, String> {
|
||||||
|
let pkg = &context.database.packages.first();
|
||||||
|
if pkg.is_none() {
|
||||||
|
return Ok(TaskParamType::None)
|
||||||
|
}
|
||||||
|
let pkg = pkg.unwrap();
|
||||||
|
|
||||||
|
// look up the first shortcut for the first listed package in the database
|
||||||
|
let path = context
|
||||||
|
.install_path
|
||||||
|
.as_ref()
|
||||||
|
.log_expect("No install path specified");
|
||||||
|
|
||||||
|
let mut metadata: Option<PackageDescription> = None;
|
||||||
|
for description in &context
|
||||||
|
.config
|
||||||
|
.as_ref()
|
||||||
|
.log_expect("Should have packages by now")
|
||||||
|
.packages
|
||||||
|
{
|
||||||
|
if pkg.name == description.name {
|
||||||
|
metadata = Some(description.clone());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let package_desc = match metadata {
|
||||||
|
Some(v) => v,
|
||||||
|
// Package metadata is missing. Dunno what went wrong but we can skip this then
|
||||||
|
None => return Ok(TaskParamType::None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let shortcut = package_desc.shortcuts.first();
|
||||||
|
|
||||||
|
// copy the path to the actual exe into launcher_path so it'll load it on exit
|
||||||
|
context.launcher_path = shortcut.map(|s| {
|
||||||
|
path.join(s.relative_path.clone()).to_str().map(|t| { t.to_string() }).unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(TaskParamType::None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dependencies(&self) -> Vec<TaskDependency> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"LaunchOnExitTask".to_string()
|
||||||
|
}
|
||||||
|
}
|
@ -13,10 +13,12 @@ pub mod check_authorization;
|
|||||||
pub mod download_pkg;
|
pub mod download_pkg;
|
||||||
pub mod ensure_only_instance;
|
pub mod ensure_only_instance;
|
||||||
pub mod install;
|
pub mod install;
|
||||||
|
pub mod install_desktop_shortcut;
|
||||||
pub mod install_dir;
|
pub mod install_dir;
|
||||||
pub mod install_global_shortcut;
|
pub mod install_global_shortcut;
|
||||||
pub mod install_pkg;
|
pub mod install_pkg;
|
||||||
pub mod install_shortcuts;
|
pub mod install_shortcuts;
|
||||||
|
pub mod launch_installed_on_exit;
|
||||||
pub mod resolver;
|
pub mod resolver;
|
||||||
pub mod save_database;
|
pub mod save_database;
|
||||||
pub mod save_executable;
|
pub mod save_executable;
|
||||||
|
BIN
ui/public/thicc_logo_installer__ea_shadow.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
ui/public/thicc_logo_installer_shadow.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
@ -4,7 +4,7 @@
|
|||||||
<div class="container is-max-height">
|
<div class="container is-max-height">
|
||||||
<div class="columns is-max-height">
|
<div class="columns is-max-height">
|
||||||
<div class="column is-one-third has-padding" v-if="!$root.$data.metadata.is_launcher">
|
<div class="column is-one-third has-padding" v-if="!$root.$data.metadata.is_launcher">
|
||||||
<img src="./assets/logo.png" width="60%" alt="Application icon" />
|
<img src="./assets/light_mode_installer_logo.png" id="applicationIcon" alt="Application icon" />
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
@ -52,6 +52,30 @@ body, div, span, h1, h2, h3, h4, h5, h6 {
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#applicationIcon {
|
||||||
|
width:0px; height: 0px;
|
||||||
|
padding: 50px 60% 0px 0px;
|
||||||
|
background: url("./assets/light_mode_installer_logo.png") left top no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.has-background-black-ter #applicationIcon {
|
||||||
|
background: url("./assets/dark_mode_installer_logo.png") left top no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-icon {
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
float: left;
|
||||||
|
padding-right: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-description {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
-webkit-user-select: text;
|
-webkit-user-select: text;
|
||||||
-moz-user-select: text;
|
-moz-user-select: text;
|
||||||
@ -124,7 +148,7 @@ pre {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Dark mode */
|
/* Dark mode */
|
||||||
body.has-background-black-ter .subtitle, body.has-background-black-ter .column > div {
|
body.has-background-black-ter .subtitle, body.has-background-black-ter .column > div, body.has-background-black-ter section {
|
||||||
color: hsl(0, 0%, 96%);
|
color: hsl(0, 0%, 96%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
ui/src/assets/dark_mode_installer_logo.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 29 KiB |
BIN
ui/src/assets/light_mode_installer_logo.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 6.2 KiB |
@ -115,7 +115,8 @@ var app = new Vue({
|
|||||||
'\n\nPlease upload the log file (in ' + search_location + ') to ' +
|
'\n\nPlease upload the log file (in ' + search_location + ') to ' +
|
||||||
'the ' + app.attrs.name + ' team'
|
'the ' + app.attrs.name + ' team'
|
||||||
}});
|
}});
|
||||||
}
|
},
|
||||||
|
{} // pass in nothing to cause `ajax` to post instead of get
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
check_authentication: function (success, error) {
|
check_authentication: function (success, error) {
|
||||||
|
@ -30,7 +30,7 @@ export default new Router({
|
|||||||
component: SelectPackages
|
component: SelectPackages
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/install/:kind',
|
path: '/install/:kind/:desktop_shortcut',
|
||||||
name: 'install',
|
name: 'install',
|
||||||
component: InstallPackages
|
component: InstallPackages
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="column has-padding">
|
<div class="column has-padding">
|
||||||
|
<section>
|
||||||
<b-message type="is-info" :active.sync="browser_opened">
|
<b-message type="is-info" :active.sync="browser_opened">
|
||||||
Page opened! Check your default browser for the page, and follow the instructions there to link your patreon account.
|
Page opened! Check your default browser for the page, and follow the instructions there to link your patreon account.
|
||||||
When you are done, enter the token below.
|
When you are done, enter the token below.
|
||||||
@ -10,19 +11,28 @@
|
|||||||
To be an Early Access member, you must be a Patreon Early Access Subscriber.
|
To be an Early Access member, you must be a Patreon Early Access Subscriber.
|
||||||
</b-message>
|
</b-message>
|
||||||
<div>
|
<div>
|
||||||
If you are a subscriber, <a v-on:click="launch_browser('https://profile.yuzu-emu.org/')">click here to link your yuzu-emu.org account</a>
|
If you are a subscriber, <a v-on:click="launch_browser('https://profile.yuzu-emu.org/')">click here to link your yuzu-emu.org account</a>
|
||||||
<br>
|
<br>
|
||||||
If you are not already a subscriber, <a v-on:click="launch_browser('https://www.patreon.com/join/yuzuteam/checkout?rid=2822069')">click here to become one</a>
|
If you are not already a subscriber, <a v-on:click="launch_browser('https://www.patreon.com/join/yuzuteam/checkout?rid=2822069')">click here to become one</a>
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
|
|
||||||
<div class="control">
|
|
||||||
<label for="token">Token</label>
|
|
||||||
<input class="input" type="text" v-model="combined_token" placeholder="Token" id="token">
|
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<p>Token</p>
|
||||||
|
<b-field>
|
||||||
|
<b-input type="text" v-model="combined_token" placeholder="Token" id="token" style='width: 50em;'></b-input>
|
||||||
|
<p class="control">
|
||||||
|
<button class="button is-info" v-on:click="paste">Paste</button>
|
||||||
|
</p>
|
||||||
|
</b-field>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
|
||||||
<b-message type="is-danger" :active.sync="invalid_token">
|
<b-message type="is-danger" :active.sync="invalid_token">
|
||||||
Login failed!
|
Login failed!
|
||||||
Double check that your token is correct and try again
|
Double check that your token is correct and try again
|
||||||
@ -47,6 +57,7 @@
|
|||||||
Your patreon is linked, and you are supporting the project, but you must first join the Early Access reward tier!
|
Your patreon is linked, and you are supporting the project, but you must first join the Early Access reward tier!
|
||||||
<a v-on:click="launch_browser('https://www.patreon.com/join/yuzuteam/checkout?rid=2822069')">Log into your patreon account</a> and choose to back the Early Access reward tier.
|
<a v-on:click="launch_browser('https://www.patreon.com/join/yuzuteam/checkout?rid=2822069')">Log into your patreon account</a> and choose to back the Early Access reward tier.
|
||||||
</b-message>
|
</b-message>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div class="is-left-floating is-bottom-floating">
|
<div class="is-left-floating is-bottom-floating">
|
||||||
<p class="control">
|
<p class="control">
|
||||||
@ -63,6 +74,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AuthenticationView',
|
name: 'AuthenticationView',
|
||||||
created: function() {
|
created: function() {
|
||||||
@ -120,6 +133,11 @@ export default {
|
|||||||
go_back: function () {
|
go_back: function () {
|
||||||
this.$router.go(-1)
|
this.$router.go(-1)
|
||||||
},
|
},
|
||||||
|
paste: function () {
|
||||||
|
document.getElementById("token").focus();
|
||||||
|
document.execCommand("paste");
|
||||||
|
|
||||||
|
},
|
||||||
launch_browser: function(url) {
|
launch_browser: function(url) {
|
||||||
const that = this;
|
const that = this;
|
||||||
let app = this.$root;
|
let app = this.$root;
|
||||||
|
@ -46,12 +46,12 @@ export default {
|
|||||||
was_install: !this.$route.params.uninstall,
|
was_install: !this.$route.params.uninstall,
|
||||||
was_update: this.$route.params.update,
|
was_update: this.$route.params.update,
|
||||||
was_migrate: this.$route.params.migrate,
|
was_migrate: this.$route.params.migrate,
|
||||||
has_installed: this.$route.params.packages_installed > 0
|
has_installed: this.$route.params.packages_installed > 0,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
exit: function () {
|
exit: function () {
|
||||||
this.$root.exit()
|
this.$root.exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ export default {
|
|||||||
|
|
||||||
// Update the updater if needed
|
// Update the updater if needed
|
||||||
if (that.$root.config.new_tool) {
|
if (that.$root.config.new_tool) {
|
||||||
this.$router.push('/install/updater')
|
this.$router.push('/install/updater/false')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.$router.replace({ name: 'migrate',
|
this.$router.replace({ name: 'migrate',
|
||||||
params: { next: app.metadata.is_launcher ? '/install/regular' : '/modify' } })
|
params: { next: app.metadata.is_launcher ? '/install/regular/false' : '/modify' } })
|
||||||
} else {
|
} else {
|
||||||
for (var x = 0; x < app.config.packages.length; x++) {
|
for (var x = 0; x < app.config.packages.length; x++) {
|
||||||
app.config.packages[x].installed = false
|
app.config.packages[x].installed = false
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<b-message title="An error occurred" type="is-danger" :closable="false">
|
<b-message title="An error occurred" type="is-danger" :closable="false">
|
||||||
<div id="error_msg" v-html="msg"></div>
|
<div id="error_msg" v-html="msg"></div>
|
||||||
</b-message>
|
</b-message>
|
||||||
<div class="field is-grouped is-right-floating" v-bind:class="{ 'is-bottom-floating': !$root.$data.metadata.is_launcher, 'is-top-floating': $root.$data.metadata.is_launcher }">
|
<div class="field is-grouped is-right-floating is-bottom-floating">
|
||||||
<p class="control">
|
<p class="control">
|
||||||
<a class="button is-primary is-medium" v-if="remaining && !$root.$data.metadata.is_launcher" v-on:click="go_back">Back</a>
|
<a class="button is-primary is-medium" v-if="remaining && !$root.$data.metadata.is_launcher" v-on:click="go_back">Back</a>
|
||||||
<a class="button is-primary is-medium" v-if="$root.$data.metadata.is_launcher" v-on:click="exit">Exit</a>
|
<a class="button is-primary is-medium" v-if="$root.$data.metadata.is_launcher" v-on:click="exit">Exit</a>
|
||||||
|
@ -24,6 +24,7 @@ export default {
|
|||||||
is_uninstall: false,
|
is_uninstall: false,
|
||||||
is_updater_update: false,
|
is_updater_update: false,
|
||||||
is_update: false,
|
is_update: false,
|
||||||
|
install_desktop_shortcut: false,
|
||||||
failed_with_error: false,
|
failed_with_error: false,
|
||||||
authorization_required: false,
|
authorization_required: false,
|
||||||
packages_installed: 0
|
packages_installed: 0
|
||||||
@ -33,7 +34,9 @@ export default {
|
|||||||
this.is_uninstall = this.$route.params.kind === 'uninstall'
|
this.is_uninstall = this.$route.params.kind === 'uninstall'
|
||||||
this.is_updater_update = this.$route.params.kind === 'updater'
|
this.is_updater_update = this.$route.params.kind === 'updater'
|
||||||
this.is_update = this.$route.params.kind === 'update'
|
this.is_update = this.$route.params.kind === 'update'
|
||||||
|
this.install_desktop_shortcut = this.$route.params.desktop_shortcut === 'true'
|
||||||
console.log('Installer kind: ' + this.$route.params.kind)
|
console.log('Installer kind: ' + this.$route.params.kind)
|
||||||
|
console.log('Installing desktop shortcut: ' + this.$route.params.desktop_shortcut)
|
||||||
this.install()
|
this.install()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -53,6 +56,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
results['path'] = app.install_location
|
results['path'] = app.install_location
|
||||||
|
results['installDesktopShortcut'] = that.install_desktop_shortcut
|
||||||
|
|
||||||
var targetUrl = '/api/start-install'
|
var targetUrl = '/api/start-install'
|
||||||
if (this.is_uninstall) {
|
if (this.is_uninstall) {
|
||||||
@ -88,7 +92,7 @@ export default {
|
|||||||
if (that.is_updater_update) {
|
if (that.is_updater_update) {
|
||||||
// Continue with what we were doing
|
// Continue with what we were doing
|
||||||
if (app.metadata.is_launcher) {
|
if (app.metadata.is_launcher) {
|
||||||
that.$router.replace('/install/regular')
|
that.$router.replace('/install/regular/' + that.install_desktop_shortcut.toString())
|
||||||
} else {
|
} else {
|
||||||
if (app.metadata.preexisting_install) {
|
if (app.metadata.preexisting_install) {
|
||||||
that.$router.replace('/modify')
|
that.$router.replace('/modify')
|
||||||
|
@ -43,7 +43,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
update: function () {
|
update: function () {
|
||||||
this.$router.push('/install/update')
|
this.$router.push('/install/update/false')
|
||||||
},
|
},
|
||||||
modify_packages: function () {
|
modify_packages: function () {
|
||||||
this.$router.push('/packages')
|
this.$router.push('/packages')
|
||||||
@ -55,7 +55,7 @@ export default {
|
|||||||
this.show_uninstall = false
|
this.show_uninstall = false
|
||||||
},
|
},
|
||||||
uninstall: function () {
|
uninstall: function () {
|
||||||
this.$router.push('/install/uninstall')
|
this.$router.push('/install/uninstall/false')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,144 +1,158 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="column has-padding">
|
<div class="column has-padding">
|
||||||
<h4 class="subtitle">Select which packages you want to install:</h4>
|
<!-- Build options -->
|
||||||
|
<div class="tile is-ancestor">
|
||||||
<!-- Build options -->
|
<div class="tile is-parent is-vertical">
|
||||||
<div class="tile is-ancestor">
|
<div class="tile is-child is-12 box clickable-box" v-for="Lpackage in $root.$data.config.packages" :key="Lpackage.name" :index="Lpackage.name" v-on:click.capture.stop="clicked_box(Lpackage)">
|
||||||
<div class="tile is-parent" v-for="Lpackage in $root.$data.config.packages" :key="Lpackage.name" :index="Lpackage.name">
|
<div class="ribbon" v-if="Lpackage.is_new"><span>New!</span></div>
|
||||||
<div class="tile is-child">
|
<label class="checkbox">
|
||||||
<div class="box clickable-box" v-if="!Lpackage.requires_authorization || (Lpackage.requires_authorization && $root.$data.has_reward_tier)" v-on:click.capture.stop="Lpackage.default = !Lpackage.default">
|
<b-checkbox v-model="Lpackage.default">
|
||||||
<div class="ribbon" v-if="Lpackage.is_new"><span>New!</span></div>
|
<span v-if="!Lpackage.installed">Install</span> {{ Lpackage.name }}
|
||||||
<label class="checkbox">
|
</b-checkbox>
|
||||||
<b-checkbox v-model="Lpackage.default">
|
<span v-if="Lpackage.installed"><i>(installed)</i></span>
|
||||||
{{ Lpackage.name }}
|
</label>
|
||||||
</b-checkbox>
|
<div>
|
||||||
<span v-if="Lpackage.installed"><i>(installed)</i></span>
|
<img class="package-icon" :src="`${publicPath + Lpackage.icon}`"/>
|
||||||
</label>
|
<p style="padding-top: 4px;" class="package-description">
|
||||||
<p>
|
{{ Lpackage.description }}
|
||||||
{{ Lpackage.description }}
|
</p>
|
||||||
</p>
|
<p class="package-description">
|
||||||
</div>
|
{{ get_extended_description(Lpackage) }}
|
||||||
<div class="box clickable-box" v-else-if="Lpackage.requires_authorization && !$root.$data.is_authenticated" v-on:click="show_authentication">
|
</p>
|
||||||
<div class="ribbon" v-if="Lpackage.is_new"><span>New!</span></div>
|
|
||||||
<b-checkbox>
|
|
||||||
{{ Lpackage.name }}
|
|
||||||
</b-checkbox>
|
|
||||||
<p>
|
|
||||||
{{Lpackage.need_authentication_description}}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="box clickable-box" v-else-if="Lpackage.requires_authorization && !$root.$data.is_linked" v-on:click="show_authorization">
|
|
||||||
<div class="ribbon" v-if="Lpackage.is_new"><span>New!</span></div>
|
|
||||||
<b-checkbox>
|
|
||||||
{{ Lpackage.name }}
|
|
||||||
</b-checkbox>
|
|
||||||
<p>
|
|
||||||
{{Lpackage.need_link_description}}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="box clickable-box" v-else-if="Lpackage.requires_authorization && !$root.$data.is_subscribed" v-on:click="show_authorization">
|
|
||||||
<div class="ribbon" v-if="Lpackage.is_new"><span>New!</span></div>
|
|
||||||
<b-checkbox>
|
|
||||||
{{ Lpackage.name }}
|
|
||||||
</b-checkbox>
|
|
||||||
<p>
|
|
||||||
{{Lpackage.need_subscription_description}}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="box clickable-box" v-else v-on:click="show_authorization">
|
|
||||||
<div class="ribbon" v-if="Lpackage.is_new"><span>New!</span></div>
|
|
||||||
<b-checkbox>
|
|
||||||
{{ Lpackage.name }}
|
|
||||||
</b-checkbox>
|
|
||||||
<p>
|
|
||||||
{{Lpackage.need_reward_tier_description}}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="subtitle is-6" v-if="!$root.$data.metadata.preexisting_install && advanced">Install Location</div>
|
|
||||||
<div class="field has-addons" v-if="!$root.$data.metadata.preexisting_install && advanced">
|
|
||||||
<div class="control is-expanded">
|
|
||||||
<input class="input" type="text" v-model="$root.$data.install_location"
|
|
||||||
placeholder="Enter a install path here">
|
|
||||||
</div>
|
|
||||||
<div class="control">
|
|
||||||
<a class="button is-dark" v-on:click="select_file">
|
|
||||||
Select
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="is-right-floating is-bottom-floating">
|
|
||||||
<div class="field is-grouped">
|
|
||||||
<p class="control">
|
|
||||||
<a class="button is-medium" v-if="!$root.$data.config.hide_advanced && !$root.$data.metadata.preexisting_install && !advanced"
|
|
||||||
v-on:click="advanced = true">Advanced...</a>
|
|
||||||
</p>
|
|
||||||
<p class="control">
|
|
||||||
<a class="button is-dark is-medium" v-if="!$root.$data.metadata.preexisting_install"
|
|
||||||
v-on:click="install">Install</a>
|
|
||||||
</p>
|
|
||||||
<p class="control">
|
|
||||||
<a class="button is-dark is-medium" v-if="$root.$data.metadata.preexisting_install"
|
|
||||||
v-on:click="install">Modify</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field is-grouped is-left-floating is-bottom-floating">
|
|
||||||
<p class="control">
|
|
||||||
<a class="button is-medium" v-if="$root.$data.metadata.preexisting_install"
|
|
||||||
v-on:click="go_back">Back</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tile is-child is-6 box clickable-box" v-if="!$root.$data.metadata.preexisting_install" v-on:click.capture.stop="installDesktopShortcut = !installDesktopShortcut">
|
||||||
|
<h4>Install Options</h4>
|
||||||
|
<b-checkbox v-model="installDesktopShortcut">
|
||||||
|
Create Desktop Shortcut
|
||||||
|
</b-checkbox>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="subtitle is-6" v-if="!$root.$data.metadata.preexisting_install && advanced">Install Location</div>
|
||||||
|
<div class="field has-addons" v-if="!$root.$data.metadata.preexisting_install && advanced">
|
||||||
|
<div class="control is-expanded">
|
||||||
|
<input class="input" type="text" v-model="$root.$data.install_location"
|
||||||
|
placeholder="Enter a install path here">
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<a class="button is-dark" v-on:click="select_file">
|
||||||
|
Select
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="is-right-floating is-bottom-floating">
|
||||||
|
<div class="field is-grouped">
|
||||||
|
<p class="control">
|
||||||
|
<a class="button is-medium" v-if="!$root.$data.config.hide_advanced && !$root.$data.metadata.preexisting_install && !advanced"
|
||||||
|
v-on:click="advanced = true">Advanced...</a>
|
||||||
|
</p>
|
||||||
|
<p class="control">
|
||||||
|
<!-- Disable the Install button on a fresh install with no packages selected -->
|
||||||
|
<button v-if="$root.$data.metadata.preexisting_install" class="button is-medium is-dark" v-on:click="install">
|
||||||
|
Modify
|
||||||
|
</button>
|
||||||
|
<button v-else class="button is-medium is-dark" v-on:click="install" :disabled="!this.has_package_selected">
|
||||||
|
Install
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field is-grouped is-left-floating is-bottom-floating">
|
||||||
|
<p class="control">
|
||||||
|
<a class="button is-medium" v-if="$root.$data.metadata.preexisting_install"
|
||||||
|
v-on:click="go_back">Back</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'SelectPackages',
|
name: 'SelectPackages',
|
||||||
created: function() {
|
created: function() {
|
||||||
if (this.$root.$data.has_reward_tier) {
|
// If they are authorized, make the packages that require authorization default
|
||||||
for (let package_index = 0; package_index < this.$root.config.packages.length; package_index++) {
|
// and also deselect any packages that don't use authorization
|
||||||
let current_package = this.$root.config.packages[package_index];
|
if (this.$root.$data.has_reward_tier) {
|
||||||
// If they are authorized, make the packages that require authorization default
|
for (let package_index = 0; package_index < this.$root.config.packages.length; package_index++) {
|
||||||
if (current_package.requires_authorization) {
|
let current_package = this.$root.config.packages[package_index];
|
||||||
current_package.default = true;
|
current_package.default = current_package.requires_authorization;
|
||||||
} else {
|
}
|
||||||
// And unselect any other packages
|
}
|
||||||
current_package.default = false;
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
publicPath: process.env.BASE_URL,
|
||||||
|
advanced: false,
|
||||||
|
installDesktopShortcut: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
has_package_selected: function() {
|
||||||
|
for (let i=0; i < this.$root.config.packages.length; ++i) {
|
||||||
|
let pkg = this.$root.config.packages[i];
|
||||||
|
if (pkg.default) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
select_file: function () {
|
||||||
|
window.external.invoke(JSON.stringify({
|
||||||
|
SelectInstallDir: {
|
||||||
|
callback_name: 'selectFileCallback'
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
install: function () {
|
||||||
|
this.$router.push('/install/regular/' + this.installDesktopShortcut.toString())
|
||||||
|
},
|
||||||
|
go_back: function () {
|
||||||
|
this.$router.go(-1)
|
||||||
|
},
|
||||||
|
show_authentication: function () {
|
||||||
|
this.$router.push('/authentication')
|
||||||
|
},
|
||||||
|
show_authorization: function () {
|
||||||
|
this.$router.push('/authentication')
|
||||||
|
},
|
||||||
|
installable: function (pkg) {
|
||||||
|
return !pkg.requires_authorization || (pkg.requires_authorization && this.$root.$data.has_reward_tier);
|
||||||
|
},
|
||||||
|
clicked_box: function (pkg) {
|
||||||
|
if (this.installable(pkg)) {
|
||||||
|
pkg.default = !pkg.default;
|
||||||
|
} else if (pkg.requires_authorization && !this.$root.$data.is_authenticated) {
|
||||||
|
this.show_authentication()
|
||||||
|
} else if (pkg.requires_authorization && !this.$root.$data.is_linked) {
|
||||||
|
this.show_authorization()
|
||||||
|
} else if (pkg.requires_authorization && !this.$root.$data.is_subscribed) {
|
||||||
|
this.show_authorization()
|
||||||
|
} else { // need_reward_tier_description
|
||||||
|
this.show_authorization()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
get_extended_description: function(pkg) {
|
||||||
|
if (!pkg.extended_description) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (this.installable(pkg)) {
|
||||||
|
return pkg.extended_description.no_action_description;
|
||||||
|
} else if (pkg.requires_authorization && !this.$root.$data.is_authenticated) {
|
||||||
|
return pkg.extended_description.need_authentication_description;
|
||||||
|
} else if (pkg.requires_authorization && !this.$root.$data.is_linked) {
|
||||||
|
return pkg.extended_description.need_link_description;
|
||||||
|
} else if (pkg.requires_authorization && !this.$root.$data.is_subscribed) {
|
||||||
|
return pkg.extended_description.need_subscription_description;
|
||||||
|
} else { // need_reward_tier_description
|
||||||
|
return pkg.extended_description.need_reward_tier_description;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
advanced: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
select_file: function () {
|
|
||||||
window.external.invoke(JSON.stringify({
|
|
||||||
SelectInstallDir: {
|
|
||||||
callback_name: 'selectFileCallback'
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
install: function () {
|
|
||||||
this.$router.push('/install/regular')
|
|
||||||
},
|
|
||||||
go_back: function () {
|
|
||||||
this.$router.go(-1)
|
|
||||||
},
|
|
||||||
show_authentication: function () {
|
|
||||||
this.$router.push('/authentication')
|
|
||||||
},
|
|
||||||
show_authorization: function () {
|
|
||||||
this.$router.push('/authentication')
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|