Skip to content

Commit

Permalink
Merge pull request #235 from tareqimbasher/js-app-refactor
Browse files Browse the repository at this point in the history
JavaScript app refactor
  • Loading branch information
tareqimbasher authored Jul 24, 2024
2 parents b822c62 + bc7c3c0 commit ba46eec
Show file tree
Hide file tree
Showing 195 changed files with 1,123 additions and 1,012 deletions.
2 changes: 1 addition & 1 deletion src/Apps/NetPad.Apps.App/App/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
},
"overrides": [
{
"files": ["src/core/@domain/api.ts", "src/core/@plugins/omnisharp/api.ts"],
"files": ["src/core/@application/api.ts", "src/core/@plugins/omnisharp/api.ts"],
"rules": {
"@typescript-eslint/ban-ts-comment": "off"
}
Expand Down
12 changes: 6 additions & 6 deletions src/Apps/NetPad.Apps.App/App/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 2 additions & 9 deletions src/Apps/NetPad.Apps.App/App/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,19 @@
"testEnvironment": "jsdom",
"transform": {
"\\.(css|less|sass|scss|styl|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "jest-transform-stub",
"\\.(ts|html)$": "@aurelia/ts-jest"
"\\.(ts|html)$": ["@aurelia/ts-jest", { "isolatedModules": true }]
},
"moduleNameMapper": {
"@common/(.*)": "<rootDir>/src/core/@common/$1",
"@common": "<rootDir>/src/core/@common",
"@domain/(.*)": "<rootDir>/src/core/@domain/$1",
"@domain": "<rootDir>/src/core/@domain",
"@application/(.*)": "<rootDir>/src/core/@application/$1",
"@application": "<rootDir>/src/core/@application"
},
"collectCoverage": false,
"collectCoverageFrom": [
"src/**/*.ts",
"!src/**/*.d.ts"
],
"globals": {
"ts-jest": {
"isolatedModules": true
}
}
]
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.4.0",
Expand Down
Binary file removed src/Apps/NetPad.Apps.App/App/resources/nuget.png
Binary file not shown.
178 changes: 178 additions & 0 deletions src/Apps/NetPad.Apps.App/App/src/app-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import {IAurelia, IContainer, ILogger, LogLevel} from "aurelia";
import {IHttpClient} from "@aurelia/fetch-client";
import {
Env,
IBackgroundService,
ILoggerRule,
ISettingsService,
IWindowBootstrapperConstructor,
Settings
} from "@application";
import {IPlatform} from "@application/platforms/iplatform";

/**
* Loads main app settings.
*/
export const loadAppSettings = async (builder: IAurelia) => {
const settings = builder.container.get(Settings);
const settingsService = builder.container.get(ISettingsService);
const latestSettings = await settingsService.get();

settings.init(latestSettings.toJSON());
}

/**
* Selects and configures the proper platform.
*/
export const configureAndGetPlatform = async (builder: IAurelia) => {
const platformType = Env.isRunningInElectron()
? (await import("@application/platforms/electron/electron-platform")).ElectronPlatform
: (await import("@application/platforms/browser/browser-platform")).BrowserPlatform;

const platform = new platformType() as IPlatform;

platform.configure(builder);

return platform;
}

/**
* Selects and configures the correct app entry point. An entry point is a view-model representing the window
* that will be the entry point for the Aurelia app.
*/
export const configureAndGetAppEntryPoint = async (builder: IAurelia) => {
const startupOptions = builder.container.get(URLSearchParams);

// Determine which window needs to be bootstrapped using the 'win' query parameter of the current window
let windowName = startupOptions.get("win");

if (!windowName && !Env.isRunningInElectron()) {
windowName = "main";
}

let bootstrapperCtor: IWindowBootstrapperConstructor;

if (windowName === "main")
bootstrapperCtor = (await import("./windows/main/main-window-bootstrapper")).MainWindowBootstrapper;
else if (windowName === "script-config")
bootstrapperCtor = (await import("./windows/script-config/script-config-window-bootstrapper")).ScriptConfigWindowBootstrapper;
else if (windowName === "data-connection")
bootstrapperCtor = (await import("./windows/data-connection/data-connection-window-bootstrapper")).DataConnectionWindowBootstrapper;
else if (windowName === "settings")
bootstrapperCtor = (await import("./windows/settings/settings-window-bootstrapper")).SettingsWindowBootstrapper;
else if (windowName === "output")
bootstrapperCtor = (await import("./windows/output/output-window-bootstrapper")).OutputWindowBootstrapper;
else if (windowName === "code")
bootstrapperCtor = (await import("./windows/code/code-window-bootstrapper")).CodeWindowBootstrapper;
else
throw new Error(`Unrecognized window: ${windowName}`);

const bootstrapper = new bootstrapperCtor(builder.container.get(ILogger));

bootstrapper.registerServices(builder);

return bootstrapper.getEntry();
}

export const startBackgroundServices = async (container: IContainer) => {
const backgroundServices = container.getAll(IBackgroundService);

const logger = container.get(ILogger);
logger.debug(`Starting ${backgroundServices.length} background services`, backgroundServices.map(x => x.constructor.name));

for (const backgroundService of backgroundServices) {
try {
await backgroundService.start();
} catch (ex) {
if (ex instanceof Error)
logger.error(`Error starting background service ${backgroundService.constructor.name}. ${ex.toString()}`);
}
}
}

export const stopBackgroundServices = async (container: IContainer) => {
const backgroundServices = container.getAll(IBackgroundService);

const logger = container.get(ILogger);
logger.debug(`Stopping ${backgroundServices.length} background services`, backgroundServices.map(x => x.constructor.name));

for (const backgroundService of backgroundServices) {
try {
backgroundService.stop();

const dispose = backgroundService["dispose" as keyof typeof backgroundService];
if (typeof dispose === "function") {
backgroundService["dispose" as keyof typeof backgroundService]();
}
} catch (ex) {
if (ex instanceof Error)
logger.error(`Error stopping background service ${backgroundService.constructor.name}. ${ex.toString()}`);
}
}
};

export const configureFetchClient = (container: IContainer) => {
const client = container.get(IHttpClient);
const logger = container.get(ILogger).scopeTo("http-client");

const isAbortError = (error: unknown) => error instanceof Error && error.name?.startsWith("AbortError");

client.configure(config =>
config
.useStandardConfiguration()
.withInterceptor({
requestError(error: unknown): Request | Response | Promise<Request | Response> {
if (!isAbortError(error)) logger.error("Request Error", error);
throw error;
},
responseError(error: unknown, request?: Request): Response | Promise<Response> {
if (!isAbortError(error)) logger.error("Response Error", error);
throw error;
}
})
);
}

export const logRules: ILoggerRule[] = [
{
loggerRegex: new RegExp(/AppLifeCycle/),
logLevel: Env.isProduction ? LogLevel.warn : LogLevel.debug
},
...(Env.isDebug ? [
// When in dev mode these loggers can get a bit chatty, increase their min level.
{
loggerRegex: new RegExp(/.\.ComponentLifecycle/),
logLevel: LogLevel.warn
},
{
loggerRegex: new RegExp(/ShortcutManager/),
logLevel: LogLevel.warn
},
{
loggerRegex: new RegExp(/ViewerHost/),
logLevel: LogLevel.warn
},
{
loggerRegex: new RegExp(/ContextMenu/),
logLevel: LogLevel.warn
},
{
loggerRegex: new RegExp(/SignalRIpcGateway/),
logLevel: LogLevel.warn
},
{
loggerRegex: new RegExp(/ElectronIpcGateway/),
logLevel: LogLevel.warn
},
{
loggerRegex: new RegExp(/ElectronEventSync/),
logLevel: LogLevel.warn
},
{
// Aurelia's own debug messages when evaluating HTML case expressions
loggerRegex: new RegExp(/^Case-#/),
logLevel: LogLevel.warn
},
] : []
),
];
89 changes: 89 additions & 0 deletions src/Apps/NetPad.Apps.App/App/src/app-life-cycle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import * as appActions from "./app-actions";
import {IContainer, ILogger} from "aurelia";
import {
AppActivatedEvent,
AppActivatingEvent,
AppCreatedEvent,
AppCreatingEvent,
AppDeactivatedEvent,
AppDeactivatingEvent,
IEventBus,
IIpcGateway
} from "@application";
import {IAppWindowEvent} from "@application/windows/app-windows";

/**
* Actions that run at specific points in the app's lifecycle
*/
export class AppLifeCycle {
constructor(private readonly startupParams: URLSearchParams,
@ILogger private readonly logger: ILogger,
@IEventBus private readonly eventBus: IEventBus,
@IContainer private readonly container: IContainer) {
this.logger = this.logger.scopeTo(nameof(AppLifeCycle));
}

public async creating(): Promise<void> {
this.logger.debug("App being created with options:", this.startupParams.toString());
this.eventBus.publish(new AppCreatingEvent());

await this.container.get(IIpcGateway).start();
}

public async hydrating(): Promise<void> {
this.logger.debug("App hydrating...");
}

public async hydrated(): Promise<void> {
this.logger.debug("App hydrated");
this.eventBus.publish(new AppCreatedEvent());
}

public async activating(): Promise<void> {
this.logger.debug("App activating...");

appActions.configureFetchClient(this.container);
await appActions.startBackgroundServices(this.container);

this.eventBus.publish(new AppActivatingEvent());
}

public async activated(): Promise<void> {
this.logger.debug("App activated");

const bc = new BroadcastChannel("windows");
bc.postMessage(<IAppWindowEvent>{
type: "activated",
windowName: this.startupParams.get("win")
});
bc.close();

this.eventBus.publish(new AppActivatedEvent());
}

public async deactivating(): Promise<void> {
this.logger.debug("App deactivating...");
this.eventBus.publish(new AppDeactivatingEvent());

await appActions.stopBackgroundServices(this.container);

const ipcGateways = this.container.getAll(IIpcGateway);
for (const ipcGateway of ipcGateways) {
await ipcGateway.stop();
ipcGateway.dispose();
}
}

public async deactivated(): Promise<void> {
this.logger.debug("App deactivated");

const bc = new BroadcastChannel("windows");
bc.postMessage(<IAppWindowEvent>{
type: "deactivated",
windowName: this.startupParams.get("win")
});
bc.close();

this.eventBus.publish(new AppDeactivatedEvent());
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export class ApiClientBase {
/**
* When overriden, intercepts the fetch call.
* When overriden, intercepts the fetch call an ApiClient makes.
* @param url Request url.
* @param options Request options.
* @param fetchCall The default fetch call.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
//----------------------
// ReSharper disable InconsistentNaming
import {IHttpClient} from "@aurelia/fetch-client";
import {ApiClientBase} from "@domain/api-client-base";
import {ApiClientBase} from "@application/api-client-base";


export interface IAppApiClient {
Expand Down Expand Up @@ -101,7 +101,7 @@ export class AppApiClient extends ApiClientBase implements IAppApiClient {
let result200: any = null;
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
result200 = resultData200 !== undefined ? resultData200 : <any>null;

return result200;
});
} else if (status !== 200 && status !== 204) {
Expand Down Expand Up @@ -761,7 +761,7 @@ export class DataConnectionsApiClient extends ApiClientBase implements IDataConn
let result200: any = null;
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
result200 = resultData200 !== undefined ? resultData200 : <any>null;

return result200;
});
} else if (status !== 200 && status !== 204) {
Expand Down Expand Up @@ -840,7 +840,7 @@ export class DataConnectionsApiClient extends ApiClientBase implements IDataConn
let result200: any = null;
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
result200 = resultData200 !== undefined ? resultData200 : <any>null;

return result200;
});
} else if (status !== 200 && status !== 204) {
Expand Down Expand Up @@ -2168,7 +2168,7 @@ export class SessionApiClient extends ApiClientBase implements ISessionApiClient
let result200: any = null;
let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver);
result200 = resultData200 !== undefined ? resultData200 : <any>null;

return result200;
});
} else if (status !== 200 && status !== 204) {
Expand Down Expand Up @@ -7753,4 +7753,4 @@ function throwException(message: string, status: number, response: string, heade
throw result;
else
throw new ApiException(message, status, response, headers, null);
}
}
Loading

0 comments on commit ba46eec

Please sign in to comment.