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

@@ -13,7 +13,7 @@
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';
import { ApplicationState, IApplicationState } from '@/application/State/ApplicationState';
import { ApplicationState } from '@/application/State/ApplicationState';
import TheHeader from '@/presentation/TheHeader.vue';
import TheFooter from '@/presentation/TheFooter.vue';
import TheCodeArea from '@/presentation/TheCodeArea.vue';

View File

@@ -1,13 +1,12 @@
import { Category } from '../../domain/Category';
import { Application } from '../../domain/Application';
import { Category } from '@/domain/Category';
import { Application } from '@/domain/Application';
import { IApplication } from '@/domain/IApplication';
import { ApplicationYaml } from 'js-yaml-loader!./../application.yaml';
import { parseCategory } from './CategoryParser';
export function parseApplication(content: ApplicationYaml): Application {
export function parseApplication(content: ApplicationYaml): IApplication {
validate(content);
const categories = new Array<Category>();
if (!content.actions || content.actions.length <= 0) {
throw new Error('Application does not define any action');
}
for (const action of content.actions) {
const category = parseCategory(action);
categories.push(category);
@@ -19,3 +18,12 @@ export function parseApplication(content: ApplicationYaml): Application {
categories);
return app;
}
function validate(content: ApplicationYaml): void {
if (!content) {
throw new Error('application is null or undefined');
}
if (!content.actions || content.actions.length <= 0) {
throw new Error('application does not define any action');
}
}

View File

@@ -1,20 +1,18 @@
import { YamlCategory, YamlScript } from 'js-yaml-loader!./application.yaml';
import { Script } from '@/domain/Script';
import { Category } from '../../domain/Category';
import { Category } from '@/domain/Category';
import { parseDocUrls } from './DocumentationParser';
import { parseScript } from './ScriptParser';
let categoryIdCounter: number = 0;
interface ICategoryChildren {
subCategories: Category[];
subScripts: Script[];
}
export function parseCategory(category: YamlCategory): Category {
if (!category.children || category.children.length <= 0) {
throw Error('Category has no children');
}
ensureValid(category);
const children: ICategoryChildren = {
subCategories: new Array<Category>(),
subScripts: new Array<Script>(),
@@ -31,6 +29,18 @@ export function parseCategory(category: YamlCategory): Category {
);
}
function ensureValid(category: YamlCategory) {
if (!category) {
throw Error('category is null or undefined');
}
if (!category.children || category.children.length === 0) {
throw Error('category has no children');
}
if (!category.category || category.category.length === 0) {
throw Error('category has no name');
}
}
function parseCategoryChild(
categoryOrScript: any, children: ICategoryChildren, parent: YamlCategory) {
if (isCategory(categoryOrScript)) {
@@ -38,11 +48,7 @@ function parseCategoryChild(
children.subCategories.push(subCategory);
} else if (isScript(categoryOrScript)) {
const yamlScript = categoryOrScript as YamlScript;
const script = new Script(
/* name */ yamlScript.name,
/* code */ yamlScript.code,
/* docs */ parseDocUrls(yamlScript),
/* is recommended? */ yamlScript.recommend);
const script = parseScript(yamlScript);
children.subScripts.push(script);
} else {
throw new Error(`Child element is neither a category or a script.
@@ -50,7 +56,6 @@ function parseCategoryChild(
}
}
function isScript(categoryOrScript: any): boolean {
return categoryOrScript.code && categoryOrScript.code.length > 0;
}

View File

@@ -1,6 +1,9 @@
import { YamlDocumentable } from 'js-yaml-loader!./application.yaml';
export function parseDocUrls(documentable: YamlDocumentable): ReadonlyArray<string> {
if (!documentable) {
throw new Error('documentable is null or undefined');
}
const docs = documentable.docs;
if (!docs) {
return [];

View File

@@ -0,0 +1,16 @@
import { Script } from '@/domain/Script';
import { YamlScript } from 'js-yaml-loader!./application.yaml';
import { parseDocUrls } from './DocumentationParser';
export function parseScript(yamlScript: YamlScript): Script {
if (!yamlScript) {
throw new Error('script is null or undefined');
}
const script = new Script(
/* name */ yamlScript.name,
/* code */ yamlScript.code,
/* revertCode */ yamlScript.revertCode,
/* docs */ parseDocUrls(yamlScript),
/* isRecommended */ yamlScript.recommend);
return script;
}

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());
}
}

View File

@@ -1,3 +1,4 @@
# Structure documented in "./application.yaml.d.ts" (as code)
name: privacy.sexy
repositoryUrl: https://github.com/undergroundwires/privacy.sexy
actions:
@@ -304,7 +305,7 @@ actions:
SET /A dps_service_running=1
net stop DPS
)
REM del /F /S /Q /A "%windir%\System32\sru*"
del /F /S /Q /A "%windir%\System32\sru*"
IF !dps_service_running! == 1 (
net start DPS
)
@@ -320,10 +321,13 @@ actions:
name: Disable Customer Experience Improvement (CEIP/SQM)
recommend: true
code: reg add "HKLM\Software\Policies\Microsoft\SQMClient\Windows" /v "CEIPEnable" /t REG_DWORD /d "0" /f
revertCode: reg add "HKLM\Software\Policies\Microsoft\SQMClient\Windows" /v "CEIPEnable" /t REG_DWORD /d "1" /f
docs: https://docs.microsoft.com/en-us/windows/win32/devnotes/ceipenable
-
name: Disable Application Impact Telemetry (AIT)
recommend: true
code: reg add "HKLM\Software\Policies\Microsoft\Windows\AppCompat" /v "AITEnable" /t REG_DWORD /d "0" /f
revertCode: reg add "HKLM\Software\Policies\Microsoft\SQMClient\Windows" /v "CEIPEnable" /t REG_DWORD /d "1" /f
-
name: Disable diagnostics telemetry
recommend: true
@@ -343,13 +347,10 @@ actions:
schtasks /change /TN "\Microsoft\Windows\Customer Experience Improvement Program\Consolidator" /DISABLE
schtasks /change /TN "\Microsoft\Windows\Customer Experience Improvement Program\KernelCeipTask" /DISABLE
schtasks /change /TN "\Microsoft\Windows\Customer Experience Improvement Program\UsbCeip" /DISABLE
-
name: Disabling Data Logging Services
recommend: true
code: |-
schtasks /change /TN "\Microsoft\Windows\Customer Experience Improvement Program\Consolidator" /DISABLE
schtasks /change /TN "\Microsoft\Windows\Customer Experience Improvement Program\KernelCeipTask" /DISABLE
schtasks /change /TN "\Microsoft\Windows\Customer Experience Improvement Program\UsbCeip" /DISABLE
revertCode: |-
schtasks /change /TN "\Microsoft\Windows\Customer Experience Improvement Program\Consolidator" /ENABLE
schtasks /change /TN "\Microsoft\Windows\Customer Experience Improvement Program\KernelCeipTask" /ENABLE
schtasks /change /TN "\Microsoft\Windows\Customer Experience Improvement Program\UsbCeip" /ENABLE
-
name: Disable telemetry in data collection policy
recommend: true
@@ -381,21 +382,21 @@ actions:
name: Disable active prompting (pings to MSFT NCSI server)
recommend: false
code: reg add "HKLM\SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters\Internet" /v "EnableActiveProbing" /t REG_DWORD /d "0" /f
revertCode: reg add "HKLM\SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters\Internet" /v "EnableActiveProbing" /t REG_DWORD /d "1" /f
-
name: Opt out from Windows privacy consent
recommend: true
code: |-
reg add "HKCU\SOFTWARE\Microsoft\Personalization\Settings" /v "AcceptedPrivacyPolicy" /t REG_DWORD /d 0 /f
code: reg add "HKCU\SOFTWARE\Microsoft\Personalization\Settings" /v "AcceptedPrivacyPolicy" /t REG_DWORD /d 0 /f
revertCode: reg add "HKCU\SOFTWARE\Microsoft\Personalization\Settings" /v "AcceptedPrivacyPolicy" /t REG_DWORD /d 1 /f
-
name: Disable Windows feedback
recommend: true
docs: https://www.tenforums.com/tutorials/2441-change-feedback-frequency-windows-10-a.html
code: |-
reg add "HKCU\SOFTWARE\Microsoft\Siuf\Rules" /v "NumberOfSIUFInPeriod" /t REG_DWORD /d 0 /f
:: removing this value sets feedback frequency to never
reg delete "HKCU\SOFTWARE\Microsoft\Siuf\Rules" /v "PeriodInNanoSeconds" /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection" /v "DoNotShowFeedbackNotifications" /t REG_DWORD /d 1 /f
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\DataCollection" /v "DoNotShowFeedbackNotifications" /t REG_DWORD /d 1 /f
docs: https://www.tenforums.com/tutorials/2441-change-feedback-frequency-windows-10-a.html
-
name: Disable text and handwriting collection
recommend: true
@@ -525,27 +526,28 @@ actions:
-
name: Deny app access to videos
recommend: true
code: |-
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\videosLibrary" /v "Value" /d "Deny" /t REG_SZ /f
code: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\videosLibrary" /v "Value" /d "Deny" /t REG_SZ /f
revertCode: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\videosLibrary" /v "Value" /d "Allow" /t REG_SZ /f
-
name: Deny app access to pictures
recommend: true
code: |-
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\picturesLibrary" /v "Value" /d "Deny" /t REG_SZ /f
code: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\picturesLibrary" /v "Value" /d "Deny" /t REG_SZ /f
revertCode: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\picturesLibrary" /v "Value" /d "Allow" /t REG_SZ /f
-
name: Deny app access to documents
recommend: true
code: |-
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\documentsLibrary" /v "Value" /d "Deny" /t REG_SZ /f
code: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\documentsLibrary" /v "Value" /d "Deny" /t REG_SZ /f
revertCode: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\documentsLibrary" /v "Value" /d "Allow" /t REG_SZ /f
-
name: Deny app access to bluetooth devices
recommend: true
code: |-
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\bluetoothSync" /v "Value" /d "Deny" /t REG_SZ /f
code: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\bluetoothSync" /v "Value" /d "Deny" /t REG_SZ /f
revertCode: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\bluetoothSync" /v "Value" /d "Allow" /t REG_SZ /f
-
name: Deny app access to text/mms
recommend: true
code: reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\DeviceAccess\Global\{992AFA70-6F47-4148-B3E9-3003349C1548}" /t REG_SZ /v "Value" /d DENY /f
code: reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\DeviceAccess\Global\{992AFA70-6F47-4148-B3E9-3003349C1548}" /t REG_SZ /v "Value" /d "Deny" /f
revertCode: reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\DeviceAccess\Global\{992AFA70-6F47-4148-B3E9-3003349C1548}" /t REG_SZ /v "Value" /d "Allow" /f
-
name: Deny location access
recommend: true
@@ -645,15 +647,19 @@ actions:
name: Disable App Launch Tracking
docs: https://www.thewindowsclub.com/enable-or-disable-app-launch-tracking-in-windows-10
recommend: true
code: reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v "Start_TrackProgs" /d "0" /t REG_DWORD /f
code: reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v "Start_TrackProgs" /d 0 /t REG_DWORD /f
revertCode: reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v "Start_TrackProgs" /d 1 /t REG_DWORD /f
-
name: Disable Inventory Collector
recommend: true
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\AppCompat" /v "DisableInventory" /t REG_DWORD /d 1 /f
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\AppCompat" /v "DisableInventory" /t REG_DWORD /d 1 /f
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\AppCompat" /v "DisableInventory" /t REG_DWORD /d 0 /f
-
name: Disable Website Access of Language List
recommend: true
docs: https://www.tenforums.com/tutorials/82980-turn-off-website-access-language-list-windows-10-a.html
code: reg add "HKCU\Control Panel\International\User Profile" /v "HttpAcceptLanguageOptOut" /t REG_DWORD /d 1 /f
revertCode: reg add "HKCU\Control Panel\International\User Profile" /v "HttpAcceptLanguageOptOut" /t REG_DWORD /d 0 /f
-
name: Disable Auto Downloading Maps
recommend: true
@@ -878,7 +884,16 @@ actions:
schtasks /change /TN "Microsoft\Office\Office ClickToRun Service Monitor" /DISABLE
schtasks /change /TN "Microsoft\Office\OfficeTelemetryAgentFallBack2016" /DISABLE
schtasks /change /TN "Microsoft\Office\OfficeTelemetryAgentLogOn2016" /DISABLE
sc stop "ClickToRunSvc" & sc config "ClickToRunSvc" start= disabled
sc stop "ClickToRunSvc" & sc config "ClickToRunSvc" start=disabled
revertCode: |-
reg add "HKCU\SOFTWARE\Policies\Microsoft\Office\15.0\osm" /v "Enablelogging" /t REG_DWORD /d 1 /f
reg add "HKCU\SOFTWARE\Policies\Microsoft\Office\15.0\osm" /v "EnableUpload" /t REG_DWORD /d 1 /f
reg add "HKCU\SOFTWARE\Policies\Microsoft\Office\16.0\osm" /v "Enablelogging" /t REG_DWORD /d 1 /f
reg add "HKCU\SOFTWARE\Policies\Microsoft\Office\16.0\osm" /v "EnableUpload" /t REG_DWORD /d 1 /f
schtasks /change /TN "Microsoft\Office\Office ClickToRun Service Monitor" /ENABLE
schtasks /change /TN "Microsoft\Office\OfficeTelemetryAgentFallBack2016" /ENABLE
schtasks /change /TN "Microsoft\Office\OfficeTelemetryAgentLogOn2016" /ENABLE
sc config "ClickToRunSvc" start=auto
-
category: Configure browsers
children:
@@ -888,7 +903,7 @@ actions:
-
name: Disable live tile data collection
recommend: true
code: reg add "HKCU\Software\Policies\Microsoft\MicrosoftEdge\Main" /v "PreventLiveTileDataCollection" /t REG_DWORD /d 1 /f
code: reg add "HKCU\Software\Policies\Microsoft\MicrosoftEdge\Main" /v "PreventLiveTileDataCollection" /t REG_DWORD /d 1 /f
-
name: Disable MFU tracking
recommend: true
@@ -1042,6 +1057,7 @@ actions:
name: Disable administrative shares
recommend: true
code: reg add "HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters" /v "AutoShareWks" /t REG_DWORD /d 0 /f
revertCode: reg add "HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters" /v "AutoShareWks" /t REG_DWORD /d 1 /f
-
name: Force enable data execution prevention (DEP)
recommend: false
@@ -1146,6 +1162,11 @@ actions:
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender" /v DisableAntiSpyware /t REG_DWORD /d 1 /f
reg add "HKLM\SYSTEM\CurrentControlSet\Services\MpsSvc" /v "Start" /t REG_DWORD /d 4 /f
reg add "HKLM\SYSTEM\CurrentControlSet\Services\WinDefend" /v "Start" /t REG_DWORD /d 4 /f
revertCode: |-
netsh advfirewall set allprofiles state on
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender" /v DisableAntiSpyware /t REG_DWORD /d 0 /f
reg add "HKLM\SYSTEM\CurrentControlSet\Services\MpsSvc" /v "Start" /t REG_DWORD /d 2 /f
reg add "HKLM\SYSTEM\CurrentControlSet\Services\WinDefend" /v "Start" /t REG_DWORD /d 2 /f
-
name: Disable Smart Screen
recommend: false
@@ -1155,19 +1176,33 @@ actions:
reg add "HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Explorer" /v "SmartScreenEnabled" /t REG_SZ /d "Off" /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppHost" /v "EnableWebContentEvaluation" /t REG_DWORD /d 0 /f
reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\AppHost" /v "EnableWebContentEvaluation" /t REG_DWORD /d 0 /f
revertCode: |-
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\System" /v "EnableSmartScreen" /t REG_DWORD /d 1 /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer" /v "SmartScreenEnabled" /t REG_SZ /d "Warn" /f
reg add "HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Explorer" /v "SmartScreenEnabled" /t REG_SZ /d "Warn" /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppHost" /v "EnableWebContentEvaluation" /t REG_DWORD /d 1 /f
reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\AppHost" /v "EnableWebContentEvaluation" /t REG_DWORD /d 1 /f
-
name: Disable scheduled On Demand anti malware scanner (MRT)
recommend: false
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\MRT" /v "DontOfferThroughWUAU" /t REG_DWORD /d 1 /f
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\MRT" /v "DontOfferThroughWUAU" /t REG_DWORD /d 0 /f
-
name: Disable automatic updates
recommend: false
docs: https://docs.microsoft.com/fr-fr/security-updates/windowsupdateservices/18127152
code: |-
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "NoAutoUpdate" /t "REG_DWORD" /d "0" /f
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "AUOptions" /t "REG_DWORD" /d "2" /f
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "ScheduledInstallDay" /t "REG_DWORD" /d "0" /f
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "ScheduledInstallTime" /t "REG_DWORD" /d "3" /f
sc stop "UsoSvc" & sc config "UsoSvc" start=disabled
revertCode: |-
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "NoAutoUpdate" /t "REG_DWORD" /d "1" /f
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "AUOptions" /t "REG_DWORD" /d "3" /f
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "ScheduledInstallDay" /t "REG_DWORD" /d "0" /f
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "ScheduledInstallTime" /t "REG_DWORD" /d "0" /f
sc config "UsoSvc" start=auto
-
category: UI for privacy
children:
@@ -1175,6 +1210,8 @@ actions:
name: Disable lock screen app notifications
recommend: true
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\System" /v "DisableLockScreenAppNotifications" /t REG_DWORD /d 1 /f
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\System" /v "DisableLockScreenAppNotifications" /t REG_DWORD /d 0 /f
docs: https://www.stigviewer.com/stig/windows_server_2012_member_server/2014-01-07/finding/V-36687
-
name: Disable online content in explorer
recommend: true
@@ -1336,8 +1373,7 @@ actions:
recommend: true
docs: https://docs.microsoft.com/en-us/windows-server/storage/file-server/volume-shadow-copy-service
code: sc stop "VSS" & sc config "VSS" start=disabled
revertCode: sc config vss start=auto
-
category: Remove bloatware
children:
@@ -2074,12 +2110,19 @@ actions:
-
name: Disable Reserved Storage for updates
recommend: false
docs: https://techcommunity.microsoft.com/t5/storage-at-microsoft/windows-10-and-reserved-storage/ba-p/428327
docs:
- https://techcommunity.microsoft.com/t5/storage-at-microsoft/windows-10-and-reserved-storage/ba-p/428327
- https://www.tenforums.com/tutorials/124858-enable-disable-reserved-storage-windows-10-a.html
code: |-
dism /online /Set-ReservedStorageState /State:Disabled /NoRestart
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\ReserveManager" /v "MiscPolicyInfo" /t REG_DWORD /d "2" /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\ReserveManager" /v "ShippedWithReserves" /t REG_DWORD /d "0" /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\ReserveManager" /v "PassedPolicy" /t REG_DWORD /d "0" /f
revertCode: |-
DISM /Online /Set-ReservedStorageState /State:Enabled /NoRestart
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\ReserveManager" /v "MiscPolicyInfo" /t REG_DWORD /d "1" /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\ReserveManager" /v "ShippedWithReserves" /t REG_DWORD /d "1" /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\ReserveManager" /v "PassedPolicy" /t REG_DWORD /d "1" /f
-
name: Run script on start-up [EXPERIMENTAL]
recommend: false

View File

@@ -1,5 +1,5 @@
declare module 'js-yaml-loader!*' {
type CategoryOrScript = YamlCategory | YamlScript;
export type CategoryOrScript = YamlCategory | YamlScript;
type DocumentationUrls = ReadonlyArray<string> | string;
export interface YamlDocumentable {
@@ -9,6 +9,7 @@ declare module 'js-yaml-loader!*' {
export interface YamlScript extends YamlDocumentable {
name: string;
code: string;
revertCode: string;
recommend: boolean;
}

View File

@@ -13,11 +13,11 @@ export class Application implements IApplication {
public readonly name: string,
public readonly repositoryUrl: string,
public readonly version: string,
public readonly categories: ReadonlyArray<ICategory>) {
public readonly actions: ReadonlyArray<ICategory>) {
if (!name) { throw Error('Application has no name'); }
if (!repositoryUrl) { throw Error('Application has no repository url'); }
if (!version) { throw Error('Version cannot be empty'); }
this.flattened = flatten(categories);
this.flattened = flatten(actions);
if (this.flattened.allCategories.length === 0) {
throw new Error('Application must consist of at least one category');
}

View File

@@ -5,9 +5,9 @@ export interface IApplication {
readonly name: string;
readonly repositoryUrl: string;
readonly version: string;
readonly categories: ReadonlyArray<ICategory>;
readonly totalScripts: number;
readonly totalCategories: number;
readonly actions: ReadonlyArray<ICategory>;
getRecommendedScripts(): ReadonlyArray<IScript>;
findCategory(categoryId: number): ICategory | undefined;

View File

@@ -1,9 +1,11 @@
import { IEntity } from './../infrastructure/Entity/IEntity';
import { IEntity } from '../infrastructure/Entity/IEntity';
import { IDocumentable } from './IDocumentable';
export interface IScript extends IEntity<string>, IDocumentable {
readonly name: string;
readonly code: string;
readonly isRecommended: boolean;
readonly documentationUrls: ReadonlyArray<string>;
readonly code: string;
readonly revertCode: string;
canRevert(): boolean;
}

View File

@@ -2,44 +2,56 @@ import { BaseEntity } from '@/infrastructure/Entity/BaseEntity';
import { IScript } from './IScript';
export class Script extends BaseEntity<string> implements IScript {
private static ensureNoEmptyLines(name: string, code: string): void {
if (code.split('\n').some((line) => line.trim().length === 0)) {
throw Error(`Script has empty lines "${name}"`);
}
}
private static ensureCodeHasUniqueLines(name: string, code: string): void {
const lines = code.split('\n')
.filter((line) => this.mayBeUniqueLine(line));
if (lines.length === 0) {
return;
}
const duplicateLines = lines.filter((e, i, a) => a.indexOf(e) !== i);
if (duplicateLines.length !== 0) {
throw Error(`Duplicates detected in script "${name}":\n ${duplicateLines.join('\n')}`);
}
}
private static mayBeUniqueLine(codeLine: string): boolean {
const trimmed = codeLine.trim();
if (trimmed === ')' || trimmed === '(') { // "(" and ")" are used often in batch code
return false;
}
return true;
}
constructor(
public name: string,
public code: string,
public documentationUrls: ReadonlyArray<string>,
public isRecommended: boolean) {
public readonly name: string,
public readonly code: string,
public readonly revertCode: string,
public readonly documentationUrls: ReadonlyArray<string>,
public readonly isRecommended: boolean) {
super(name);
if (code == null || code.length === 0) {
throw new Error('Code is empty or null');
validateCode(name, code);
if (revertCode) {
validateCode(name, revertCode);
if (code === revertCode) {
throw new Error(`${name}: Code itself and its reverting code cannot be the same`);
}
}
Script.ensureCodeHasUniqueLines(name, code);
Script.ensureNoEmptyLines(name, code);
}
public canRevert(): boolean {
return Boolean(this.revertCode);
}
}
export { IScript } from './IScript';
function validateCode(name: string, code: string): void {
if (!code || code.length === 0) {
throw new Error(`Code of ${name} is empty or null`);
}
ensureCodeHasUniqueLines(name, code);
ensureNoEmptyLines(name, code);
}
function ensureNoEmptyLines(name: string, code: string): void {
if (code.split('\n').some((line) => line.trim().length === 0)) {
throw Error(`Script has empty lines "${name}"`);
}
}
function mayBeUniqueLine(codeLine: string): boolean {
const trimmed = codeLine.trim();
if (trimmed === ')' || trimmed === '(') { // "(" and ")" are used often in batch code
return false;
}
return true;
}
function ensureCodeHasUniqueLines(name: string, code: string): void {
const lines = code.split('\n')
.filter((line) => mayBeUniqueLine(line));
if (lines.length === 0) {
return;
}
const duplicateLines = lines.filter((e, i, a) => a.indexOf(e) !== i);
if (duplicateLines.length !== 0) {
throw Error(`Duplicates detected in script "${name}":\n ${duplicateLines.join('\n')}`);
}
}

1
src/global.d.ts vendored
View File

@@ -13,6 +13,7 @@ declare module 'liquor-tree' {
}
interface ICustomLiquorTreeData {
documentationUrls: ReadonlyArray<string>;
isReversible: boolean;
}
/**

View File

@@ -4,6 +4,7 @@ export interface IRepository<TKey, TEntity extends IEntity<TKey>> {
readonly length: number;
getItems(predicate?: (entity: TEntity) => boolean): TEntity[];
addItem(item: TEntity): void;
addOrUpdateItem(item: TEntity): void;
removeItem(id: TKey): void;
exists(item: TEntity): boolean;
exists(id: TKey): boolean;
}

View File

@@ -18,14 +18,24 @@ export class InMemoryRepository<TKey, TEntity extends IEntity<TKey>> implements
public addItem(item: TEntity): void {
if (!item) {
throw new Error('Item is null');
throw new Error('item is null or undefined');
}
if (this.exists(item)) {
if (this.exists(item.id)) {
throw new Error(`Cannot add (id: ${item.id}) as it is already exists`);
}
this.items.push(item);
}
public addOrUpdateItem(item: TEntity): void {
if (!item) {
throw new Error('item is null or undefined');
}
if (this.exists(item.id)) {
this.removeItem(item.id);
}
this.items.push(item);
}
public removeItem(id: TKey): void {
const index = this.items.findIndex((item) => item.id === id);
if (index === -1) {
@@ -34,8 +44,8 @@ export class InMemoryRepository<TKey, TEntity extends IEntity<TKey>> implements
this.items.splice(index, 1);
}
public exists(entity: TEntity): boolean {
const index = this.items.findIndex((item) => item.id === entity.id);
public exists(id: TKey): boolean {
const index = this.items.findIndex((item) => item.id === id);
return index !== -1;
}
}

View File

@@ -9,7 +9,7 @@
<script lang="ts">
import { Component, Prop, Vue, Emit } from 'vue-property-decorator';
import { StatefulVue, IApplicationState } from './StatefulVue';
import { StatefulVue } from './StatefulVue';
import { SaveFileDialog } from './../infrastructure/SaveFileDialog';
import { Clipboard } from './../infrastructure/Clipboard';

View File

@@ -18,7 +18,7 @@
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import CardListItem from './CardListItem.vue';
import { StatefulVue, IApplicationState } from '@/presentation/StatefulVue';
import { StatefulVue } from '@/presentation/StatefulVue';
import { ICategory } from '@/domain/ICategory';
@Component({
@@ -32,7 +32,7 @@ export default class CardList extends StatefulVue {
public async mounted() {
const state = await this.getCurrentStateAsync();
this.setCategories(state.app.categories);
this.setCategories(state.app.actions);
this.onOutsideOfActiveCardClicked(() => {
this.activeCategoryId = null;
});
@@ -52,7 +52,7 @@ export default class CardList extends StatefulVue {
return;
}
const element = document.querySelector(`[data-category="${this.activeCategoryId}"]`);
if (!element.contains(event.target)) {
if (element && !element.contains(event.target)) {
callback();
}
};

View File

@@ -4,7 +4,7 @@ import { INode } from './SelectableTree/INode';
export function parseAllCategories(app: IApplication): INode[] | undefined {
const nodes = new Array<INode>();
for (const category of app.categories) {
for (const category of app.actions) {
const children = parseCategoryRecursively(category);
nodes.push(convertCategoryToNode(category, children));
}
@@ -23,6 +23,7 @@ export function parseSingleCategory(categoryId: number, app: IApplication): INod
export function getScriptNodeId(script: IScript): string {
return script.id;
}
export function getCategoryNodeId(category: ICategory): string {
return `Category${category.id}`;
}
@@ -53,6 +54,7 @@ function convertCategoryToNode(
text: category.name,
children,
documentationUrls: category.documentationUrls,
isReversible: false,
};
}
@@ -62,5 +64,6 @@ function convertScriptToNode(script: IScript): INode {
text: script.name,
children: undefined,
documentationUrls: script.documentationUrls,
isReversible: script.canRevert(),
};
}

View File

@@ -6,7 +6,9 @@
:selectedNodeIds="selectedNodeIds"
:filterPredicate="filterPredicate"
:filterText="filterText"
v-on:nodeSelected="checkNodeAsync($event)">
v-on:nodeSelected="toggleNodeSelectionAsync($event)"
v-on:nodeRevertToggled="handleNodeRevertToggleAsync($event)"
>
</SelectableTree>
</span>
<span v-else>Nooo 😢</span>
@@ -25,6 +27,7 @@
import { parseAllCategories, parseSingleCategory, getScriptNodeId, getCategoryNodeId } from './ScriptNodeParser';
import SelectableTree, { FilterPredicate } from './SelectableTree/SelectableTree.vue';
import { INode } from './SelectableTree/INode';
import { SelectedScript } from '@/application/State/Selection/SelectedScript';
@Component({
components: {
@@ -50,13 +53,13 @@
await this.initializeNodesAsync(this.categoryId);
}
public async checkNodeAsync(node: INode) {
public async toggleNodeSelectionAsync(node: INode) {
if (node.children != null && node.children.length > 0) {
return; // only interested in script nodes
}
const state = await this.getCurrentStateAsync();
if (!this.selectedNodeIds.some((id) => id === node.id)) {
state.selection.addSelectedScript(node.id);
state.selection.addSelectedScript(node.id, false);
} else {
state.selection.removeSelectedScript(node.id);
}
@@ -71,7 +74,7 @@
this.nodes = parseAllCategories(state.app);
}
this.selectedNodeIds = state.selection.selectedScripts
.map((script) => getScriptNodeId(script));
.map((selected) => getScriptNodeId(selected.script));
}
public filterPredicate(node: INode): boolean {
@@ -81,7 +84,7 @@
(category: ICategory) => node.id === getCategoryNodeId(category));
}
private handleSelectionChanged(selectedScripts: ReadonlyArray<IScript>): void {
private handleSelectionChanged(selectedScripts: ReadonlyArray<SelectedScript>): void {
this.selectedNodeIds = selectedScripts
.map((node) => node.id);
}

View File

@@ -0,0 +1,46 @@
<template>
<div class="documentationUrls">
<a v-for="url of this.documentationUrls"
v-bind:key="url"
:href="url"
:alt="url"
target="_blank" class="documentationUrl"
v-tooltip.top-center="url"
v-on:click.stop>
<font-awesome-icon :icon="['fas', 'info-circle']" />
</a>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
/** Wrapper for Liquor Tree, reveals only abstracted INode for communication */
@Component
export default class DocumentationUrls extends Vue {
@Prop() public documentationUrls: string[];
}
</script>
<style scoped lang="scss">
@import "@/presentation/styles/colors.scss";
.documentationUrls {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
}
.documentationUrl {
display: flex;
color: $gray;
cursor: pointer;
vertical-align: middle;
&:hover {
color: $slate;
}
&:not(:first-child) {
margin-left: 0.1em;
}
}
</style>

View File

@@ -1,6 +1,7 @@
export interface INode {
readonly id: string;
readonly text: string;
readonly isReversible: boolean;
readonly documentationUrls: ReadonlyArray<string>;
readonly children?: ReadonlyArray<INode>;
}

View File

@@ -1,17 +1,14 @@
<template>
<div id="node">
<div>{{ this.data.text }}</div>
<div
v-for="url of this.data.documentationUrls"
v-bind:key="url">
<a :href="url"
:alt="url"
target="_blank" class="docs"
v-tooltip.top-center="url"
v-on:click.stop>
<font-awesome-icon :icon="['fas', 'info-circle']" />
</a>
</div>
<div class="item text">{{ this.data.text }}</div>
<RevertToggle
class="item"
v-if="data.isReversible"
:scriptId="data.id" />
<DocumentationUrls
class="item"
v-if="data.documentationUrls && data.documentationUrls.length > 0"
:documentationUrls="this.data.documentationUrls" />
</div>
</template>
@@ -19,8 +16,15 @@
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { INode } from './INode';
import RevertToggle from './RevertToggle.vue';
import DocumentationUrls from './DocumentationUrls.vue';
/** Wrapper for Liquor Tree, reveals only abstracted INode for communication */
@Component
@Component({
components: {
RevertToggle,
DocumentationUrls,
},
})
export default class Node extends Vue {
@Prop() public data: INode;
}
@@ -30,17 +34,15 @@
<style scoped lang="scss">
@import "@/presentation/styles/colors.scss";
#node {
display:flex;
flex-direction: row;
display:flex;
flex-direction: row;
flex-wrap: wrap;
.docs {
color: $gray;
cursor: pointer;
margin-left:5px;
&:hover {
color: $slate;
.text {
display: flex;
align-items: center;
}
.item:not(:first-child) {
margin-left: 5px;
}
}
}
</style>

View File

@@ -12,6 +12,7 @@ export function convertExistingToNode(liquorTreeNode: ILiquorTreeExistingNode):
children: (!liquorTreeNode.children || liquorTreeNode.children.length === 0)
? [] : liquorTreeNode.children.map((childNode) => convertExistingToNode(childNode)),
documentationUrls: liquorTreeNode.data.documentationUrls,
isReversible : liquorTreeNode.data.isReversible,
};
}
@@ -27,6 +28,7 @@ export function toNewLiquorTreeNode(node: INode): ILiquorTreeNewNode {
node.children.map((childNode) => toNewLiquorTreeNode(childNode)),
data: {
documentationUrls: node.documentationUrls,
isReversible: node.isReversible,
},
};
}

View File

@@ -0,0 +1,141 @@
<template>
<div class="checkbox-switch" >
<input type="checkbox" class="input-checkbox"
v-model="isReverted"
@change="onRevertToggledAsync()" >
<div class="checkbox-animate">
<span class="checkbox-off">revert</span>
<span class="checkbox-on">revert</span>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue';
import { INode } from './INode';
import { SelectedScript } from '@/application/State/Selection/SelectedScript';
@Component
export default class RevertToggle extends StatefulVue {
@Prop() public scriptId: string;
public isReverted = false;
public async mounted() {
const state = await this.getCurrentStateAsync();
state.selection.changed.on(this.handleSelectionChanged);
}
public async onRevertToggledAsync() {
const state = await this.getCurrentStateAsync();
state.selection.addOrUpdateSelectedScript(this.scriptId, this.isReverted);
}
private handleSelectionChanged(selectedScripts: ReadonlyArray<SelectedScript>): void {
const selectedScript = selectedScripts.find((script) => script.id === this.scriptId);
if (!selectedScript) {
this.isReverted = false;
} else {
this.isReverted = selectedScript.revert;
}
}
}
</script>
<style scoped lang="scss">
@import "@/presentation/styles/colors.scss";
$width: 85px;
$height: 30px;
// https://www.designlabthemes.com/css-toggle-switch/
.checkbox-switch {
cursor: pointer;
display: inline-block;
overflow: hidden;
position: relative;
width: $width;
height: $height;
-webkit-border-radius: $height;
border-radius: $height;
line-height: $height;
font-size: $height / 2;
display: inline-block;
input.input-checkbox {
position: absolute;
left: 0;
top: 0;
width: $width;
height: $height;
padding: 0;
margin: 0;
opacity: 0;
z-index: 2;
cursor: pointer;
}
.checkbox-animate {
position: relative;
width: $width;
height: $height;
background-color: $gray;
-webkit-transition: background-color 0.25s ease-out 0s;
transition: background-color 0.25s ease-out 0s;
// Circle
&:before {
$circle-size: $height * 0.66;
content: "";
display: block;
position: absolute;
width: $circle-size;
height: $circle-size;
border-radius: $circle-size * 2;
-webkit-border-radius: $circle-size * 2;
background-color: $slate;
top: $height * 0.16;
left: $width * 0.05;
-webkit-transition: left 0.3s ease-out 0s;
transition: left 0.3s ease-out 0s;
z-index: 10;
}
}
input.input-checkbox:checked {
+ .checkbox-animate {
background-color: $accent;
}
+ .checkbox-animate:before {
left: ($width - $width/3.5);
background-color: $light-gray;
}
+ .checkbox-animate .checkbox-off {
display: none;
opacity: 0;
}
+ .checkbox-animate .checkbox-on {
display: block;
opacity: 1;
}
}
.checkbox-off, .checkbox-on {
float: left;
color: $white;
font-weight: 700;
-webkit-transition: all 0.3s ease-out 0s;
transition: all 0.3s ease-out 0s;
}
.checkbox-off {
margin-left: $width / 3;
opacity: 1;
}
.checkbox-on {
display: none;
float: right;
margin-right: $width / 3;
opacity: 0;
}
}
</style>

View File

@@ -8,7 +8,7 @@
ref="treeElement"
>
<span class="tree-text" slot-scope="{ node }">
<Node :data="convertExistingToNode(node)"/>
<Node :data="convertExistingToNode(node)" />
</span>
</tree>
</span>
@@ -144,6 +144,7 @@
text: oldNode.data.text,
data: {
documentationUrls: oldNode.data.documentationUrls,
isReversible: oldNode.data.isReversible,
},
children: oldNode.children == null ? [] :
updateCheckedState(oldNode.children, selectedNodeIds),
@@ -154,9 +155,3 @@
return result;
}
</script>
<style scoped lang="scss">
@import "@/presentation/styles/colors.scss";
</style>

View File

@@ -32,7 +32,8 @@ import { Component, Prop, Vue } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue';
import SelectableOption from './SelectableOption.vue';
import { IApplicationState } from '@/application/State/IApplicationState';
import { IScript } from '@/domain/Script';
import { IScript } from '@/domain/IScript';
import { SelectedScript } from '../../../application/State/Selection/SelectedScript';
@Component({
components: {
@@ -79,12 +80,14 @@ export default class TheSelector extends StatefulVue {
private updateSelections(state: IApplicationState) {
this.isNoneSelected = state.selection.totalSelected === 0;
this.isAllSelected = state.selection.totalSelected === state.app.totalScripts;
this.isRecommendedSelected = this.areSame(state.app.getRecommendedScripts(), state.selection.selectedScripts);
this.isRecommendedSelected = this.areAllRecommended(state.app.getRecommendedScripts(),
state.selection.selectedScripts);
}
private areSame(scripts: ReadonlyArray<IScript>, other: ReadonlyArray<IScript>): boolean {
private areAllRecommended(scripts: ReadonlyArray<IScript>, other: ReadonlyArray<SelectedScript>): boolean {
other = other.filter((selected) => !(selected).revert);
return (scripts.length === other.length) &&
scripts.every((script) => other.some((s) => s.id === script.id));
scripts.every((script) => other.some((selected) => selected.id === script.id));
}
}
</script>

View File

@@ -1,6 +1,6 @@
import { ApplicationState, IApplicationState } from '../application/State/ApplicationState';
import { ApplicationState } from '@/application/State/ApplicationState';
import { IApplicationState } from '@/application/State/IApplicationState';
import { Vue } from 'vue-property-decorator';
export { IApplicationState };
export abstract class StatefulVue extends Vue {
public isLoading = true;

View File

@@ -4,7 +4,7 @@
<script lang="ts">
import { Component, Prop, Watch, Vue } from 'vue-property-decorator';
import { StatefulVue, IApplicationState } from './StatefulVue';
import { StatefulVue } from './StatefulVue';
import ace from 'ace-builds';
import 'ace-builds/webpack-resolver';
import { CodeBuilder } from '../application/State/Code/CodeBuilder';

View File

@@ -15,7 +15,7 @@
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { StatefulVue, IApplicationState } from './StatefulVue';
import { StatefulVue } from './StatefulVue';
import { SaveFileDialog } from './../infrastructure/SaveFileDialog';
import { Clipboard } from './../infrastructure/Clipboard';
import IconButton from './IconButton.vue';