Launch app on fresh install exit. Add desktop shortcuts

This commit is contained in:
James Rowe 2019-12-06 22:04:44 -07:00
parent 9b58c273d1
commit 732e344605
24 changed files with 500 additions and 223 deletions

View File

@ -12,6 +12,35 @@ auth_url = "https://api.yuzu-emu.org/jwt/installer/"
iss = "citra-core" iss = "citra-core"
aud = "installer" 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]] [[packages]]
name = "yuzu" name = "yuzu"
description = "Includes frequent updates to yuzu with all the latest reviewed and tested features." description = "Includes frequent updates to yuzu with all the latest reviewed and tested features."
@ -27,28 +56,3 @@ default = true
relative_path = "yuzu-windows-msvc/yuzu.exe" relative_path = "yuzu-windows-msvc/yuzu.exe"
description = "Launch yuzu" description = "Launch yuzu"
[[packages]]
name = "yuzu Early Access"
description = "Bonus preview release for project supporters. Thanks for your support!"
icon = "thicc_logo_installer__ea_shadow.png"
# 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"
requires_authorization = true
# puts a "new" ribbon the package select
is_new = true
[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"

View File

@ -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.
@ -43,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

View File

@ -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);

View File

@ -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);

View File

@ -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),

View File

@ -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);

View File

@ -164,3 +164,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;
}

View File

@ -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, U16CStr};
extern "C" { extern "C" {
pub fn saveShortcut( pub fn saveShortcut(
@ -50,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
@ -67,6 +89,20 @@ mod natives {
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);
@ -103,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");
@ -132,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(),

View File

@ -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: {:?}"),

View File

@ -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()),

View File

@ -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

View 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)
}
}

View File

@ -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 {})),
] ]
} }

View File

@ -87,6 +87,14 @@ impl Task for InstallShortcutsTask {
)?); )?);
} }
// 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))
} }

View File

@ -0,0 +1,76 @@
//! 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;
use native::create_desktop_shortcut;
pub struct LaunchOnExitTask {}
impl Task for LaunchOnExitTask {
fn execute(
&mut self,
mut input: Vec<TaskParamType>,
context: &mut InstallerFramework,
messenger: &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()
}
}

View File

@ -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;

View File

@ -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) {

View File

@ -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
}, },

View File

@ -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();
} }
} }
} }

View File

@ -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

View File

@ -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>

View File

@ -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')

View File

@ -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')
} }
} }
} }

View File

@ -1,160 +1,151 @@
<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 class="package-description">
<div> {{ Lpackage.description }}
<img class="package-icon" :src="`${publicPath + Lpackage.icon}`"/> </p>
<p class="package-description"> <p class="package-description">
{{ Lpackage.description }} {{ get_extended_description(Lpackage) }}
</p> </p>
</div>
</div>
<div class="box clickable-box" v-else-if="Lpackage.requires_authorization && !$root.$data.is_authenticated" v-on:click="show_authentication">
<div class="ribbon" v-if="Lpackage.is_new"><span>New!</span></div>
<b-checkbox>
{{ Lpackage.name }}
</b-checkbox>
<div>
<img class="package-icon" :src="`${publicPath + Lpackage.icon}`"/>
<p class="package-description">
{{ Lpackage.description }}
</p>
</div>
</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>
<div>
<img class="package-icon" :src="`${publicPath + Lpackage.icon}`"/>
<p class="package-description">
{{ Lpackage.description }}
</p>
</div>
</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>
<div>
<img class="package-icon" :src="`${publicPath + Lpackage.icon}`"/>
<p class="package-description">
{{ Lpackage.description }}
</p>
</div>
</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>
<div>
<img class="package-icon" :src="`${publicPath + Lpackage.icon}`"/>
<p class="package-description">
{{ Lpackage.description }}
</p>
</div>
</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">
</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>
</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
}
},
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 {
publicPath: process.env.BASE_URL,
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>