added ability to revert (#21)

This commit is contained in:
undergroundwires
2020-07-15 19:04:56 +01:00
parent 57028987f1
commit 9c063d59de
58 changed files with 1448 additions and 265 deletions

View File

@@ -8,7 +8,7 @@ import { Signal } from '@/infrastructure/Events/Signal';
import { parseApplication } from '../Parser/ApplicationParser';
import { IApplicationState } from './IApplicationState';
import { Script } from '@/domain/Script';
import { Application } from '@/domain/Application';
import { IApplication } from '@/domain/IApplication';
import { IApplicationCode } from './Code/IApplicationCode';
import applicationFile from 'js-yaml-loader!@/application/application.yaml';
@@ -34,7 +34,7 @@ export class ApplicationState implements IApplicationState {
private constructor(
/** Inner instance of the all scripts */
public readonly app: Application,
public readonly app: IApplication,
/** Initially selected scripts */
public readonly defaultScripts: Script[]) {
this.selection = new UserSelection(app, defaultScripts);
@@ -42,5 +42,3 @@ export class ApplicationState implements IApplicationState {
this.filter = new UserFilter(app);
}
}
export { IApplicationState, IUserFilter };

View File

@@ -1,16 +1,19 @@
import { SelectedScript } from '@/application/State/Selection/SelectedScript';
import { IUserSelection } from '@/application/State/Selection/IUserSelection';
import { UserScriptGenerator } from './UserScriptGenerator';
import { IUserSelection } from './../Selection/IUserSelection';
import { Signal } from '@/infrastructure/Events/Signal';
import { IApplicationCode } from './IApplicationCode';
import { IScript } from '@/domain/IScript';
import { IUserScriptGenerator } from './IUserScriptGenerator';
export class ApplicationCode implements IApplicationCode {
public readonly changed = new Signal<string>();
public current: string;
private readonly generator: UserScriptGenerator;
private readonly generator: IUserScriptGenerator = new UserScriptGenerator();
constructor(userSelection: IUserSelection, private readonly version: string) {
constructor(
userSelection: IUserSelection,
private readonly version: string) {
if (!userSelection) { throw new Error('userSelection is null or undefined'); }
if (!version) { throw new Error('version is null or undefined'); }
this.generator = new UserScriptGenerator();
@@ -20,7 +23,7 @@ export class ApplicationCode implements IApplicationCode {
});
}
private setCode(scripts: ReadonlyArray<IScript>) {
private setCode(scripts: ReadonlyArray<SelectedScript>) {
this.current = scripts.length === 0 ? '' : this.generator.buildCode(scripts, this.version);
this.changed.notify(this.current);
}

View File

@@ -0,0 +1,5 @@
import { SelectedScript } from '@/application/State/Selection/SelectedScript';
export interface IUserScriptGenerator {
buildCode(selectedScripts: ReadonlyArray<SelectedScript>, version: string): string;
}

View File

@@ -1,7 +1,8 @@
import { SelectedScript } from '@/application/State/Selection/SelectedScript';
import { IUserScriptGenerator } from './IUserScriptGenerator';
import { CodeBuilder } from './CodeBuilder';
import { Script } from '@/domain/Script';
const adminRightsScript = {
export const adminRightsScript = {
name: 'Ensure admin privileges',
code: 'fltmc >nul 2>&1 || (\n' +
' echo This batch script requires administrator privileges. Right-click on\n' +
@@ -11,17 +12,19 @@ const adminRightsScript = {
')',
};
export class UserScriptGenerator {
public buildCode(scripts: ReadonlyArray<Script>, version: string): string {
if (!scripts) { throw new Error('scripts is undefined'); }
if (!scripts.length) { throw new Error('scripts are empty'); }
export class UserScriptGenerator implements IUserScriptGenerator {
public buildCode(selectedScripts: ReadonlyArray<SelectedScript>, version: string): string {
if (!selectedScripts) { throw new Error('scripts is undefined'); }
if (!selectedScripts.length) { throw new Error('scripts are empty'); }
if (!version) { throw new Error('version is undefined'); }
const builder = new CodeBuilder()
.appendLine('@echo off')
.appendCommentLine(`https://privacy.sexy — v${version}${new Date().toUTCString()}`)
.appendFunction(adminRightsScript.name, adminRightsScript.code).appendLine();
for (const script of scripts) {
builder.appendFunction(script.name, script.code).appendLine();
for (const selection of selectedScripts) {
const name = selection.revert ? `${selection.script.name} (revert)` : selection.script.name;
const code = selection.revert ? selection.script.revertCode : selection.script.code;
builder.appendFunction(name, code).appendLine();
}
return builder.appendLine()
.appendLine('pause')

View File

@@ -1,5 +1,5 @@
import { IFilterResult } from './IFilterResult';
import { IScript } from '@/domain/Script';
import { IScript } from '@/domain/IScript';
import { ICategory } from '@/domain/ICategory';
export class FilterResult implements IFilterResult {

View File

@@ -1,6 +1,7 @@
import { IScript } from '@/domain/IScript';
import { FilterResult } from './FilterResult';
import { IFilterResult } from './IFilterResult';
import { Application } from '../../../domain/Application';
import { IApplication } from '@/domain/IApplication';
import { IUserFilter } from './IUserFilter';
import { Signal } from '@/infrastructure/Events/Signal';
@@ -8,7 +9,7 @@ export class UserFilter implements IUserFilter {
public readonly filtered = new Signal<IFilterResult>();
public readonly filterRemoved = new Signal<void>();
constructor(private application: Application) {
constructor(private application: IApplication) {
}
@@ -18,11 +19,9 @@ export class UserFilter implements IUserFilter {
}
const filterLowercase = filter.toLocaleLowerCase();
const filteredScripts = this.application.getAllScripts().filter(
(script) =>
script.name.toLowerCase().includes(filterLowercase) ||
script.code.toLowerCase().includes(filterLowercase));
(script) => isScriptAMatch(script, filterLowercase));
const filteredCategories = this.application.getAllCategories().filter(
(script) => script.name.toLowerCase().includes(filterLowercase));
(category) => category.name.toLowerCase().includes(filterLowercase));
const matches = new FilterResult(
filteredScripts,
@@ -37,3 +36,16 @@ export class UserFilter implements IUserFilter {
this.filterRemoved.notify();
}
}
function isScriptAMatch(script: IScript, filterLowercase: string) {
if (script.name.toLowerCase().includes(filterLowercase)) {
return true;
}
if (script.code.toLowerCase().includes(filterLowercase)) {
return true;
}
if (script.revertCode) {
return script.revertCode.toLowerCase().includes(filterLowercase);
}
return false;
}

View File

@@ -1,11 +1,13 @@
import { SelectedScript } from './SelectedScript';
import { ISignal } from '@/infrastructure/Events/Signal';
import { IScript } from '@/domain/IScript';
export interface IUserSelection {
readonly changed: ISignal<ReadonlyArray<IScript>>;
readonly selectedScripts: ReadonlyArray<IScript>;
readonly changed: ISignal<ReadonlyArray<SelectedScript>>;
readonly selectedScripts: ReadonlyArray<SelectedScript>;
readonly totalSelected: number;
addSelectedScript(scriptId: string): void;
addSelectedScript(scriptId: string, revert: boolean): void;
addOrUpdateSelectedScript(scriptId: string, revert: boolean): void;
removeSelectedScript(scriptId: string): void;
selectOnly(scripts: ReadonlyArray<IScript>): void;
isSelected(script: IScript): boolean;

View File

@@ -0,0 +1,14 @@
import { BaseEntity } from '@/infrastructure/Entity/BaseEntity';
import { IScript } from '@/domain/IScript';
export class SelectedScript extends BaseEntity<string> {
constructor(
public readonly script: IScript,
public readonly revert: boolean,
) {
super(script.id);
if (revert && !script.canRevert()) {
throw new Error('cannot revert an irreversible script');
}
}
}

View File

@@ -1,13 +1,14 @@
import { SelectedScript } from './SelectedScript';
import { IApplication } from '@/domain/IApplication';
import { IUserSelection } from './IUserSelection';
import { InMemoryRepository } from '@/infrastructure/Repository/InMemoryRepository';
import { IScript } from '@/domain/Script';
import { IScript } from '@/domain/IScript';
import { Signal } from '@/infrastructure/Events/Signal';
import { IRepository } from '@/infrastructure/Repository/IRepository';
export class UserSelection implements IUserSelection {
public readonly changed = new Signal<ReadonlyArray<IScript>>();
private readonly scripts = new InMemoryRepository<string, IScript>();
public readonly changed = new Signal<ReadonlyArray<SelectedScript>>();
private readonly scripts: IRepository<string, SelectedScript> = new InMemoryRepository<string, SelectedScript>();
constructor(
private readonly app: IApplication,
@@ -15,33 +16,40 @@ export class UserSelection implements IUserSelection {
selectedScripts: ReadonlyArray<IScript>) {
if (selectedScripts && selectedScripts.length > 0) {
for (const script of selectedScripts) {
this.scripts.addItem(script);
const selected = new SelectedScript(script, false);
this.scripts.addItem(selected);
}
}
}
/** Add a script to users application */
public addSelectedScript(scriptId: string): void {
public addSelectedScript(scriptId: string, revert: boolean): void {
const script = this.app.findScript(scriptId);
if (!script) {
throw new Error(`Cannot add (id: ${scriptId}) as it is unknown`);
}
this.scripts.addItem(script);
const selectedScript = new SelectedScript(script, revert);
this.scripts.addItem(selectedScript);
this.changed.notify(this.scripts.getItems());
}
public addOrUpdateSelectedScript(scriptId: string, revert: boolean): void {
const script = this.app.findScript(scriptId);
const selectedScript = new SelectedScript(script, revert);
this.scripts.addOrUpdateItem(selectedScript);
this.changed.notify(this.scripts.getItems());
}
/** Remove a script from users application */
public removeSelectedScript(scriptId: string): void {
this.scripts.removeItem(scriptId);
this.changed.notify(this.scripts.getItems());
}
public isSelected(script: IScript): boolean {
return this.scripts.exists(script);
return this.scripts.exists(script.id);
}
/** Get users scripts based on his/her selections */
public get selectedScripts(): ReadonlyArray<IScript> {
public get selectedScripts(): ReadonlyArray<SelectedScript> {
return this.scripts.getItems();
}
@@ -51,8 +59,9 @@ export class UserSelection implements IUserSelection {
public selectAll(): void {
for (const script of this.app.getAllScripts()) {
if (!this.scripts.exists(script)) {
this.scripts.addItem(script);
if (!this.scripts.exists(script.id)) {
const selection = new SelectedScript(script, false);
this.scripts.addItem(selection);
}
}
this.changed.notify(this.scripts.getItems());
@@ -78,9 +87,11 @@ export class UserSelection implements IUserSelection {
.forEach((scriptId) => this.scripts.removeItem(scriptId));
}
// Select from unselected scripts
scripts
.filter((script) => !this.scripts.exists(script))
.forEach((script) => this.scripts.addItem(script));
const unselectedScripts = scripts.filter((script) => !this.scripts.exists(script.id));
for (const toSelect of unselectedScripts) {
const selection = new SelectedScript(toSelect, false);
this.scripts.addItem(selection);
}
this.changed.notify(this.scripts.getItems());
}
}