frontend: ui: include prebuilt assets...

... and update rust side stuff
This commit is contained in:
liushuyu 2019-06-25 11:20:42 +08:00
parent ff574c9d73
commit 6c19b8b0d1
No known key found for this signature in database
GPG Key ID: 23D1CE4534419437
18 changed files with 33 additions and 865 deletions

View File

@ -34,17 +34,14 @@ pub fn file_from_string(file_path: &str) -> Option<(String, &'static [u8])> {
file_path,
"/index.html",
"/favicon.ico",
"/logo.png",
"/css/bulma.min.css",
"/css/main.css",
"/img/logo.png",
"/css/app.css",
"/css/chunk-vendors.css",
"/fonts/roboto-v18-latin-regular.eot",
"/fonts/roboto-v18-latin-regular.woff",
"/fonts/roboto-v18-latin-regular.woff2",
"/js/vue.min.js",
"/js/vue-router.min.js",
"/js/helpers.js",
"/js/views.js",
"/js/main.js"
"/js/chunk-vendors.js",
"/js/app.js"
)?;
Some((string_mime, contents))

1
static/css/app.css Normal file
View File

@ -0,0 +1 @@
@font-face{font-family:Roboto;font-style:normal;font-weight:400;src:url(../fonts/roboto-v18-latin-regular.eot);src:local("Roboto"),local("Roboto-Regular"),url(../fonts/roboto-v18-latin-regular.woff2) format("woff2"),url(../fonts/roboto-v18-latin-regular.woff) format("woff")}body,html{overflow:hidden;height:100%}body,div,h1,h2,h3,h4,h5,h6,span{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:default}pre{-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;cursor:text}.tile.is-child>.box{height:100%}.has-padding{padding:2rem;position:relative}.clickable-box{cursor:pointer}.clickable-box label{pointer-events:none}.is-max-height{height:100%}.is-bottom-floating{position:absolute;bottom:0}.is-right-floating{position:absolute;right:0}.has-padding .is-right-floating{right:1rem}.is-left-floating{position:absolute;left:0}.has-padding .is-left-floating{left:1rem}.fullscreen{position:fixed;top:0;left:0;right:0;bottom:0;width:100%;height:100%;z-index:9999;padding:20px;background:#fff}body.has-background-black-ter .column>div,body.has-background-black-ter .subtitle{color:#f5f5f5}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,93 +0,0 @@
/* roboto-regular - latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: url('../fonts/roboto-v18-latin-regular.eot'); /* IE9 Compat Modes */
src: local('Roboto'), local('Roboto-Regular'),
url('../fonts/roboto-v18-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
url('../fonts/roboto-v18-latin-regular.woff') format('woff');
}
html, body {
overflow: hidden;
height: 100%;
}
body, div, span, h1, h2, h3, h4, h5, h6 {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: default;
}
pre {
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
cursor: text;
}
.tile.is-child > .box {
height: 100%;
}
.has-padding {
padding: 2rem;
position: relative;
}
.clickable-box {
cursor: pointer;
}
.clickable-box label {
pointer-events: none;
}
.is-max-height {
height: 100%;
}
.is-bottom-floating {
position: absolute;
bottom: 0;
}
.is-right-floating {
position: absolute;
right: 0;
}
.has-padding .is-right-floating {
right: 1rem;
}
.is-left-floating {
position: absolute;
left: 0;
}
.has-padding .is-left-floating {
left: 1rem;
}
.fullscreen {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 9999;
padding: 20px;
background: #fff;
}
/* Dark mode */
body.has-background-black-ter .subtitle, body.has-background-black-ter .column > div {
color: hsl(0, 0%, 96%);
}

View File

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -1,67 +1,3 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title id="window-title">... Installer</title>
<link rel="icon" href="/favicon.ico" />
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
<link rel="stylesheet" href="/css/bulma.min.css" type="text/css">
<link rel="stylesheet" href="/css/main.css" type="text/css">
</head>
<body class="is-max-height">
<div class="fullscreen" id="ie-blackout" style="display: none">
<div class="title">Your computer is out of date.</div>
<div class="subtitle">
Make sure that your computer is up to date, and that you have Internet Explorer 11 installed.
</div>
<div class="subtitle">
Please note we do not support pirated or unsupported versions of Windows.
</div>
</div>
<script type="text/javascript">
if (!document.__proto__) {
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><script src=/api/attrs></script><link rel=icon href=/favicon.ico><title id=window-title>... Installer</title><link href=/css/app.css rel=preload as=style><link href=/css/chunk-vendors.css rel=preload as=style><link href=/js/app.js rel=preload as=script><link href=/js/chunk-vendors.js rel=preload as=script><link href=/css/chunk-vendors.css rel=stylesheet><link href=/css/app.css rel=stylesheet></head><body><div class=fullscreen id=ie-blackout style="display: none"><div class=title>Your computer is out of date.</div><div class=subtitle>Make sure that your computer is up to date, and that you have Internet Explorer 11 installed.</div><div class=subtitle>Please note we do not support pirated or unsupported versions of Windows.</div></div><script>if (!document.__proto__) {
document.getElementById("ie-blackout").style.display = "block";
}
</script>
<div id="app" class="is-max-height">
<section class="section is-max-height">
<div class="container is-max-height">
<div class="columns is-max-height">
<div class="column is-one-third has-padding" v-if="!metadata.is_launcher">
<img src="/logo.png" width="60%" />
<br />
<br />
<h2 class="subtitle" v-if="!metadata.preexisting_install">
Welcome to the {{ attrs.name }} installer!
</h2>
<h2 class="subtitle" v-if="!metadata.preexisting_install">
We will have you up and running in just a few moments.
</h2>
<h2 class="subtitle" v-if="metadata.preexisting_install">
Welcome to the {{ attrs.name }} Maintenance Tool.
</h2>
</div>
<router-view></router-view>
</div>
</div>
</section>
</div>
<script src="/js/vue.min.js" type="text/javascript"></script>
<script src="/js/vue-router.min.js" type="text/javascript"></script>
<script src="/api/attrs" type="text/javascript"></script>
<script src="/js/helpers.js" type="text/javascript"></script>
<script src="/js/views.js" type="text/javascript"></script>
<script src="/js/main.js" type="text/javascript"></script>
</body>
</html>
}</script><noscript><strong>We're sorry but ui doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.js></script><script src=/js/app.js></script></body></html>

2
static/js/app.js Normal file

File diff suppressed because one or more lines are too long

1
static/js/app.js.map Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,144 +0,0 @@
/**
* helpers.js
*
* Additional state-less helper methods.
*/
var request_id = 0;
/**
* Makes a AJAX request.
*
* @param path The path to connect to.
* @param successCallback A callback with a JSON payload.
* @param failCallback A fail callback. Optional.
* @param data POST data. Optional.
*/
function ajax(path, successCallback, failCallback, data) {
if (failCallback === undefined) {
failCallback = defaultFailHandler;
}
console.log("Making HTTP request to " + path);
var req = new XMLHttpRequest();
req.addEventListener("load", function() {
// The server can sometimes return a string error. Make sure we handle this.
if (this.status === 200 && this.getResponseHeader('Content-Type').indexOf("application/json") !== -1) {
successCallback(JSON.parse(this.responseText));
} else {
failCallback(this.responseText);
}
});
req.addEventListener("error", failCallback);
req.open(data == null ? "GET" : "POST", path + "?nocache=" + request_id++, true);
// Rocket only currently supports URL encoded forms.
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
if (data != null) {
var form = "";
for (var key in data) {
if (!data.hasOwnProperty(key)) {
continue;
}
if (form !== "") {
form += "&";
}
form += encodeURIComponent(key) + "=" + encodeURIComponent(data[key]);
}
req.send(form);
} else {
req.send();
}
}
/**
* Makes a AJAX request, streaming each line as it arrives. Type should be text/plain,
* each line will be interpreted as JSON separately.
*
* @param path The path to connect to.
* @param callback A callback with a JSON payload. Called for every line as it comes.
* @param successCallback A callback with a raw text payload.
* @param failCallback A fail callback. Optional.
* @param data POST data. Optional.
*/
function stream_ajax(path, callback, successCallback, failCallback, data) {
var req = new XMLHttpRequest();
console.log("Making streaming HTTP request to " + path);
req.addEventListener("load", function() {
// The server can sometimes return a string error. Make sure we handle this.
if (this.status === 200) {
successCallback(this.responseText);
} else {
failCallback(this.responseText);
}
});
var buffer = "";
var seenBytes = 0;
req.onreadystatechange = function() {
if(req.readyState > 2) {
buffer += req.responseText.substr(seenBytes);
var pointer;
while ((pointer = buffer.indexOf("\n")) >= 0) {
var line = buffer.substring(0, pointer).trim();
buffer = buffer.substring(pointer + 1);
if (line.length === 0) {
continue;
}
var contents = JSON.parse(line);
callback(contents);
}
seenBytes = req.responseText.length;
}
};
req.addEventListener("error", failCallback);
req.open(data == null ? "GET" : "POST", path + "?nocache=" + request_id++, true);
// Rocket only currently supports URL encoded forms.
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
if (data != null) {
var form = "";
for (var key in data) {
if (!data.hasOwnProperty(key)) {
continue;
}
if (form !== "") {
form += "&";
}
form += encodeURIComponent(key) + "=" + encodeURIComponent(data[key]);
}
req.send(form);
} else {
req.send();
}
}
/**
* The default handler if a AJAX request fails. Not to be used directly.
*
* @param e The XMLHttpRequest that failed.
*/
function defaultFailHandler(e) {
console.error("A AJAX request failed, and was not caught:");
console.error(e);
}

View File

@ -1,79 +0,0 @@
// Overwrite loggers with the logging backend
if (window.external !== undefined && window.external.invoke !== undefined) {
window.onerror = function(msg, url, line) {
old_onerror(msg, url, line);
window.external.invoke(JSON.stringify({
Log: {
kind: "error",
msg: msg + " @ " + url + ":" + line
}
}));
};
// Borrowed from http://tobyho.com/2012/07/27/taking-over-console-log/
function intercept(method){
console[method] = function(){
var message = Array.prototype.slice.apply(arguments).join(' ');
window.external.invoke(JSON.stringify({
Log: {
kind: method,
msg: message
}
}));
}
}
var methods = ['log', 'warn', 'error'];
for (var i = 0; i < methods.length; i++) {
intercept(methods[i]);
}
}
// Disable F5
function disable_shortcuts(e) {
switch (e.keyCode) {
case 116: // F5
e.preventDefault();
break;
}
}
// Check to see if we need to enable dark mode
ajax("/api/dark-mode", function(enable) {
if (enable) {
document.body.classList.add("has-background-black-ter");
}
});
window.addEventListener("keydown", disable_shortcuts);
document.getElementById("window-title").innerText = base_attributes.name + " Installer";
function selectFileCallback(name) {
app.install_location = name;
}
var app = new Vue({
router: router,
data: {
attrs: base_attributes,
config : {},
install_location : "",
// If the option to pick an install location should be provided
show_install_location : true,
metadata : {
database : [],
install_path : "",
preexisting_install : false
}
},
methods: {
"exit": function() {
ajax("/api/exit", function() {}, function(msg) {
alert("LiftInstall encountered and error while exiting: " + msg
+ "\nPlease upload the log file (in the same directory as the installer) to " +
"the respective maintainers for this application (where you got it from!)");
});
}
}
}).$mount("#app");

View File

@ -1,460 +0,0 @@
const DownloadConfig = {
template: `
<div class="column has-padding">
<h4 class="subtitle">Downloading config...</h4>
<br />
<progress class="progress is-info is-medium" value="0" max="100">
0%
</progress>
</div>
`,
created: function() {
this.download_install_status();
},
methods: {
download_install_status: function() {
var that = this; // IE workaround
ajax("/api/installation-status", function(e) {
app.metadata = e;
that.download_config();
});
},
download_config: function() {
var that = this; // IE workaround
ajax("/api/config", function(e) {
app.config = e;
that.choose_next_state();
}, function(e) {
console.error("Got error while downloading config: "
+ e);
if (app.metadata.is_launcher) {
// Just launch the target application
app.exit();
} else {
router.replace({name: 'showerr', params: {msg: "Got error while downloading config: "
+ e}});
}
});
},
choose_next_state: function() {
// Update the updater if needed
if (app.config.new_tool) {
router.push("/install/updater");
return;
}
if (app.metadata.preexisting_install) {
app.install_location = app.metadata.install_path;
// Copy over installed packages
for (var x = 0; x < app.config.packages.length; x++) {
app.config.packages[x].default = false;
app.config.packages[x].installed = false;
}
for (var i = 0; i < app.metadata.database.packages.length; i++) {
// Find this config package
for (var x = 0; x < app.config.packages.length; x++) {
if (app.config.packages[x].name === app.metadata.database.packages[i].name) {
app.config.packages[x].default = true;
app.config.packages[x].installed = true;
}
}
}
if (app.metadata.is_launcher) {
router.replace("/install/regular");
} else {
router.replace("/modify");
}
} else {
for (var x = 0; x < app.config.packages.length; x++) {
app.config.packages[x].installed = false;
}
// Need to do a bit more digging to get at the
// install location.
ajax("/api/default-path", function(e) {
if (e.path != null) {
app.install_location = e.path;
} else {
console.warn("App location received is empty!");
}
});
router.replace("/packages");
}
}
}
};
const SelectPackages = {
template: `
<div class="column has-padding">
<h4 class="subtitle">Select which packages you want to install:</h4>
<!-- Build options -->
<div class="tile is-ancestor">
<div class="tile is-parent" v-for="package in $root.$data.config.packages" :index="package.name">
<div class="tile is-child">
<div class="box clickable-box" v-on:click.capture.stop="package.default = !package.default">
<label class="checkbox">
<input type="checkbox" v-model="package.default" />
{{ package.name }}
<span v-if="package.installed"><i>(installed)</i></span>
</label>
<p>
{{ package.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>
`,
data: function() {
return {
advanced: false
}
},
methods: {
select_file: function() {
window.external.invoke(JSON.stringify({
SelectInstallDir: {
callback_name: "selectFileCallback"
}
}));
},
install: function() {
router.push("/install/regular");
},
go_back: function() {
router.go(-1);
}
}
};
const InstallPackages = {
template: `
<div class="column has-padding">
<h4 class="subtitle" v-if="$root.$data.metadata.is_launcher || is_update">Checking for updates...</h4>
<h4 class="subtitle" v-else-if="is_uninstall">Uninstalling...</h4>
<h4 class="subtitle" v-else-if="is_updater_update">Downloading self-update...</h4>
<h4 class="subtitle" v-else>Installing...</h4>
<div v-html="$root.$data.config.installing_message"></div>
<br />
<div v-html="progress_message"></div>
<progress class="progress is-info is-medium" v-bind:value="progress" max="100">
{{ progress }}%
</progress>
</div>
`,
data: function() {
return {
progress: 0.0,
progress_message: "Please wait...",
is_uninstall: false,
is_updater_update: false,
is_update: false,
failed_with_error: false,
packages_installed: 0
}
},
created: function() {
this.is_uninstall = this.$route.params.kind === "uninstall";
this.is_updater_update = this.$route.params.kind === "updater";
this.is_update = this.$route.params.kind === "update";
console.log("Installer kind: " + this.$route.params.kind);
this.install();
},
methods: {
install: function() {
var results = {};
for (var package_index = 0; package_index < app.config.packages.length; package_index++) {
var current_package = app.config.packages[package_index];
if (current_package.default != null) {
results[current_package.name] = current_package.default;
}
}
results["path"] = app.install_location;
var that = this; // IE workaround
var targetUrl = "/api/start-install";
if (this.is_uninstall) {
targetUrl = "/api/uninstall";
}
if (this.is_updater_update) {
targetUrl = "/api/update-updater";
}
stream_ajax(targetUrl, function(line) {
if (line.hasOwnProperty("Status")) {
that.progress_message = line.Status[0];
that.progress = line.Status[1] * 100;
}
if (line.hasOwnProperty("PackageInstalled")) {
that.packages_installed += 1;
}
if (line.hasOwnProperty("Error")) {
if (app.metadata.is_launcher) {
app.exit();
} else {
that.failed_with_error = true;
router.replace({name: 'showerr', params: {msg: line.Error}});
}
}
}, function(e) {
if (that.is_updater_update) {
// Continue with what we were doing
if (app.metadata.is_launcher) {
router.replace("/install/regular");
} else {
if (app.metadata.preexisting_install) {
router.replace("/modify");
} else {
router.replace("/packages");
}
}
} else {
if (app.metadata.is_launcher) {
app.exit();
} else if (!that.failed_with_error) {
if (that.is_uninstall) {
router.replace({name: 'complete', params: {
uninstall: true,
update: that.is_update,
installed: that.packages_installed
}});
} else {
router.replace({name: 'complete', params: {
uninstall: false,
update: that.is_update,
installed: that.packages_installed
}});
}
}
}
}, undefined, results);
}
}
};
const ErrorView = {
template: `
<div class="column has-padding">
<h4 class="subtitle">An error occurred:</h4>
<pre>{{ msg }}</pre>
<div class="field is-grouped is-right-floating is-bottom-floating">
<p class="control">
<a class="button is-primary is-medium" v-if="remaining" v-on:click="go_back">Back</a>
</p>
</div>
</div>
`,
data: function() {
return {
msg: this.$route.params.msg,
remaining: window.history.length > 1
}
},
methods: {
go_back: function() {
router.go(-1);
}
}
};
const CompleteView = {
template: `
<div class="column has-padding">
<div v-if="was_update">
<div v-if="has_installed">
<h4 class="subtitle">{{ $root.$data.attrs.name }} has been updated.</h4>
<p>You can find your installed applications in your start menu.</p>
</div>
<div v-else>
<h4 class="subtitle">{{ $root.$data.attrs.name }} is already up to date!</h4>
<p>You can find your installed applications in your start menu.</p>
</div>
</div>
<div v-else-if="was_install">
<h4 class="subtitle">Thanks for installing {{ $root.$data.attrs.name }}!</h4>
<p>You can find your installed applications in your start menu.</p>
</div>
<div v-else>
<h4 class="subtitle">{{ $root.$data.attrs.name }} has been uninstalled.</h4>
</div>
<div class="field is-grouped is-right-floating is-bottom-floating">
<p class="control">
<a class="button is-dark is-medium" v-on:click="exit">Exit</a>
</p>
</div>
</div>
`,
data: function() {
return {
was_install: !this.$route.params.uninstall,
was_update: this.$route.params.update,
has_installed: this.$route.params.packages_installed > 0
}
},
methods: {
exit: function() {
app.exit();
}
}
};
const ModifyView = {
template: `
<div class="column has-padding">
<h4 class="subtitle">Choose an option:</h4>
<a class="button is-dark is-medium" v-on:click="update">
Update
</a>
<br />
<br />
<a class="button is-dark is-medium" v-on:click="modify_packages">
Modify
</a>
<br />
<br />
<a class="button is-dark is-medium" v-on:click="prepare_uninstall">
Uninstall
</a>
<div class="modal is-active" v-if="show_uninstall">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Are you sure you want to uninstall {{ $root.$data.attrs.name }}?</p>
</header>
<footer class="modal-card-foot">
<button class="button is-danger" v-on:click="uninstall">Yes</button>
<button class="button" v-on:click="cancel_uninstall">No</button>
</footer>
</div>
</div>
</div>
`,
data: function() {
return {
show_uninstall: false
}
},
methods: {
update: function() {
router.push("/install/update");
},
modify_packages: function() {
router.push("/packages");
},
prepare_uninstall: function() {
this.show_uninstall = true;
},
cancel_uninstall: function() {
this.show_uninstall = false;
},
uninstall: function() {
router.push("/install/uninstall");
},
}
};
const router = new VueRouter({
routes: [
{
path: '/config',
name: 'config',
component: DownloadConfig
},
{
path: '/packages',
name: 'packages',
component: SelectPackages
},
{
path: '/install/:kind',
name: 'install',
component: InstallPackages
},
{
path: '/showerr',
name: 'showerr',
component: ErrorView
},
{
path: '/complete/:uninstall/:update/:packages_installed',
name: 'complete',
component: CompleteView
},
{
path: '/modify',
name: 'modify',
component: ModifyView
},
{
path: '/',
redirect: '/config'
}
]
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="X-UA-Compatible" content="IE=11">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<script src="/api/attrs" type="text/javascript"></script>
<link rel="icon" href="<%= BASE_URL %>favicon.ico">

View File

@ -1,5 +1,6 @@
module.exports = {
devServer: {
proxy: 'http://127.0.0.1:3000'
}
},
filenameHashing: false
}