Several changes

- added `vue-i18n`
- made `Locales` class to get launcher locales
- added checking that settings window is not already open in `Launcher.showSettings()`
- improved `promisify()`; now it can work with `Promise` objects
- improved `Process` class;
  added `Process.kill()` and `Process.running()` methods
- improved `Window` class, fixed its output
This commit is contained in:
Observer KRypt0n_ 2021-12-25 16:09:46 +02:00
parent 1a99aae933
commit 0684774ea8
No known key found for this signature in database
GPG key ID: DC5D4EC1303465DA
15 changed files with 246 additions and 28 deletions

View file

@ -10,6 +10,7 @@
"dependencies": {
"js-md5": "^0.7.3",
"vue": "^3.2.25",
"vue-i18n": "^9.2.0-beta.25",
"yaml": "^1.10.2"
},
"devDependencies": {

2
public/locales/en.yaml Normal file
View file

@ -0,0 +1,2 @@
settings:
test: Hello, World!

2
public/locales/ru.yaml Normal file
View file

@ -0,0 +1,2 @@
settings:
test: Привет, Мир!

View file

@ -9,7 +9,15 @@
<body>
<div id="app">
<h1>{{ title }}</h1>
<h1>{{ $t('settings.test') }}</h1>
<div class="locale-changer">
<select v-model="$i18n.locale">
<option v-for="locale in $i18n.availableLocales" :key="`locale-${locale}`" :value="locale">{{ locale }}</option>
</select>
</div>
<l-checkbox property="test"></l-checkbox>
</div>
<script src="neutralino.js"></script>

View file

@ -0,0 +1,12 @@
<template>
<h1>{{ property }}</h1>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
props: ['property'],
});
</script>

View file

@ -39,7 +39,7 @@ promisify(async () => {
});
});
let app = createApp({
const app = createApp({
data: () => ({
uri: {
social: '',
@ -81,7 +81,8 @@ let app = createApp({
() => launcher.updateSocial(),
() => launcher.updateBackground()
],
callAtOnce: true
callAtOnce: true,
interval: 500
});
// Show window when all the stuff was completed

View file

@ -1,11 +1,31 @@
import { createApp } from 'vue/dist/vue.esm-bundler';
import { createI18n } from 'vue-i18n';
import Window from '../ts/neutralino/Window';
createApp({
import Checkbox from '../components/Checkbox.vue';
import Locales from '../ts/core/Locales';
const app = createApp({
data: () => ({
title: 'about'
}),
components: {
'l-checkbox': Checkbox
},
mounted: () => Window.current.show()
}).mount('#app');
});
Locales.get().then((locales) => {
app.use(createI18n({
locale: 'en',
fallbackLocale: 'en',
// @ts-expect-error
messages: locales
}));
app.mount('#app');
});

View file

@ -50,6 +50,13 @@ class Paths
*/
public static readonly shadersDir: string = `${this.appDir}/public/shaders`;
/**
* Locales directory
*
* @default "[constants.paths.app]/public/locales"
*/
public static readonly localesDir: string = `${this.appDir}/public/locales`;
/**
* Launcher data directory
*

View file

@ -1,4 +1,5 @@
import Window from './neutralino/Window';
import Process from './neutralino/Process';
import constants from './Constants';
import Configs from './Configs';
@ -7,6 +8,8 @@ import Background from './launcher/Background';
import ProgressBar from './launcher/ProgressBar';
import State from './launcher/State';
declare const Neutralino;
export default class Launcher
{
public app;
@ -14,6 +17,8 @@ export default class Launcher
public state: State;
public progressBar: ProgressBar;
protected settingsMenu?: Process;
public constructor(app)
{
this.app = app;
@ -46,12 +51,36 @@ export default class Launcher
t(0);
}
public showSettings()
public showSettings(): Promise<boolean>
{
Window.open('settings', {
title: 'Settings',
width: 900,
height: 600
return new Promise(async (resolve) => {
if (this.settingsMenu && await this.settingsMenu.running())
resolve(false);
else
{
this.settingsMenu = undefined;
const window = await Window.open('settings', {
title: 'Settings',
width: 900,
height: 600,
enableInspector: true
});
if (window.status)
{
this.settingsMenu = new Process(window.data!.pid, 500);
/*this.settingsMenu.finish(() => {
Window.current.show();
})
Window.current.hide();*/
}
resolve(window.status);
}
});
}

57
src/ts/core/Locales.ts Normal file
View file

@ -0,0 +1,57 @@
import YAML from 'yaml';
import constants from '../Constants';
import promisify from './promisify';
type AvailableLocales =
| 'en'
| 'ru';
declare const Neutralino;
export default class Locales
{
/**
* Get locales
*
* @param locale - locale name to get. If null - then will be returned array of all available locales
*/
public static get(locale: AvailableLocales|null = null): Promise<object>
{
return new Promise((resolve) => {
if (locale === null)
{
Neutralino.filesystem.readDirectory(constants.paths.localesDir)
.then(async (folders: { entry: string, type: string }[]) => {
folders = folders.filter((folder) => folder.type === 'FILE');
const pipeline = promisify({
callbacks: folders.map((folder) => {
return new Promise((resolve) => {
Neutralino.filesystem.readFile(`${constants.paths.localesDir}/${folder.entry}`)
.then((locale) => resolve(YAML.parse(locale)));
});
}),
callAtOnce: true
});
pipeline.then((locales) => {
let result = {};
for (let i = 0; i < folders.length; i++)
{
const lang = folders[i].entry.substring(0, folders[i].entry.length - 5);
result[lang] = locales[i];
}
resolve(result);
});
});
}
else Neutralino.filesystem.readFile(`${constants.paths.localesDir}/${locale}.yaml`)
.then((locale) => resolve(YAML.parse(locale)));
});
}
};

View file

@ -1,7 +1,7 @@
type callback = () => any;
type PromiseOptions = {
callbacks: callback[];
callbacks: callback[]|Promise<any>[];
/**
* If true, then all the callbacks will be called
@ -24,21 +24,30 @@ type PromiseOptions = {
/**
* Make a promise from a function(s) and run it
*/
export default function promisify(callback: callback|PromiseOptions): Promise<any>
export default function promisify(callback: callback|Promise<any>|PromiseOptions): Promise<any>
{
return new Promise(async (resolve) => {
// promisify(() => { ... })
if (typeof callback === 'function')
resolve(await Promise.resolve(callback()));
// promisify(new Promise(...))
else if (typeof callback['then'] === 'function')
resolve(await callback);
// promisify({ callbacks: [ ... ] })
else
{
let outputs = {};
// @ts-expect-error
if (callback.callAtOnce)
{
// @ts-expect-error
let remained = callback.callbacks.length;
for (let i = 0; i < callback.callbacks.length; ++i)
// @ts-expect-error
for (let i = 0; i < callback.callbacks.length; ++i) // @ts-expect-error
promisify(callback.callbacks[i]).then((output) => {
outputs[i] = output;
@ -46,18 +55,20 @@ export default function promisify(callback: callback|PromiseOptions): Promise<an
});
const updater = () => {
if (remained > 0)
if (remained > 0) // @ts-expect-error
setTimeout(updater, callback.interval ?? 100);
else resolve(outputs);
};
// @ts-expect-error
setTimeout(updater, callback.interval ?? 100);
}
else
{
for (let i = 0; i < callback.callbacks.length; ++i)
// @ts-expect-error
for (let i = 0; i < callback.callbacks.length; ++i) // @ts-expect-error
outputs[i] = await promisify(callback.callbacks[i]);
resolve(outputs);

View file

@ -8,12 +8,19 @@ type ProcessOptions = {
class Process
{
/**
* Process ID
*/
public readonly id: number;
/**
* Interval between process status update
* Interval in ms between process status update
*
* null if you don't want to update process status
*
* @default 200
*/
public interval: number = 200;
public interval: number|null;
protected _finished: boolean = false;
@ -27,15 +34,19 @@ class Process
protected onFinish?: (process: Process) => void;
public constructor(pid: number)
public constructor(pid: number, interval: number|null = 200)
{
this.id = pid;
this.interval = interval;
const updateStatus = async () => {
Neutralino.os.execCommand(`ps -p ${this.id}`).then((output) => {
const updateStatus = () => {
this.running().then((running) => {
// The process is still running
if (output.stdOut.includes(this.id))
setTimeout(updateStatus, this.interval);
if (running)
{
if (this.interval)
setTimeout(updateStatus, this.interval);
}
// Otherwise the process was stopped
else
@ -48,7 +59,8 @@ class Process
});
};
setTimeout(updateStatus, this.interval);
if (this.interval)
setTimeout(updateStatus, this.interval);
}
/**
@ -60,6 +72,44 @@ class Process
if (this._finished)
callback(this);
// If user stopped process status auto-checking
// then we should check it manually when this method was called
else if (this.interval === null)
{
this.running().then((running) => {
if (!running)
{
this._finished = true;
callback(this);
}
});
}
}
/**
* Kill process
*/
public kill(forced: boolean = false): Promise<void>
{
return new Promise((resolve) => {
Neutralino.os.execCommand(`kill ${forced ? '-9' : '-15'} ${this.id}`).then(() => resolve());
});
}
/**
* Returns whether the process is running
*
* This method doesn't call onFinish event
*/
public running(): Promise<boolean>
{
return new Promise((resolve) => {
Neutralino.os.execCommand(`ps -p ${this.id}`).then((output) => {
resolve(output.stdOut.includes(this.id));
});
});
}
/**

View file

@ -22,6 +22,16 @@ type WindowOptions = WindowSize & {
processArgs?: string;
};
type WindowOpenResult = {
status: boolean;
data?: {
pid: number;
stdOut: string;
stdErr: string;
exitCode: number;
};
};
declare const Neutralino;
class Window
@ -31,10 +41,10 @@ class Window
return Neutralino.window;
}
public static async open(name: string, options: WindowOptions = {}): Promise<boolean>
public static open(name: string, options: WindowOptions = {}): Promise<WindowOpenResult>
{
return new Promise(async (resolve) => {
const status = Neutralino.window.create(`/${name}.html`, {
const status = await Neutralino.window.create(`/${name}.html`, {
width: 600,
height: 400,
enableInspector: false,
@ -48,14 +58,18 @@ class Window
hidden: true
});
resolve(status !== undefined);
resolve({
status: status !== undefined,
data: status
});
});
}
}
export type {
WindowSize,
WindowOptions
WindowOptions,
WindowOpenResult
};
export default Window;

4
src/ts/types/vue-sfc.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
declare module '*.vue'
{
export * from 'vue/dist/vue.esm-bundler';
};

View file

@ -47,7 +47,7 @@
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
"removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */