...
 
Commits (37)
......@@ -25,6 +25,10 @@ The current released and prebuilt stable version is: **2.1.0**
**Model-Viewer** <br/>
#11 - opening new BFRES file reloads the window instead on manually deleting old stuff (less RAM usage) <br/>
**Internal Code Changes.** <br/>
#dev - upgrade to electron 3.0.2 (NodeJS 10.2.0) <br/>
#dev - started porting JS to TypeScript with a IOC library <br/>
<hr/>
### Version 2.1.0 - "Field-Editor + Map"
......
......@@ -4,11 +4,11 @@
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
const Theme_Manager = requireGlobal('lib/theme_manager.js');
const Loader = requireGlobal("lib/loader.js");
const Window_Handler_Remote = requireGlobal("lib/window_handler_remote.js");
const Main_Config = requireGlobal("lib/config/main_config.js");
const Project_Manager = require("./../lib/project/manager.js");
const Theme_Manager = require('./../lib/theme_manager.js');
const Loader = require("./../lib/loader.js").default;
const Window_Handler_Remote = require("./../lib/window_handler_remote.js");
const Main_Config = require("./../lib/config/main-config.js").default;
const Project_Manager = require("./../lib/project/manager.js").default;
const electron = require('electron');
const path = require('path');
......
......@@ -18,7 +18,7 @@
<link rel="stylesheet" href="css/photon.dark.css" data-theme="href" />
<link rel="stylesheet" href="css/theme.dark.css" data-theme="href" />
<link rel="stylesheet" href="node_modules/izitoast/dist/css/iziToast.min.css" />
<link rel="stylesheet" href="css/iziToast.min.css" />
<link rel="stylesheet" href="css/loader.css">
<link rel="stylesheet" href="css/split.css" />
......
......@@ -8,18 +8,15 @@ const electron = require('electron');
const fs = require('fs-extra');
const path = require('path');
const Split = require('split.js');
const {dialog} = electron.remote;
const Notify = requireGlobal("lib/notify/notify.js");
const Filter = requireGlobal("lib/filter.js");
const Notify = require("./../../lib/notify/notify.js");
const Field_Editor = require("./lib/field_editor.js");
const String_Table = requireGlobal("lib/string_table/string_table.js");
const String_Table = require("./../../lib/string_table/string_table.js");
const extractField = require("./lib/field_extractor");
const extractStaticMubins = require("./lib/static_mubin_extractor");
const TitleBG_Handler = require("./../../lib/titlebg_handler");
const App_Base = requireGlobal("apps/base.js");
const App_Base = require("./../base.js");
module.exports = class App extends App_Base
{
......@@ -117,8 +114,8 @@ module.exports = class App extends App_Base
this.render();
// some performance cuts
this.fieldEditor.setRenderSetting("targetFPS", "number", 30);
this.fieldEditor.setRenderSetting("camSpeed", "number", 2);
//this.fieldEditor.setRenderSetting("targetFPS", "number", 30); // @TODO!!
//this.fieldEditor.setRenderSetting("camSpeed", "number", 2); // @TODO!!
//this.fieldEditor.setRenderSetting("accurateTimer", "bool", true);
} catch(e) {
......@@ -157,7 +154,7 @@ module.exports = class App extends App_Base
await super.run();
const titleBgHandler = new TitleBG_Handler(this.config.getValue("game.path"), this.project.getPath());
this.fieldEditor = new Field_Editor(this.node.querySelector(".shrine-canvas"), this.node, this.project, this.loader, this.stringTable, titleBgHandler);
this.fieldEditor = new Field_Editor(this.node.querySelector(".mubin-canvas"), this.node, this.project, this.loader, this.stringTable, titleBgHandler);
let fieldSection = this.args.section ? this.args.section : "J-8";
let fieldPos = this.args.pos ? this.args.pos : undefined;
......
......@@ -4,27 +4,25 @@
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
// This files contains super-globals
require("reflect-metadata");
const electron = require('electron');
const querystring = require("querystring");
// This files contains super-globals
const __BASE_PATH = electron.remote.app.getAppPath() + "/";
function requireGlobal(path)
{
//if(path == "lib/sarc/sarc.js")
//throw "ewweffe SARC";
return require.main.require(__BASE_PATH + path);
}
// THREE.js is not compatible with the normal includes
var THREE = requireGlobal("lib/3d_renderer/three.min.js");
var THREE = require(__BASE_PATH + "lib/3d_renderer/three.min.js");
var mainApp = null;
document.addEventListener('DOMContentLoaded', () =>
{
const querystring = require("querystring");
let args = querystring.parse(global.window.location.search.substr(1));
let window = electron.remote.getCurrentWindow();
......
......@@ -19,7 +19,7 @@
<link rel="stylesheet" href="css/photon.dark.css" data-theme="href" />
<link rel="stylesheet" href="css/theme.dark.css" data-theme="href" />
<link rel="stylesheet" href="node_modules/izitoast/dist/css/iziToast.min.css" />
<link rel="stylesheet" href="css/iziToast.min.css" />
<link rel="stylesheet" href="css/split.css" />
<link rel="stylesheet" href="css/dialog.css" />
......
......@@ -11,8 +11,8 @@ const fs = require('fs');
const path = require('path');
const url = require('url');
const swal = require('sweetalert2');
const Filter = requireGlobal("lib/filter.js");
const Notify = requireGlobal("lib/notify/notify.js");
const Filter = require("./../../lib/filter.js");
const Notify = require("./../../lib/notify/notify.js");
const {dialog} = electron.remote;
const BrowserWindow = electron.remote.BrowserWindow;
......
......@@ -21,7 +21,7 @@
<link rel="stylesheet" href="css/photon.dark.css" data-theme="href" />
<link rel="stylesheet" href="css/theme.dark.css" data-theme="href" />
<link rel="stylesheet" href="node_modules/izitoast/dist/css/iziToast.min.css" />
<link rel="stylesheet" href="css/iziToast.min.css" />
<link rel="stylesheet" href="css/loader.css">
<link rel="stylesheet" href="css/split.css" />
......
......@@ -4,7 +4,7 @@
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
const App_Base = requireGlobal("./apps/base.js");
const App_Base = require("./../../apps/base.js");
const JSON_IPC = require("./../../lib/json_ipc/json_ipc");
const Graph = require("./lib/graph/graph");
......
......@@ -21,7 +21,7 @@
<link rel="stylesheet" href="css/photon.dark.css" data-theme="href" />
<link rel="stylesheet" href="css/theme.dark.css" data-theme="href" />
<link rel="stylesheet" href="node_modules/izitoast/dist/css/iziToast.min.css" />
<link rel="stylesheet" href="css/iziToast.min.css" />
<link rel="stylesheet" href="css/loader.css">
<link rel="stylesheet" href="css/split.css" />
......
......@@ -4,7 +4,7 @@
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
const App_Base = requireGlobal("./apps/base.js");
const App_Base = require("./../base.js");
const World_Map = require("./lib/world_map/world_map");
const World_Map_UI = require("./lib/world_map/ui");
......
......@@ -4,7 +4,7 @@
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
const QuerySelector_Cache = require("./../../../../lib/queryselector_chache");
const QuerySelector_Cache = require("./../../../../lib/queryselector-cache").default;
const FIELD_SECTION_REGEX = /[A-J]-[1-9]/;
const SHRINE_REGEX = /Dungeon[0-9]{3}/;
......
......@@ -19,7 +19,7 @@
<link rel="stylesheet" href="css/photon.dark.css" data-theme="href" />
<link rel="stylesheet" href="css/theme.dark.css" data-theme="href" />
<link rel="stylesheet" href="node_modules/izitoast/dist/css/iziToast.min.css" />
<link rel="stylesheet" href="css/iziToast.min.css" />
<link rel="stylesheet" href="css/split.css" />
<link rel="stylesheet" href="css/style.css" />
......
......@@ -4,16 +4,15 @@
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
const electron = require('electron');
const fs = require('fs-extra');
const Filter = requireGlobal("lib/filter.js");
const Notify = requireGlobal("lib/notify/notify.js");
const Filter = require("./../../lib/filter.js");
const Notify = require("./../../lib/notify/notify.js");
const getFolderSize = require("./../../lib/folder_size.js");
const formatFilesize = require("filesize");
const App_Base = requireGlobal("./apps/base.js");
const File_Drop = requireGlobal("./lib/file_drop.js");
const App_Base = require("./../../apps/base.js");
const File_Drop = require("./../../lib/file_drop.js");
module.exports = class App extends App_Base
{
......
......@@ -8,7 +8,7 @@
width: 90%;
}
.window canvas.shrine-canvas
.window canvas.mubin-canvas
{
width: 100%;
height: 100%;
......
......@@ -18,7 +18,7 @@
<link rel="stylesheet" href="css/photon.dark.css" data-theme="href" />
<link rel="stylesheet" href="css/theme.dark.css" data-theme="href" />
<link rel="stylesheet" href="node_modules/izitoast/dist/css/iziToast.min.css" />
<link rel="stylesheet" href="css/iziToast.min.css" />
<link rel="stylesheet" href="css/loader.css">
<link rel="stylesheet" href="css/split.css" />
......@@ -157,7 +157,7 @@
</div>
<div class="pane" id="main-sidebar-2">
<canvas class="shrine-canvas" tabindex="1">Oops! here should be something nice in 3D</canvas>
<canvas class="mubin-canvas" tabindex="1">Oops! here should be something nice in 3D</canvas>
</div>
<div class="pane-sm sidebar" id="main-sidebar-3" style="max-width: none;">
......
......@@ -10,19 +10,19 @@ const path = require('path');
const url = require('url');
const Split = require('split.js');
const Notify = requireGlobal("lib/notify/notify.js");
const Filter = requireGlobal("lib/filter.js");
const Notify = require("./../../lib/notify/notify.js");
const Filter = require("./../../lib/filter.js");
const Binary_File_Loader = require("binary-file").Loader;
const SARC = require("sarc-lib");
const Shrine_Editor = require("./lib/shrine_editor.js");
const String_Table = requireGlobal("lib/string_table/string_table.js");
const String_Table = require("./../../lib/string_table/string_table.js");
const JSON_IPC = require("./../../lib/json_ipc/json_ipc");
const {dialog} = electron.remote;
const BrowserWindow = electron.remote.BrowserWindow;
const App_Base = requireGlobal("apps/base.js");
const App_Base = require("./../../apps/base.js");
module.exports = class App extends App_Base
{
......@@ -158,7 +158,7 @@ module.exports = class App extends App_Base
{
await super.run();
this.shrineEditor = new Shrine_Editor(this.node.querySelector(".shrine-canvas"), this.node, this.project, this.loader, this.stringTable);
this.shrineEditor = new Shrine_Editor(undefined, this.node, this.project, this.loader, this.stringTable);
this.initTools();
// 000 = ivy shrine
......
......@@ -8,7 +8,7 @@ const fs = require('fs-extra');
const path = require('path');
const Binary_File_Loader = require("binary-file").Loader;
const BFRES_Parser = requireGlobal('lib/bfres/parser.js');
const BFRES_Parser = require('./../../../../lib/bfres/parser.js');
/**
* Class to load the shrine model and its textures
*/
......
......@@ -6,7 +6,7 @@
const path = require("path");
const Mubin_Editor = require("./../../../lib/mubin_editor/editor");
const Mubin_Editor = require("./../../../lib/mubin_editor/editor").default;
const Shrine_Model_Loader = require("./shrine/model_loader");
const Shrine_Creator = require("./shrine/creator");
......
This diff is collapsed.
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: [
"<rootDir>/lib"
],
transform: {
"^.+\\.tsx?$": "ts-jest"
},
//testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
moduleFileExtensions: [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
],
}
\ No newline at end of file
......@@ -12,7 +12,6 @@ module.exports = class Renderer_Helper_Selector extends Base
constructor(threeContext, scene, camera, canvasNode)
{
super(threeContext);
this.camera = camera;
this.scene = scene;
this.canvasNode = canvasNode;
......
......@@ -4,10 +4,6 @@
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
const Binary_File_Parser = require('binary-file').Parser;
const BFRES_FileTypes = requireGlobal('./lib/bfres/file_types.js');
const Content_Types = requireGlobal("lib/bfres/content_types.json");
module.exports = class EMBEDDED_Parser
{
constructor(parser, entry, contentType)
......
......@@ -4,10 +4,6 @@
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
const Binary_File_Parser = require('binary-file').Parser;
const BFRES_FileTypes = requireGlobal('./lib/bfres/file_types.js');
const Content_Types = requireGlobal("lib/bfres/content_types.json");
module.exports = class FSHU_Parser
{
constructor(parser, entry, contentType)
......
......@@ -4,8 +4,6 @@
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
const Binary_File_Parser = require('binary-file').Parser;
const BFRES_FileTypes = requireGlobal('./lib/bfres/file_types.js');
const GX2_Block_Handler = require('./../gx2/block_handler');
const Content_Types = require("./../content_types.json");
......@@ -28,7 +26,7 @@ module.exports = class FTEX_Parser
this.mipmapStartLevel = (this.contentType == Content_Types.MIPMAP) ? 1 : 0;
this.ftexReducer = new FTEX_Reducer();
this.textureTypes = requireGlobal("lib/bfres/gx2/texture_types.json");
this.textureTypes = require("./../gx2/texture_types.json");
this.loader = null;
}
......
......@@ -3,7 +3,6 @@
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
const BFRES_FileTypes = requireGlobal('./lib/bfres/file_types.js');
module.exports = class GX2_Block_Handler
{
......@@ -14,7 +13,7 @@ module.exports = class GX2_Block_Handler
this.format = format;
this.colorChannels = 4;
let textureTypes = requireGlobal("lib/bfres/gx2/texture_types.json");
let textureTypes = require("./texture_types.json");
if(textureTypes[format] == null)
throw `GX2: unknown texture format '${format}'!`;
......
......@@ -5,7 +5,7 @@
*/
const Binary_File_Parser = require('binary-file').Parser;
const BFRES_FileTypes = requireGlobal('./lib/bfres/file_types.js');
const BFRES_FileTypes = require('./file_types');
const Content_Types = require("./content_types.json");
......@@ -97,7 +97,7 @@ module.exports = class BFRES_Parser
if(this.autoLoad && fileInfo.preload === true && entry.namePointer != 0)
{
const Parser_Class = requireGlobal(fileInfo.parser);
const Parser_Class = require("./../../" + fileInfo.parser); // @TODO make this nicer and work with TS
entry.parser = new Parser_Class(this.parser, entry, this.contentType);
entry.parser.loader = this.loader;
......
......@@ -6,7 +6,7 @@
const fs = require("fs");
const BFRES_Parser = require("./parser.js");
const HTML_Loader = requireGlobal("lib/html_loader.js");
const HTML_Loader = require("./../html-loader.js").default;
const BFRES_FileTypes = require("./file_types.js");
module.exports = class BFRES_Renderer
......
......@@ -4,27 +4,29 @@
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
const os = require('os');
const fs = require("fs-extra");
const path = require('path');
import * as os from 'os';
import * as fs from "fs-extra";
import * as path from 'path';
const Config_Manager = require("./manager.js");
import ConfigManager from "./manager";
import Service from '../injector/service';
const CONFIG_NAME = "config.json";
const CONFIG_DEF_NAME = "config.default.json";
const CONFIG_DIR = path.join(os.homedir(), ".ice-spear");
module.exports = class Main_Config extends Config_Manager
@Service()
export default class MainConfig extends ConfigManager
{
constructor()
{
super();
this.load();
this.load(path.join(__BASE_PATH, CONFIG_DEF_NAME));
}
load()
load(filePath: string): boolean
{
const defaultConfBuffer = fs.readFileSync(path.join(__BASE_PATH, CONFIG_DEF_NAME));
const defaultConfBuffer = fs.readFileSync(filePath);
const defaultConf = JSON.parse(defaultConfBuffer.toString("utf8"));
if(!super.load(path.join(CONFIG_DIR, CONFIG_NAME)))
......@@ -38,9 +40,10 @@ module.exports = class Main_Config extends Config_Manager
}
this.addMissingSettings(this.config, defaultConf);
return true;
}
addMissingSettings(userConfig, defaultConf)
addMissingSettings(userConfig: any, defaultConf: any): void
{
for(let i in defaultConf)
{
......@@ -58,7 +61,7 @@ module.exports = class Main_Config extends Config_Manager
}
}
setDefaultValues()
setDefaultValues(): void
{
this.setValue("projects.path", path.join(os.homedir(), ".ice-spear-projects"));
}
......
......@@ -4,42 +4,38 @@
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
const os = require('os');
const fs = require("fs");
const path = require('path');
import * as fs from 'fs-extra';
module.exports = class Config_Manager
export default class ConfigManager
{
constructor()
{
this.path;
this.config = {};
}
path: string;
config: any = {};
load(path)
load(path: string): boolean
{
this.path = path;
if(fs.existsSync(this.path))
{
this.config = JSON.parse(fs.readFileSync(this.path));
try{
this.config = JSON.parse(fs.readFileSync(this.path, {encoding: 'utf8'}));
return true;
} catch (e) {
console.warn(e);
}
return false;
}
save(path = undefined)
save(filePath: string = undefined): void
{
fs.writeFileSync(path || this.path,
fs.writeFileSync(filePath || this.path,
JSON.stringify(this.config, null, 4)
);
}
getConfig()
getConfig(): any
{
return this.config;
}
getValue(name)
getValue(name: string): any
{
let parts = name.split(".");
let ref = this.config;
......@@ -54,7 +50,7 @@ module.exports = class Config_Manager
return ref;
}
setValue(name, value)
setValue(name: string, value: any): void
{
let parts = name.split(".");
let lastPart = parts.pop();
......@@ -73,7 +69,7 @@ module.exports = class Config_Manager
ref[lastPart] = value;
}
pushValue(name, value, unique = false)
pushValue(name: string, value: any, unique = false): void
{
const currVal = this.getValue(name);
if(Array.isArray(currVal))
......
......@@ -4,8 +4,7 @@
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
//const Binary_File_Parser = require('binary-file').Parser;
const BFRES_FileTypes = requireGlobal("lib/bfres/file_types.js");
const BFRES_FileTypes = require("./../../bfres/file_types.js");
module.exports = class Editor_Hex
{
......
......@@ -8,10 +8,10 @@ const electron = require('electron');
const {dialog} = electron.remote;
const Split = require('split.js');
const FMDL_Parser = requireGlobal('./lib/bfres/fmdl/parser.js');
const Renderer = requireGlobal("lib/3d_renderer/renderer.js");
const Filter = requireGlobal("lib/filter.js");
const Converter_OBJ = requireGlobal("lib/model_converter/obj.js");
const FMDL_Parser = require('./../../bfres/fmdl/parser.js');
const Renderer = require("./../../3d_renderer/renderer.js");
const Filter = require("./../../filter.js");
const Converter_OBJ = require("./../../model_converter/obj.js");
module.exports = class Editor_Model
{
......
......@@ -8,7 +8,7 @@ const fs = require("fs");
const {dialog} = require('electron').remote;
const Split = require('split.js');
const BFRES_FileTypes = requireGlobal("lib/bfres/file_types.js");
const BFRES_FileTypes = require("./../../bfres/file_types.js");
module.exports = class Editor_ShaderParams
{
......@@ -32,26 +32,5 @@ module.exports = class Editor_ShaderParams
snapOffset: 60,
gutterSize: 12
});
this.loadData();
}
loadData()
{
if(this.entry.parser == null)
{
let fileInfo = BFRES_FileTypes.info[this.entry.type];
const Parser_Class = requireGlobal(fileInfo.parser);
this.entry.parser = new Parser_Class(this.parser, this.entry, this.bfres.contentType);
this.entry.parser.parse();
}
this.render();
}
render()
{
}
};
......@@ -9,7 +9,7 @@ const {dialog} = require('electron').remote;
const Split = require('split.js');
const PNG = require("png-lib");
const BFRES_FileTypes = requireGlobal("lib/bfres/file_types.js");
const BFRES_FileTypes = require("./../../bfres/file_types.js");
module.exports = class Editor_Texture
{
......@@ -44,7 +44,7 @@ module.exports = class Editor_Texture
if(this.entry.parser == null)
{
let fileInfo = BFRES_FileTypes.info[this.entry.type];
const Parser_Class = requireGlobal(fileInfo.parser);
const Parser_Class = require("./../../" + fileInfo.parser);
this.entry.parser = new Parser_Class(this.parser, this.entry, this.bfres.contentType);
this.entry.parser.parse();
......
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
import 'reflect-metadata';
import * as assert from 'assert';
import HtmlLoader from './html-loader';
import { Inject } from './injector';
(global as any).__BASE_PATH = "";
(global as any).document = {
createRange: () => {
return {
createContextualFragment: (html: string) => {
return html;
}
}
}
};
class Test {
@Inject({instanced: true, args: ["test"]})
htmlLoader: HtmlLoader;
};
describe("HtmlLoaderTest", () =>
{
test("isInjectable", () =>
{
assert.notEqual((new Test()).htmlLoader, undefined);
});
test("creation", () =>
{
let htmlLoader: HtmlLoader = new HtmlLoader("testPath");
assert.notEqual(htmlLoader, undefined);
});
/**
* loading for the first time loads the data from the file
*/
test("loadingHtmlForTheFirstTime", () =>
{
let htmlLoader: HtmlLoader = new HtmlLoader("testPath");
(htmlLoader as any).loadFile = (filePath: string) => "<div>test</div>";
assert.deepEqual(htmlLoader.create(), "<div>test</div>");
});
/**
* loading for the second time should not load the file again
*/
test("loadingHtmlForTheSecondTime", () =>
{
let htmlLoader: HtmlLoader = new HtmlLoader("testPath");
(htmlLoader as any).loadFile = (filePath: string) => "<div>test</div>";
assert.deepEqual(htmlLoader.create(), "<div>test</div>");
(htmlLoader as any).loadFile = (filePath: string) => "<div>already loaded</div>";
assert.deepEqual(htmlLoader.create(), "<div>test</div>");
});
});
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
import * as fs from 'fs';
import * as path from 'path';
import Service from './injector/service';
declare var __BASE_PATH: string;
@Service()
export default class HtmlLoader
{
private htmlFilePath: string;
private htmlText: string;
constructor(filePath: string)
{
this.htmlFilePath = path.join(__BASE_PATH, filePath);
}
private loadFile(filePath: string): string
{
return fs.readFileSync(filePath, 'utf8');
}
/**
* creates a html node from a file
*/
create()
{
if(!this.htmlText) {
this.htmlText = this.loadFile(this.htmlFilePath);
}
return document.createRange().createContextualFragment(this.htmlText);
}
}
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
const fs = require('fs');
const path = require("path");
module.exports = class HTML_Loader
{
constructor(filePath)
{
this.htmlText = fs.readFileSync(path.join(__BASE_PATH, filePath), 'utf8');
}
create()
{
return document.createRange().createContextualFragment(this.htmlText);
}
}
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
export default class InjectionAsyncNameException extends Error {
name = "InjectionAsyncNameException";
constructor(targetService: string, propertyName: string) {
super(
`Injection of '${propertyName}' failed in '${targetService}', no service name was provided.\n` +
`This is necessary for async injects.`
);
}
}
\ No newline at end of file
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
export default class InjectionFailedException extends Error {
name = "InjectionFailedException";
constructor(targetService: string, propertyName: string) {
super(`Injection of '${propertyName}' failed in '${targetService}'`);
}
}
\ No newline at end of file
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
export default class InjectionNoReflectionException extends Error {
name = "InjectionNoReflectionException";
constructor(targetService: string, propertyName: string) {
super(
`Resolving reflection data of '${propertyName}' failed in '${targetService}', ` +
`this might be due to a circular dependency`
);
}
}
\ No newline at end of file
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
export { default as Inject } from './inject';
export { default as InjectAsync } from './inject-async';
export { default as Service } from './service';
export { default as ServiceManager } from './service-manager';
export { default as ServiceInfo } from './service-info';
\ No newline at end of file
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
import InjectOptions from "./inject-options";
import InjectionAsyncNameException from "./exception/injection-async-name-exception";
import ServiceManager from "./service-manager";
import injectService from "./inject-service";
const InjectAsync = (options: InjectOptions = {}) => <T>(target: T, propertyKey: string) => {
const serviceClassName = options.serviceName;
if(!serviceClassName) {
throw new InjectionAsyncNameException(target.constructor.name, propertyKey);
}
ServiceManager.waitFor(serviceClassName).then(() => {
injectService(options, target, serviceClassName, propertyKey);
});
};
export default InjectAsync;
\ No newline at end of file
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
export default interface InjectOptions
{
instanced?: boolean;
args?: any[];
arg?: any;
serviceName?: string;
};
\ No newline at end of file
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
import ServiceInfo from "./service-info";
import InjectOptions from "./inject-options";
import { ServiceManager } from ".";
import InjectionFailedException from "./exception/injection-failed-exception";
export default function injectService<T>(options: InjectOptions, target: T, serviceClassName: string, propertyKey: string)
{
const targetName = target.constructor.name;
const args = ("arg" in options) ? [options.arg] : options.args;
const service: any = ServiceManager.getUntyped(serviceClassName, args, !!options.instanced);
if(!service) {
throw new InjectionFailedException(targetName, propertyKey);
}
(target as any)[propertyKey] = service;
ServiceInfo.logRelation(targetName, serviceClassName);
}
\ No newline at end of file
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
import InjectionNoReflectionException from "./exception/injection-no-reflection-exception";
import InjectOptions from "./inject-options";
import ServiceManager from "./service-manager";
import injectService from "./inject-service";
/**
* Decorator to inject a service into a property
* @param target target class instance (not the service)
* @param propertyKey name of the property to set
*/
const Inject = (options: InjectOptions = {}) => <T>(target: T, propertyKey: string) => {
const property = Reflect.getMetadata("design:type", target, propertyKey);
const targetName = target.constructor.name;
if(!property) {
console.log(ServiceManager);
throw new InjectionNoReflectionException(targetName, propertyKey);
}
const serviceClassName = property.__serviceName || property.name;
injectService(options, target, serviceClassName, propertyKey);
};
export default Inject;
\ No newline at end of file
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
export interface ServiceRelation {
targetName: string,
serviceName: string
}
/**
* Manager to store and create services classes and instances
*/
const ServiceInfo = new class {
private serviceRelations: ServiceRelation[] = [];
logRelation(targetName: string, serviceName: string): void
{
this.serviceRelations.push({targetName, serviceName});
}
exportAsMermaid(): string {
return "graph TD\n" +
this.serviceRelations.map(
relation => `${relation.targetName} --> ${relation.serviceName}`
).join("\n");
}
};
export default ServiceInfo;
\ No newline at end of file
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
import * as assert from 'assert';
describe('ServiceManager', () => {
test('todo', () => {
});
});
\ No newline at end of file
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
interface ServiceClassStorage {
[key: string]: any
};
interface ServiceInstanceStorage {
[key: string]: Object
};
interface AsyncServiceWaitList {
serviceName: string;
resolve: Function;
}
/**
* Manager to store and create services classes and instances
*/
const ServiceManager = new class {
private serviceClasses: ServiceClassStorage = {};
private instances: ServiceInstanceStorage = {};
private asyncWaitList: AsyncServiceWaitList[] = [];
/**
* Receive a service by class
* @param serviceClass class to receive
* @param instanced if true, a new instance is created every time
* @returns class instance, may return undefined
*/
get<T>(serviceClass: Function, args: any[] = [], instanced: boolean = false): T {
return (this.getUntyped(serviceClass.name, args, instanced) as T);
}
/**
* same as get(), but untyped
* @param serviceName name if the service to receive
* @param instanced if true, a new instance is created every time
* @returns class instance, may return undefined
*/
getUntyped(serviceName: string, args: any[] = [], instanced: boolean = false): any {
if (!this.instances[serviceName] || instanced) {
const serviceClass = this.serviceClasses[serviceName];
if(!serviceClass) {
return undefined;
}
const serviceInstance = new serviceClass(...args);
if (instanced) {
return serviceInstance;
}
this.set(serviceName, serviceInstance);
}
return this.instances[serviceName];
}
/**
* registers a new service class
* @param serviceName name
* @param serviceClass class
*/
register<T>(serviceName: string, serviceClass: T): void {
this.serviceClasses[serviceName] = serviceClass;
}
/**
* sets an instance of a service
* @param serviceName name
* @param serviceInstance instance
*/
set(serviceName: string, serviceInstance: any) {
this.instances[serviceName] = serviceInstance;
this.asyncWaitList = this.asyncWaitList.filter(entry => {
if(entry.serviceName == serviceName) {
entry.resolve();
return false;
}
return true;
});
}
waitFor(serviceName: string): Promise<void> {
if(this.instances[serviceName])
return;
return new Promise((resolve): void => {
this.asyncWaitList.push({serviceName, resolve});
});
}
};
export default ServiceManager;
\ No newline at end of file
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
import {ClassConstructorType, ClassDecoratorType} from './type';
import ServiceManager from './service-manager';
/**
* Decorator to register a class as a service
*/
const Service = () : ClassDecoratorType<ClassConstructorType<object>> => {
return (target: ClassConstructorType<object>) => {
ServiceManager.register(target.name, target);
};
};
export default Service;
\ No newline at end of file
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
import 'reflect-metadata';
import * as assert from 'assert';
describe("InjectorCircularTest", () =>
{
/**
* test three classes that each inject the next one
* should result in an custom exception
*/
test("circularInjectionTest", async () =>
{
try {
const CircularA = (await import('./classes/circular-a')).default;
let serviceA = new CircularA();
} catch (e) {
return assert.equal((e as any).name, "InjectionNoReflectionException");
}
assert.fail("No Exception was thrown");
});
});
\ No newline at end of file
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
import Inject from "../../inject";
import Service from "../../service";
import CircularB from "./circular-c";
@Service()
export default class CircularA {
@Inject()
serviceB: CircularB;
};
\ No newline at end of file
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
import Inject from "../../inject";
import Service from "../../service";
import CircularC from "./circular-c";
@Service()
export default class CircularB {
@Inject()
serviceC: CircularC;
};
\ No newline at end of file
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
import Inject from "../../inject";
import Service from "../../service";
import CircularA from "./circular-a";
@Service()
export default class CircularC {
@Inject()
serviceA: CircularA;
};
\ No newline at end of file
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
import Inject from "../../inject";
import Service from "../../service";
import CircularA from "./circular-a";
class NotAService {};
@Service()
export default class InjectUnknown {
@Inject()
service: NotAService;
};
\ No newline at end of file
/**
* @copyright 2018 - Max Bebök
* @author Max Bebök
* @license GNU-GPLv3 - see the "LICENSE" file in the root directory
*/
import 'reflect-metadata';
import * as assert from 'assert';
import Inject from './../inject';
import Service from '../service';
/**
* !! IMPORTANT !!
* due to the globally scoped 'Reflect' object, metadata for classes stay between tests
* there is also no easy way to reset them
*/
describe("InjectorIntegrationTest", () =>
{
/**
* test injection of a single existing service
*/
test("canInjectSingleService", async () =>
{
@Service()
class TestA0 {};
class TestA1 { @Inject() service: TestA0}
const testA1 = new TestA1();
assert.equal(testA1.service instanceof TestA0, true);
});
/**
* test injection of a single service that doesn't exists
* should throw an exception
*/
test("injectSingleUnknownService", () =>
{
try {
class TestB0 {};
class TestB1 { @Inject() service: TestB0}
const testB = new TestB1();
} catch (e) {
return assert.equal((e as any).name, "InjectionFailedException");
}
assert.fail("No Exception was thrown");
});
/**
* test injection of a single service that doesn't exists
* should throw an exception
*/
test("injectMultipleServices", () =>
{
@Service() class TestC0 {};
@Service() class TestC1 {};
@Service() class TestC2 {};
class TestC3 {
@Inject() service0: TestC0;
@Inject() service1: TestC1;
@Inject() service2: TestC2;
}
const test = new TestC3();
assert.equal(test.service0 instanceof TestC0, true);
assert.equal(test.service1 instanceof TestC1, true);
assert.equal(test.service2 instanceof TestC2, true);
});
/**
* injecting the class in itself should throw
*/
test("injectClassItself", () =>