mirror of
https://github.com/an-anime-team/an-anime-game-launcher.git
synced 2025-03-07 08:37:45 +03:00
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:
parent
0854a2fefd
commit
5835f1e6cf
9 changed files with 330 additions and 74 deletions
|
@ -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">
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -4,7 +4,7 @@ body
|
|||
margin: 0
|
||||
padding: 0
|
||||
|
||||
user-select: none
|
||||
-webkit-user-select: none
|
||||
|
||||
img.background
|
||||
position: absolute
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) => {
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
77
src/ts/launcher/Background.ts
Normal file
77
src/ts/launcher/Background.ts
Normal 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`);
|
||||
});
|
||||
});
|
||||
}*/
|
||||
};
|
187
src/ts/launcher/ProgressBar.ts
Normal file
187
src/ts/launcher/ProgressBar.ts
Normal 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 };
|
Loading…
Add table
Reference in a new issue