Convert project backend to typescript
This commit is contained in:
parent
31f2b3a133
commit
8683140003
9
.gitignore
vendored
9
.gitignore
vendored
@ -1,7 +1,8 @@
|
||||
/.idea
|
||||
node_modules/
|
||||
/config
|
||||
/dist
|
||||
.idea
|
||||
node_modules
|
||||
config
|
||||
dist
|
||||
build
|
||||
GH_TOKEN
|
||||
|
||||
yarn-error.log
|
||||
|
22
package.json
22
package.json
@ -8,14 +8,14 @@
|
||||
},
|
||||
"homepage": "https://gitlab.com/ArisuOngaku/tabs",
|
||||
"license": "GPL-3.0-only",
|
||||
"main": "tabs.js",
|
||||
"main": "build/main.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"dev": "electron . --dev",
|
||||
"build": "electron-builder",
|
||||
"build-arch": "electron-builder --linux dir",
|
||||
"release": "GH_TOKEN=$(cat GH_TOKEN) electron-builder -wlp always",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"clean": "! test -d build || rm -r build",
|
||||
"compile": "yarn clean && tsc",
|
||||
"start": "yarn compile && electron .",
|
||||
"dev": "yarn compile && electron . --dev",
|
||||
"build": "yarn compile && electron-builder -wl",
|
||||
"release": "yarn compile && GH_TOKEN=$(cat GH_TOKEN) electron-builder -wlp always"
|
||||
},
|
||||
"dependencies": {
|
||||
"appdata-path": "^1.0.0",
|
||||
@ -25,11 +25,17 @@
|
||||
"single-instance": "^0.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^12.12.41",
|
||||
"electron": "^9.0.0",
|
||||
"electron-builder": "^22.4.0"
|
||||
"electron-builder": "^22.4.0",
|
||||
"typescript": "^3.9.3"
|
||||
},
|
||||
"build": {
|
||||
"appId": "tabs-app",
|
||||
"files": [
|
||||
"resources/**/*",
|
||||
"build/**/*"
|
||||
],
|
||||
"linux": {
|
||||
"target": "AppImage",
|
||||
"icon": "resources/logo.png",
|
||||
|
@ -9,18 +9,19 @@ const configDir = Meta.isDevMode() ? getAppDataPath('tabs-app-dev') : getAppData
|
||||
const configFile = path.resolve(configDir, 'config.json');
|
||||
|
||||
export default class Config {
|
||||
updateCheckSkip = undefined;
|
||||
securityButton = true;
|
||||
homeButton = false;
|
||||
backButton = true;
|
||||
forwardButton = false;
|
||||
refreshButton = false;
|
||||
public services: Service[] = [];
|
||||
public updateCheckSkip?: string;
|
||||
public securityButton: boolean = true;
|
||||
public homeButton: boolean = false;
|
||||
public backButton: boolean = true;
|
||||
public forwardButton: boolean = false;
|
||||
public refreshButton: boolean = false;
|
||||
|
||||
properties = [];
|
||||
private properties: string[] = [];
|
||||
|
||||
constructor() {
|
||||
// Load data from config file
|
||||
let data = {};
|
||||
let data: any = {};
|
||||
if (fs.existsSync(configDir) && fs.statSync(configDir).isDirectory()) {
|
||||
if (fs.existsSync(configFile) && fs.statSync(configFile).isFile())
|
||||
data = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
||||
@ -29,7 +30,6 @@ export default class Config {
|
||||
}
|
||||
|
||||
// Parse services
|
||||
this.services = [];
|
||||
if (typeof data.services === 'object') {
|
||||
let i = 0;
|
||||
for (const service of data.services) {
|
||||
@ -60,18 +60,18 @@ export default class Config {
|
||||
console.log('> Config saved to', configFile.toString());
|
||||
}
|
||||
|
||||
defineProperty(name, data) {
|
||||
defineProperty(name: string, data: any) {
|
||||
if (data[name] !== undefined) {
|
||||
this[name] = data[name];
|
||||
(<any>this)[name] = data[name];
|
||||
}
|
||||
|
||||
this.properties.push(name);
|
||||
}
|
||||
|
||||
update(data) {
|
||||
update(data: any) {
|
||||
for (const prop of this.properties) {
|
||||
if (data[prop] !== undefined) {
|
||||
this[prop] = data[prop];
|
||||
(<any>this)[prop] = data[prop];
|
||||
}
|
||||
}
|
||||
}
|
29
src/Meta.js
29
src/Meta.js
@ -1,29 +0,0 @@
|
||||
export default class Meta {
|
||||
static #title = 'Tabs';
|
||||
static #devMode = null;
|
||||
|
||||
static get title() {
|
||||
return this.#title;
|
||||
}
|
||||
|
||||
static isDevMode() {
|
||||
if (this.#devMode === null) {
|
||||
this.#devMode = process.argv.length > 2 && process.argv[2] === '--dev';
|
||||
console.debug('Dev mode:', this.#devMode);
|
||||
}
|
||||
|
||||
return this.#devMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param service {Service}
|
||||
* @param viewTitle {string}
|
||||
*/
|
||||
static getTitleForService(service, viewTitle) {
|
||||
let suffix = '';
|
||||
if (typeof viewTitle === 'string' && viewTitle.length > 0) {
|
||||
suffix = ' - ' + viewTitle;
|
||||
}
|
||||
return this.title + ' - ' + service.name + suffix;
|
||||
}
|
||||
}
|
23
src/Meta.ts
Normal file
23
src/Meta.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import Service from "./Service";
|
||||
|
||||
export default class Meta {
|
||||
public static readonly title = 'Tabs';
|
||||
private static devMode?: boolean;
|
||||
|
||||
public static isDevMode() {
|
||||
if (this.devMode === undefined) {
|
||||
this.devMode = process.argv.length > 2 && process.argv[2] === '--dev';
|
||||
console.debug('Dev mode:', this.devMode);
|
||||
}
|
||||
|
||||
return this.devMode;
|
||||
}
|
||||
|
||||
public static getTitleForService(service: Service, viewTitle: string) {
|
||||
let suffix = '';
|
||||
if (viewTitle.length > 0) {
|
||||
suffix = ' - ' + viewTitle;
|
||||
}
|
||||
return this.title + ' - ' + service.name + suffix;
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
class Service {
|
||||
constructor(partition, name, icon, isImage, url, useFavicon) {
|
||||
if (arguments.length === 1) {
|
||||
let data = arguments[0];
|
||||
for (let k in data) {
|
||||
if (data.hasOwnProperty(k)) {
|
||||
this[k] = data[k];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.partition = partition;
|
||||
this.name = name;
|
||||
this.icon = icon;
|
||||
this.isImage = isImage;
|
||||
this.url = url;
|
||||
this.useFavicon = useFavicon;
|
||||
}
|
||||
|
||||
for (let k in Service.requiredProperties) {
|
||||
if (Service.requiredProperties.hasOwnProperty(k)) {
|
||||
if (!this.hasOwnProperty(k) || this[k] === undefined) {
|
||||
this[k] = Service.requiredProperties[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Service.requiredProperties = {
|
||||
'partition': null,
|
||||
'name': null,
|
||||
'icon': null,
|
||||
'isImage': null,
|
||||
'url': null,
|
||||
'useFavicon': true,
|
||||
'autoLoad': false,
|
||||
'customCSS': null,
|
||||
'customUserAgent': null,
|
||||
'permissions': {},
|
||||
};
|
||||
|
||||
export default Service;
|
31
src/Service.ts
Normal file
31
src/Service.ts
Normal file
@ -0,0 +1,31 @@
|
||||
export default class Service {
|
||||
public partition?: string;
|
||||
public name?: string;
|
||||
public icon?: string;
|
||||
public isImage?: boolean = false;
|
||||
public url?: string;
|
||||
public useFavicon?: boolean = true;
|
||||
public favicon?: string;
|
||||
public autoLoad?: boolean = false;
|
||||
public customCSS?: string;
|
||||
public customUserAgent?: string;
|
||||
public permissions?: {} = {};
|
||||
|
||||
constructor(partition: string, name?: string, icon?: string, isImage?: boolean, url?: string, useFavicon?: boolean) {
|
||||
if (arguments.length === 1) {
|
||||
const data = arguments[0];
|
||||
for (const k in data) {
|
||||
if (data.hasOwnProperty(k)) {
|
||||
(<any>this)[k] = data[k];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.partition = partition;
|
||||
this.name = name;
|
||||
this.icon = icon;
|
||||
this.isImage = isImage;
|
||||
this.url = url;
|
||||
this.useFavicon = useFavicon;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,32 +1,32 @@
|
||||
import {autoUpdater} from "electron-updater";
|
||||
import {autoUpdater, UpdateInfo} from "electron-updater";
|
||||
|
||||
export default class Updater {
|
||||
#updateInfo;
|
||||
private updateInfo?: UpdateInfo;
|
||||
|
||||
constructor() {
|
||||
autoUpdater.autoDownload = false;
|
||||
autoUpdater.on('error', err => {
|
||||
this.notifyUpdate(false, err);
|
||||
console.log('Error while checking for updates', err);
|
||||
});
|
||||
autoUpdater.on('update-available', v => {
|
||||
this.notifyUpdate(true, v);
|
||||
console.log('Update available', v);
|
||||
});
|
||||
autoUpdater.on('update-not-available', () => {
|
||||
this.notifyUpdate(false);
|
||||
console.log('No update available.');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Function} callback
|
||||
*/
|
||||
checkForUpdates(callback) {
|
||||
if (this.#updateInfo) {
|
||||
callback(this.#updateInfo.version !== this.getCurrentVersion().raw, this.#updateInfo);
|
||||
checkForUpdates(callback: UpdateCheckCallback) {
|
||||
if (this.updateInfo) {
|
||||
callback(this.updateInfo.version !== this.getCurrentVersion().raw, this.updateInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
autoUpdater.checkForUpdates().then(r => {
|
||||
this.#updateInfo = r.updateInfo;
|
||||
this.updateInfo = r.updateInfo;
|
||||
callback(r.updateInfo.version !== this.getCurrentVersion().raw, r.updateInfo);
|
||||
}).catch(err => {
|
||||
callback(false, err);
|
||||
@ -36,8 +36,6 @@ export default class Updater {
|
||||
getCurrentVersion() {
|
||||
return autoUpdater.currentVersion;
|
||||
}
|
||||
|
||||
notifyUpdate(available, data) {
|
||||
console.log('Update:', available, data);
|
||||
}
|
||||
}
|
||||
|
||||
export type UpdateCheckCallback = (available: boolean, data: UpdateInfo) => void;
|
@ -1,11 +1,13 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import SingleInstance from "single-instance";
|
||||
import {app, BrowserWindow, dialog, ipcMain, Menu, shell, Tray} from "electron";
|
||||
|
||||
import Meta from "./Meta";
|
||||
import Config from "./Config";
|
||||
import Service from "./Service";
|
||||
import Updater from "./Updater";
|
||||
import Event = Electron.Event;
|
||||
|
||||
const resourcesDir = path.resolve(__dirname, '../resources');
|
||||
const iconPath = path.resolve(resourcesDir, 'logo.png');
|
||||
@ -21,9 +23,9 @@ const solidIcons = listIcons('solid');
|
||||
|
||||
let selectedService = 0;
|
||||
|
||||
let tray;
|
||||
let window;
|
||||
let serviceSettingsWindow, settingsWindow;
|
||||
let tray: Tray;
|
||||
let window: BrowserWindow | null;
|
||||
let serviceSettingsWindow: BrowserWindow | null, settingsWindow: BrowserWindow | null;
|
||||
|
||||
function toggleMainWindow() {
|
||||
if (window != null) {
|
||||
@ -41,7 +43,7 @@ async function createWindow() {
|
||||
// Check for updates
|
||||
updater.checkForUpdates((available, updateInfo) => {
|
||||
if (available && updateInfo.version !== config.updateCheckSkip) {
|
||||
dialog.showMessageBox(window, {
|
||||
dialog.showMessageBox(window!, {
|
||||
message: `Version ${updateInfo.version} of tabs is available. Do you wish to download this update?`,
|
||||
buttons: [
|
||||
'Cancel',
|
||||
@ -71,7 +73,7 @@ async function createWindow() {
|
||||
tray.setToolTip('Tabs');
|
||||
tray.setContextMenu(Menu.buildFromTemplate([
|
||||
{label: 'Tabs', enabled: false},
|
||||
{label: 'Open Tabs', click: () => window.show()},
|
||||
{label: 'Open Tabs', click: () => window!.show()},
|
||||
{type: 'separator'},
|
||||
{label: 'Quit', role: 'quit'}
|
||||
]));
|
||||
@ -142,7 +144,7 @@ async function createWindow() {
|
||||
enableRemoteModule: true,
|
||||
webviewTag: true,
|
||||
},
|
||||
parent: window,
|
||||
parent: window!,
|
||||
modal: true,
|
||||
autoHideMenuBar: true,
|
||||
height: 850,
|
||||
@ -155,10 +157,10 @@ async function createWindow() {
|
||||
mode: 'right'
|
||||
});
|
||||
}
|
||||
let syncListener;
|
||||
let syncListener: () => void;
|
||||
ipcMain.on('sync-settings', syncListener = () => {
|
||||
serviceSettingsWindow.webContents.send('syncIcons', brandIcons, solidIcons);
|
||||
serviceSettingsWindow.webContents.send('loadService', serviceId, config.services[serviceId]);
|
||||
serviceSettingsWindow!.webContents.send('syncIcons', brandIcons, solidIcons);
|
||||
serviceSettingsWindow!.webContents.send('loadService', serviceId, config.services[serviceId]);
|
||||
});
|
||||
serviceSettingsWindow.on('close', () => {
|
||||
ipcMain.removeListener('sync-settings', syncListener);
|
||||
@ -179,7 +181,7 @@ async function createWindow() {
|
||||
}
|
||||
config.save();
|
||||
|
||||
window.webContents.send('updateService', id, newService);
|
||||
window!.webContents.send('updateService', id, newService);
|
||||
});
|
||||
|
||||
ipcMain.on('deleteService', (e, id) => {
|
||||
@ -187,7 +189,7 @@ async function createWindow() {
|
||||
delete config.services[id];
|
||||
config.save();
|
||||
|
||||
window.webContents.send('deleteService', id);
|
||||
window!.webContents.send('deleteService', id);
|
||||
});
|
||||
|
||||
ipcMain.on('reorderService', (e, serviceId, targetId) => {
|
||||
@ -219,10 +221,10 @@ async function createWindow() {
|
||||
|
||||
ipcMain.on('updateWindowTitle', (event, serviceId, viewTitle) => {
|
||||
if (serviceId === null) {
|
||||
window.setTitle(Meta.title);
|
||||
window!.setTitle(Meta.title);
|
||||
} else {
|
||||
const service = config.services[serviceId];
|
||||
window.setTitle(Meta.getTitleForService(service, viewTitle));
|
||||
window!.setTitle(Meta.getTitleForService(service, viewTitle));
|
||||
}
|
||||
});
|
||||
|
||||
@ -236,7 +238,7 @@ async function createWindow() {
|
||||
enableRemoteModule: true,
|
||||
webviewTag: true,
|
||||
},
|
||||
parent: window,
|
||||
parent: window!,
|
||||
modal: true,
|
||||
autoHideMenuBar: true,
|
||||
height: 850,
|
||||
@ -249,21 +251,21 @@ async function createWindow() {
|
||||
mode: 'right'
|
||||
});
|
||||
}
|
||||
let syncListener;
|
||||
let syncListener: () => void;
|
||||
ipcMain.on('syncSettings', syncListener = () => {
|
||||
settingsWindow.webContents.send('current-version', updater.getCurrentVersion());
|
||||
settingsWindow.webContents.send('config', config);
|
||||
settingsWindow!.webContents.send('current-version', updater.getCurrentVersion());
|
||||
settingsWindow!.webContents.send('config', config);
|
||||
});
|
||||
|
||||
let checkForUpdatesListener;
|
||||
ipcMain.on('checkForUpdates', checkForUpdatesListener = (e) => {
|
||||
let checkForUpdatesListener: () => void;
|
||||
ipcMain.on('checkForUpdates', checkForUpdatesListener = () => {
|
||||
updater.checkForUpdates((available, version) => {
|
||||
settingsWindow.webContents.send('updateStatus', available, version);
|
||||
settingsWindow!.webContents.send('updateStatus', available, version);
|
||||
});
|
||||
});
|
||||
|
||||
let saveConfigListener;
|
||||
ipcMain.on('save-config', saveConfigListener = (e, data) => {
|
||||
let saveConfigListener: (e: Event, data: any) => void;
|
||||
ipcMain.on('save-config', saveConfigListener = (e: Event, data: any) => {
|
||||
config.update(data);
|
||||
config.save();
|
||||
sendData();
|
||||
@ -284,18 +286,18 @@ async function createWindow() {
|
||||
|
||||
function sendData() {
|
||||
console.log('Syncing data');
|
||||
window.webContents.send('data', Meta.title, brandIcons, solidIcons, selectedService, path.resolve(resourcesDir, 'empty.html'), config);
|
||||
window!.webContents.send('data', Meta.title, brandIcons, solidIcons, selectedService, path.resolve(resourcesDir, 'empty.html'), config);
|
||||
}
|
||||
|
||||
function setActiveService(index) {
|
||||
function setActiveService(index: number) {
|
||||
console.log('Selected service is now', index);
|
||||
selectedService = index;
|
||||
}
|
||||
|
||||
function listIcons(set) {
|
||||
function listIcons(set: string) {
|
||||
console.log('Loading icon set', set);
|
||||
const directory = path.resolve(resourcesDir, 'icons/' + set);
|
||||
const icons = [];
|
||||
const icons: { name: string; faIcon: string }[] = [];
|
||||
const dir = set === 'brands' ? 'fab' : 'fas';
|
||||
fs.readdirSync(directory).forEach(i => icons.push({
|
||||
name: i.split('.svg')[0],
|
||||
@ -304,7 +306,15 @@ function listIcons(set) {
|
||||
return icons;
|
||||
}
|
||||
|
||||
console.log('Starting app');
|
||||
app.on('ready', () => {
|
||||
|
||||
// Check if application is already running
|
||||
const lock = new SingleInstance('tabs-app');
|
||||
lock.lock().then(() => {
|
||||
console.log('Starting app');
|
||||
app.on('ready', () => {
|
||||
createWindow().catch(console.error);
|
||||
});
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
process.exit(0);
|
||||
});
|
7
src/types/single-instance.d.ts
vendored
Normal file
7
src/types/single-instance.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
declare module "single-instance" {
|
||||
export default class SingleInstance {
|
||||
constructor(lockName: string);
|
||||
|
||||
public lock(): Promise<void>;
|
||||
}
|
||||
}
|
10
tabs.js
10
tabs.js
@ -1,10 +0,0 @@
|
||||
#!/bin/electron
|
||||
const SingleInstance = require('single-instance');
|
||||
const lock = new SingleInstance('tabs-app');
|
||||
lock.lock().then(() => {
|
||||
require = require("esm")(module);
|
||||
module.exports = require("./src/main.js");
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
process.exit(0);
|
||||
});
|
26
tsconfig.json
Normal file
26
tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"esModuleInterop": true,
|
||||
"outDir": "build",
|
||||
"target": "ES6",
|
||||
"strict": true,
|
||||
"lib": [
|
||||
"es2020",
|
||||
"dom"
|
||||
],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"src/types"
|
||||
],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"*": [
|
||||
"node_modules/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
10
yarn.lock
10
yarn.lock
@ -70,6 +70,11 @@
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-12.12.39.tgz#532d25c1e639d89dd6f3aa1d7b3962e3e7fa943d"
|
||||
integrity sha512-pADGfwnDkr6zagDwEiCVE4yQrv7XDkoeVa4OfA9Ju/zRTk6YNDLGtQbkdL4/56mCQQCs4AhNrBIag6jrp7ZuOg==
|
||||
|
||||
"@types/node@^12.12.41":
|
||||
version "12.12.41"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-12.12.41.tgz#cf48562b53ab6cf85d28dde95f1d06815af275c8"
|
||||
integrity sha512-Q+eSkdYQJ2XK1AJnr4Ji8Gvk3sRDybEwfTvtL9CA25FFUSD2EgZQewN6VCyWYZCXg5MWZdwogdTNBhlWRcWS1w==
|
||||
|
||||
"@types/semver@^7.1.0":
|
||||
version "7.2.0"
|
||||
resolved "https://registry.npmjs.org/@types/semver/-/semver-7.2.0.tgz#0d72066965e910531e1db4621c15d0ca36b8d83b"
|
||||
@ -1505,6 +1510,11 @@ typedarray@^0.0.6:
|
||||
resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||
|
||||
typescript@^3.9.3:
|
||||
version "3.9.3"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-3.9.3.tgz#d3ac8883a97c26139e42df5e93eeece33d610b8a"
|
||||
integrity sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==
|
||||
|
||||
unique-string@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d"
|
||||
|
Loading…
Reference in New Issue
Block a user