Several changes

- added some default settings setting
- changed unsupported `user-select` style to `-webkit-user-select`
- `Configs.set()` now can store promise objects
- made `ProgressBar` and `Background` classes in `launcher` folder
  to simplify `Launcher` code
This commit is contained in:
Observer KRypt0n_ 2021-12-23 20:07:48 +02:00
parent 0854a2fefd
commit 5835f1e6cf
No known key found for this signature in database
GPG key ID: DC5D4EC1303465DA
9 changed files with 330 additions and 74 deletions

View file

@ -9,11 +9,11 @@
<body>
<div id="app" theme="light">
<img class="background" :src="backgroundUri">
<img class="background" :src="uri.background">
<div class="downloader-panel" theme="dark">
<div class="downloader-label">
<span id="downloaded">Downloading</span>
<span id="downloaded"></span>
<span id="speed"></span>
<span id="eta"></span>
</div>
@ -24,7 +24,7 @@
</div>
<div id="launcher-content">
<iframe id="social" :src="socialUri" scrolling="no" style="position: absolute; border: 0; top: 0; left: 0;" width="100%" height="100%"></iframe>
<iframe :src="uri.social" scrolling="no" style="position: absolute; border: 0; top: 0; left: 0;" width="100%" height="100%"></iframe>
</div>
<div id="settings">

View file

@ -3,11 +3,25 @@ import { createApp } from 'vue/dist/vue.esm-bundler';
import Window from '../ts/neutralino/Window';
import Launcher from '../ts/Launcher';
import Configs from '../ts/Configs';
import constants from '../ts/Constants';
(async () => {
Configs.defaults({
lang: {
launcher: 'en-us',
voice: 'en-us'
},
prefix: await constants.paths.prefix.default
});
})();
let app = createApp({
data: () => ({
socialUri: '',
backgroundUri: ''
uri: {
social: '',
background: ''
}
}),
methods: {

View file

@ -4,7 +4,7 @@ body
margin: 0
padding: 0
user-select: none
-webkit-user-select: none
img.background
position: absolute

View file

@ -12,6 +12,7 @@ export default class Configs
* Get config value
*
* @param name config name, e.g. "lang.launcher"
*
* @returns undefined if config doesn't exist. Otherwise - config value
*/
public static get(name: string = ''): Promise<undefined|scalar|scalar[]>
@ -37,9 +38,10 @@ export default class Configs
*
* @param name config name, e.g. "lang.launcher"
* @param value config value, e.g. "en-us"
*
* @returns Promise<void> indicates if the settings were updated
*/
public static set(name: string, value: scalar|scalar[]): Promise<void>
public static set(name: string, value: scalar|scalar[]|Promise<scalar|scalar[]>): Promise<void>
{
const getUpdatedArray = (path: string[], array: scalar|scalar[], value: scalar|scalar[]): scalar|scalar[] => {
array[path[0]] = path.length > 1 ?
@ -49,6 +51,8 @@ export default class Configs
};
return new Promise(async (resolve) => {
value = await Promise.resolve(value);
Neutralino.filesystem.readFile(await constants.paths.config).then(async (config) => {
config = JSON.stringify(getUpdatedArray(name.split('.'), JSON.parse(config), value), null, 4);
@ -67,6 +71,7 @@ export default class Configs
* Set default values
*
* @param configs object of default values
*
* @returns Promise<void> indicates if the default settings were applied
*/
public static defaults(configs: scalar): Promise<void>

View file

@ -7,6 +7,8 @@ import type {
import constants from './Constants';
import Downloader, { Stream } from './core/Downloader';
declare const Neutralino;
export default class Game
@ -118,4 +120,11 @@ export default class Game
.catch((error) => reject(error));
});
}
public static update(version: string|null = null): Promise<Stream>
{
return new Promise((resolve) => {
});
}
}

View file

@ -1,83 +1,49 @@
import constants from './Constants';
import Configs from './Configs';
import fetch from './core/Fetch';
import Background from './launcher/Background';
import ProgressBar from './launcher/ProgressBar';
export default class Launcher
{
public app;
public progressBar: ProgressBar;
public constructor(app)
{
this.app = app;
}
this.progressBar = new ProgressBar(this);
/**
* Get background image URI
*
* Neutralino is unnable to load local files
* so we sadly can't provide proper caching
*/
/*public getBackgroundUri(): Promise<string>
{
return new Promise((resolve, reject) => {
Cache.get('background').then(async (background) => {
const launcherDir = await constants.paths.launcherDir;
// Progress bar test
this.progressBar.init({
label: 'Abobus',
showSpeed: true,
showEta: true,
showPercents: true,
showTotals: true,
// If the background is not cached or
// the cache is expired
if (background === null || background.expired)
{
const header = await fetch(constants.backgroundUri + await Configs.get('lang.launcher'));
// Reject an error if background server is not available
if (!header.ok)
reject(new Error(`${constants.placeholders.uppercase.company}'s background server is not responding`));
else
{
header.body().then(async (body) => {
const json = JSON.parse(body);
// If the background wasn't loaded - then again reject an error
if (json.data.adv.background === undefined)
reject(new Error('Background property wasn\'t found'));
else
{
// Store some background info to the cache
await Cache.set('background', {
gameVersion: (await Game.latest).version,
cachedAt: Math.round(Date.now() / 1000)
}, 7 * 24 * 60 * 60);
console.log(json.data.adv.background);
// Download background picture and return path to it
Downloader.download(json.data.adv.background, `${launcherDir}/background.png`)
.then((stream) => {
stream.finish(() => resolve(`file://${launcherDir}/background.png`));
});
}
});
}
}
// Background is cached
// todo: add cache auto dropping when the banner is updated
else resolve(`file://${launcherDir}/background.png`);
});
finish: () => this.progressBar.hide()
});
}*/
this.progressBar.show();
const t = (curr) => {
if (curr <= 3000)
{
this.progressBar.update(curr, 3000, 1);
setTimeout(() => t(curr + 1), 10);
}
};
t(0);
}
public updateBackground(): Promise<void>
{
return new Promise(async (resolve) => {
fetch(constants.backgroundUri + await Configs.get('lang.launcher'))
.then((header) => header.body().then((body) => {
this.app.backgroundUri = JSON.parse(body).data.adv.background;
this.app.uri.background = await Background.get();
resolve();
}));
resolve();
});
}
@ -87,7 +53,7 @@ export default class Launcher
public updateSocial(): Promise<void>
{
return new Promise(async (resolve) => {
this.app.socialUri = `https://${constants.placeholders.lowercase.first}.${constants.placeholders.lowercase.company}.com/launcher/10/${await Configs.get('lang.launcher')}?api_url=https%3A%2F%2Fapi-os-takumi.${constants.placeholders.lowercase.company}.com%2Fhk4e_global&key=gcStgarh&prev=false`;
this.app.uri.social = `https://${constants.placeholders.lowercase.first}.${constants.placeholders.lowercase.company}.com/launcher/10/${await Configs.get('lang.launcher')}?api_url=https%3A%2F%2Fapi-os-takumi.${constants.placeholders.lowercase.company}.com%2Fhk4e_global&key=gcStgarh&prev=false`;
resolve();
});

View file

@ -82,10 +82,8 @@ export default abstract class Installer
});
};
if (typeof unpackDir === 'string')
unpackArchive(unpackDir);
else unpackDir.then((unpackDir) => unpackArchive(unpackDir));
Promise.resolve(unpackDir)
.then((unpackDir) => unpackArchive(unpackDir));
});
});
});

View file

@ -0,0 +1,77 @@
import constants from '../Constants';
import Configs from '../Configs';
import fetch from '../core/Fetch';
export default class Background
{
/**
* Get background uri
*/
public static get(): Promise<string>
{
return new Promise(async (resolve) => {
fetch(constants.backgroundUri + await Configs.get('lang.launcher'))
.then((header) => header.body().then((body) => {
resolve(JSON.parse(body).data.adv.background);
}));
});
}
/**
* Get background image URI
*
* Neutralino is unnable to load local files
* so we sadly can't provide proper caching
*/
/*public getBackgroundUri(): Promise<string>
{
return new Promise((resolve, reject) => {
Cache.get('background').then(async (background) => {
const launcherDir = await constants.paths.launcherDir;
// If the background is not cached or
// the cache is expired
if (background === null || background.expired)
{
const header = await fetch(constants.backgroundUri + await Configs.get('lang.launcher'));
// Reject an error if background server is not available
if (!header.ok)
reject(new Error(`${constants.placeholders.uppercase.company}'s background server is not responding`));
else
{
header.body().then(async (body) => {
const json = JSON.parse(body);
// If the background wasn't loaded - then again reject an error
if (json.data.adv.background === undefined)
reject(new Error('Background property wasn\'t found'));
else
{
// Store some background info to the cache
await Cache.set('background', {
gameVersion: (await Game.latest).version,
cachedAt: Math.round(Date.now() / 1000)
}, 7 * 24 * 60 * 60);
console.log(json.data.adv.background);
// Download background picture and return path to it
Downloader.download(json.data.adv.background, `${launcherDir}/background.png`)
.then((stream) => {
stream.finish(() => resolve(`file://${launcherDir}/background.png`));
});
}
});
}
}
// Background is cached
// todo: add cache auto dropping when the banner is updated
else resolve(`file://${launcherDir}/background.png`);
});
});
}*/
};

View file

@ -0,0 +1,187 @@
import Launcher from '../Launcher';
type InitOptions = {
label: string|((current: number, total: number, difference: number) => string);
update?: (current: number, total: number, difference: number) => void;
finish?: () => void;
showSpeed: boolean;
showEta: boolean;
showPercents: boolean;
showTotals: boolean;
};
export default class ProgressBar
{
public launcher: Launcher;
public downloaderElement: HTMLElement;
public progressElement: HTMLElement;
public downloadedLabelElement: HTMLElement;
public speedLabelElement: HTMLElement;
public etaLabelElement: HTMLElement;
public options: InitOptions;
protected progress = {
beganAt: 0,
prevTime: 0,
temp: 0
};
public constructor(launcher: Launcher)
{
this.launcher = launcher;
this.downloaderElement = <HTMLElement>document.getElementsByClassName('downloader-panel')[0];
this.progressElement = <HTMLElement>this.downloaderElement.children[1].children[0];
[
this.downloadedLabelElement,
this.speedLabelElement,
this.etaLabelElement
] = <HTMLCollectionOf<HTMLElement>>this.downloaderElement.children[0].children;
}
/**
* Show all the progress bar
*/
public show()
{
this.downloaderElement.style['display'] = 'block';
}
/**
* Hide all the progress bar
*/
public hide()
{
this.downloaderElement.style['display'] = 'none';
}
/**
* Init progress bar with some options
*/
public init(options: InitOptions)
{
this.options = options;
this.speedLabelElement.style['display'] = options.showSpeed ? 'inline-block' : 'none';
this.etaLabelElement.style['display'] = options.showEta ? 'inline-block' : 'none';
this.speedLabelElement.textContent = '';
this.etaLabelElement.textContent = '';
if (typeof options.label === 'string')
this.downloadedLabelElement.textContent = options.label;
this.progress.beganAt = Date.now();
}
/**
* Update progress bar
*/
public update(current: number, total: number, difference: number): void
{
// Update progress label if it is not a static text
if (typeof this.options.label === 'function')
this.downloadedLabelElement.textContent = this.options.label(current, total, difference);
// Otherwise update percents and totals if we should
else if (this.options.showPercents || this.options.showPercents)
{
this.downloadedLabelElement.textContent = `${this.options.label}:`;
if (this.options.showPercents)
this.downloadedLabelElement.textContent += ` ${Math.round(current / total * 100)}%`;
if (this.options.showTotals)
this.downloadedLabelElement.textContent += ` (${ProgressBar.prettifyBytes(current)} / ${ProgressBar.prettifyBytes(total)})`;
}
// Update progress width
this.progressElement.style['width'] = `${(current / total * 100).toFixed(2)}%`;
this.progress.temp += difference;
// If the delay between update calls was more than 1 second - then update some stats
if (Date.now() - this.progress.prevTime > 1000)
{
// Update speed if we need
if (this.options.showSpeed)
this.speedLabelElement.textContent = `${ProgressBar.prettifyBytes(this.progress.temp / (Date.now() - this.progress.prevTime) * 1000)}/s`;
// Update ETA if we need
if (this.options.showEta)
{
type etaType = string | number;
let elapsed = (Date.now() - this.progress.beganAt) / 1000;
let eta = Math.round(total * elapsed / current - elapsed);
let etaHours: etaType = Math.floor(eta / 3600),
etaMinutes: etaType = Math.floor((eta - etaHours * 3600) / 60),
etaSeconds: etaType = eta - etaHours * 3600 - etaMinutes * 60;
if (etaHours < 10)
etaHours = '0' + etaHours.toString();
if (etaMinutes < 10)
etaMinutes = '0' + etaMinutes.toString();
if (etaSeconds < 10)
etaSeconds = '0' + etaSeconds.toString();
this.etaLabelElement.textContent = `ETA: ${etaHours}:${etaMinutes}:${etaSeconds}`;
}
this.progress.prevTime = Date.now();
this.progress.temp = 0;
}
// Call user-provided update callback
if (this.options.update)
this.options.update(current, total, difference);
if (current === total && this.options.finish)
this.options.finish();
}
/**
* Prettify bytes
*
* @param bytes - amount of bytes
*
* @returns prettified string ("10 B", "7 KB", etc)
*/
public static prettifyBytes (bytes: number): string
{
const types = [
{
name: 'B',
multiplier: 1
},
{
name: 'KB',
multiplier: 1024
},
{
name: 'MB',
multiplier: 1024 * 1024
},
{
name: 'GB',
multiplier: 1024 * 1024 * 1024
}
].filter(type => type.multiplier < bytes);
return types.length == 0 ?
`${bytes} B` :
`${(bytes / types[types.length - 1].multiplier).toFixed(2)} ${types[types.length - 1].name}`;
}
};
export type { InitOptions };