Skip to content

Commit

Permalink
Refactor to add readonly interfaces
Browse files Browse the repository at this point in the history
Using more granular interfaces adds to expressiveness of the code.
Knowing what needs to mutate the state explicitly helps easier
understanding of the code and therefore increases the maintainability.
  • Loading branch information
undergroundwires committed Dec 24, 2021
1 parent a1871a2 commit c3c5b89
Show file tree
Hide file tree
Showing 16 changed files with 58 additions and 42 deletions.
10 changes: 7 additions & 3 deletions src/application/Context/IApplicationContext.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { ICategoryCollectionState } from './State/ICategoryCollectionState';
import { ICategoryCollectionState, IReadOnlyCategoryCollectionState } from './State/ICategoryCollectionState';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { IEventSource } from '@/infrastructure/Events/IEventSource';
import { IApplication } from '@/domain/IApplication';

export interface IApplicationContext {
export interface IReadOnlyApplicationContext {
readonly app: IApplication;
readonly state: ICategoryCollectionState;
readonly state: IReadOnlyCategoryCollectionState;
readonly contextChanged: IEventSource<IApplicationContextChangedEvent>;
}

export interface IApplicationContext extends IReadOnlyApplicationContext {
readonly state: ICategoryCollectionState;
changeContext(os: OperatingSystem): void;
}

Expand Down
2 changes: 1 addition & 1 deletion src/application/Context/State/CategoryCollectionState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { UserSelection } from './Selection/UserSelection';
import { IUserSelection } from './Selection/IUserSelection';
import { ICategoryCollectionState } from './ICategoryCollectionState';
import { IApplicationCode } from './Code/IApplicationCode';
import { ICategoryCollection } from '../../../domain/ICategoryCollection';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { OperatingSystem } from '@/domain/OperatingSystem';

export class CategoryCollectionState implements ICategoryCollectionState {
Expand Down
4 changes: 2 additions & 2 deletions src/application/Context/State/Code/ApplicationCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { CodeChangedEvent } from './Event/CodeChangedEvent';
import { CodePosition } from './Position/CodePosition';
import { ICodeChangedEvent } from './Event/ICodeChangedEvent';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { IUserSelection } from '@/application/Context/State/Selection/IUserSelection';
import { IReadOnlyUserSelection } from '@/application/Context/State/Selection/IUserSelection';
import { UserScriptGenerator } from './Generation/UserScriptGenerator';
import { EventSource } from '@/infrastructure/Events/EventSource';
import { IApplicationCode } from './IApplicationCode';
Expand All @@ -16,7 +16,7 @@ export class ApplicationCode implements IApplicationCode {
private scriptPositions = new Map<SelectedScript, CodePosition>();

constructor(
userSelection: IUserSelection,
userSelection: IReadOnlyUserSelection,
private readonly scriptingDefinition: IScriptingDefinition,
private readonly generator: IUserScriptGenerator = new UserScriptGenerator()) {
if (!userSelection) { throw new Error('userSelection is null or undefined'); }
Expand Down
5 changes: 4 additions & 1 deletion src/application/Context/State/Filter/IUserFilter.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { IEventSource } from '@/infrastructure/Events/IEventSource';
import { IFilterResult } from './IFilterResult';

export interface IUserFilter {
export interface IReadOnlyUserFilter {
readonly currentFilter: IFilterResult | undefined;
readonly filtered: IEventSource<IFilterResult>;
readonly filterRemoved: IEventSource<void>;
}

export interface IUserFilter extends IReadOnlyUserFilter {
setFilter(filter: string): void;
removeFilter(): void;
}
15 changes: 10 additions & 5 deletions src/application/Context/State/ICategoryCollectionState.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { IUserFilter } from './Filter/IUserFilter';
import { IUserSelection } from './Selection/IUserSelection';
import { IReadOnlyUserFilter, IUserFilter } from './Filter/IUserFilter';
import { IReadOnlyUserSelection, IUserSelection } from './Selection/IUserSelection';
import { IApplicationCode } from './Code/IApplicationCode';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { OperatingSystem } from '@/domain/OperatingSystem';

export interface ICategoryCollectionState {
export interface IReadOnlyCategoryCollectionState {
readonly code: IApplicationCode;
readonly os: OperatingSystem;
readonly filter: IReadOnlyUserFilter;
readonly selection: IReadOnlyUserSelection;
readonly collection: ICategoryCollection;
}

export interface ICategoryCollectionState extends IReadOnlyCategoryCollectionState {
readonly filter: IUserFilter;
readonly selection: IUserSelection;
readonly collection: ICategoryCollection;
readonly os: OperatingSystem;
}
7 changes: 5 additions & 2 deletions src/application/Context/State/Selection/IUserSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ import { IScript } from '@/domain/IScript';
import { ICategory } from '@/domain/ICategory';
import { IEventSource } from '@/infrastructure/Events/IEventSource';

export interface IUserSelection {
export interface IReadOnlyUserSelection {
readonly changed: IEventSource<ReadonlyArray<SelectedScript>>;
readonly selectedScripts: ReadonlyArray<SelectedScript>;
isSelected(scriptId: string): boolean;
areAllSelected(category: ICategory): boolean;
isAnySelected(category: ICategory): boolean;
}

export interface IUserSelection extends IReadOnlyUserSelection {
removeAllInCategory(categoryId: number): void;
addOrUpdateAllInCategory(categoryId: number, revert: boolean): void;
addSelectedScript(scriptId: string, revert: boolean): void;
addOrUpdateSelectedScript(scriptId: string, revert: boolean): void;
removeSelectedScript(scriptId: string): void;
selectOnly(scripts: ReadonlyArray<IScript>): void;
isSelected(scriptId: string): boolean;
selectAll(): void;
deselectAll(): void;
}
10 changes: 5 additions & 5 deletions src/presentation/components/Code/CodeButtons/TheCodeButtons.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ import Dialog from '@/presentation/components/Shared/Dialog.vue';
import IconButton from './IconButton.vue';
import MacOsInstructions from './MacOsInstructions.vue';
import { Environment } from '@/application/Environment/Environment';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { CodeRunner } from '@/infrastructure/CodeRunner';
import { IApplicationContext } from '@/application/Context/IApplicationContext';
import { IReadOnlyApplicationContext } from '@/application/Context/IApplicationContext';
@Component({
components: {
Expand Down Expand Up @@ -70,7 +70,7 @@ export default class TheCodeButtons extends StatefulVue {
await executeCode(context);
}
protected handleCollectionState(newState: ICategoryCollectionState): void {
protected handleCollectionState(newState: IReadOnlyCategoryCollectionState): void {
this.canRun = this.isDesktopVersion && newState.collection.os === Environment.CurrentEnvironment.os;
this.isMacOsCollection = newState.collection.os === OperatingSystem.macOS;
this.fileName = buildFileName(newState.collection.scripting);
Expand All @@ -91,7 +91,7 @@ export default class TheCodeButtons extends StatefulVue {
}
}
function saveCode(fileName: string, state: ICategoryCollectionState) {
function saveCode(fileName: string, state: IReadOnlyCategoryCollectionState) {
const content = state.code.current;
const type = getType(state.collection.scripting.language);
SaveFileDialog.saveFile(content, fileName, type);
Expand All @@ -115,7 +115,7 @@ function buildFileName(scripting: IScriptingDefinition) {
return fileName;
}
async function executeCode(context: IApplicationContext) {
async function executeCode(context: IReadOnlyApplicationContext) {
const runner = new CodeRunner();
await runner.runCode(
/*code*/ context.state.code.current,
Expand Down
4 changes: 2 additions & 2 deletions src/presentation/components/Code/TheCodeArea.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import 'ace-builds/webpack-resolver';
import { ICodeChangedEvent } from '@/application/Context/State/Code/Event/ICodeChangedEvent';
import { IScript } from '@/domain/IScript';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { CodeBuilderFactory } from '@/application/Context/State/Code/Generation/CodeBuilderFactory';
import Responsive from '@/presentation/components/Shared/Responsive.vue';
import { NonCollapsing } from '@/presentation/components/Scripts/View/Cards/NonCollapsingDirective';
Expand Down Expand Up @@ -45,7 +45,7 @@ export default class TheCodeArea extends StatefulVue {
}
}
protected handleCollectionState(newState: ICategoryCollectionState): void {
protected handleCollectionState(newState: IReadOnlyCategoryCollectionState): void {
this.destroyEditor();
this.editor = initializeEditor(this.theme, this.editorId, newState.collection.scripting.language);
const appCode = newState.code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { IScript } from '@/domain/IScript';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { RecommendationLevel } from '@/domain/RecommendationLevel';
import { scrambledEqual } from '@/application/Common/Array';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { ICategoryCollectionState, IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';

export enum SelectionType {
Standard,
Expand Down Expand Up @@ -34,7 +34,7 @@ export class SelectionTypeHandler {
}

interface ISingleTypeHandler {
isSelected: (state: ICategoryCollectionState) => boolean;
isSelected: (state: IReadOnlyCategoryCollectionState) => boolean;
select: (state: ICategoryCollectionState) => void;
}

Expand Down Expand Up @@ -62,7 +62,7 @@ function getRecommendationLevelSelector(level: RecommendationLevel): ISingleType
};
}

function hasAllSelectedLevelOf(level: RecommendationLevel, state: ICategoryCollectionState) {
function hasAllSelectedLevelOf(level: RecommendationLevel, state: IReadOnlyCategoryCollectionState) {
const scripts = state.collection.getScriptsByLevel(level);
const selectedScripts = state.selection.selectedScripts;
return areAllSelected(scripts, selectedScripts);
Expand Down
4 changes: 2 additions & 2 deletions src/presentation/components/Scripts/Menu/TheOsChanger.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import { Component } from 'vue-property-decorator';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { StatefulVue } from '@/presentation/components/Shared/StatefulVue';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { ApplicationFactory } from '@/application/ApplicationFactory';
import MenuOptionList from './MenuOptionList.vue';
import MenuOptionListItem from './MenuOptionListItem.vue';
Expand All @@ -38,7 +38,7 @@ export default class TheOsChanger extends StatefulVue {
context.changeContext(newOs);
}
protected handleCollectionState(newState: ICategoryCollectionState): void {
protected handleCollectionState(newState: IReadOnlyCategoryCollectionState): void {
this.currentOs = newState.os;
this.$forceUpdate(); // v-bind:class is not updated otherwise
}
Expand Down
6 changes: 3 additions & 3 deletions src/presentation/components/Scripts/Menu/TheScriptsMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import TheOsChanger from './TheOsChanger.vue';
import TheSelector from './Selector/TheSelector.vue';
import TheViewChanger from './View/TheViewChanger.vue';
import { StatefulVue } from '@/presentation/components/Shared/StatefulVue';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IEventSubscription } from '@/infrastructure/Events/IEventSource';
@Component({
Expand All @@ -37,11 +37,11 @@ export default class TheScriptsMenu extends StatefulVue {
protected initialize(): void {
return;
}
protected handleCollectionState(newState: ICategoryCollectionState): void {
protected handleCollectionState(newState: IReadOnlyCategoryCollectionState): void {
this.subscribe(newState);
}
private subscribe(state: ICategoryCollectionState) {
private subscribe(state: IReadOnlyCategoryCollectionState) {
this.listeners.push(state.filter.filterRemoved.on(() => {
this.isSearching = false;
}));
Expand Down
4 changes: 2 additions & 2 deletions src/presentation/components/Scripts/View/Cards/CardList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { Component } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/components/Shared/StatefulVue';
import { ICategory } from '@/domain/ICategory';
import { hasDirective } from './NonCollapsingDirective';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
@Component({
components: {
Expand All @@ -56,7 +56,7 @@ export default class CardList extends StatefulVue {
this.activeCategoryId = isExpanded ? categoryId : undefined;
}
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
protected handleCollectionState(newState: IReadOnlyCategoryCollectionState): void {
this.setCategories(newState.collection.actions);
this.activeCategoryId = undefined;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { StatefulVue } from '@/presentation/components/Shared/StatefulVue';
import { INode } from './INode';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { getReverter } from './Reverter/ReverterFactory';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
@Component
export default class RevertToggle extends StatefulVue {
Expand All @@ -37,7 +37,7 @@ export default class RevertToggle extends StatefulVue {
this.handler.selectWithRevertState(this.isReverted, context.state.selection);
}
protected handleCollectionState(newState: ICategoryCollectionState): void {
protected handleCollectionState(newState: IReadOnlyCategoryCollectionState): void {
this.updateStatus(newState.selection.selectedScripts);
this.events.unsubscribeAll();
this.events.register(newState.selection.changed.on((scripts) => this.updateStatus(scripts)));
Expand Down
6 changes: 3 additions & 3 deletions src/presentation/components/Scripts/View/TheScriptsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { Component, Prop } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/components/Shared/StatefulVue';
import { ViewType } from '@/presentation/components/Scripts/Menu/View/ViewType';
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { ApplicationFactory } from '@/application/ApplicationFactory';
/** Shows content of single category or many categories */
Expand Down Expand Up @@ -74,12 +74,12 @@ export default class TheScriptsView extends StatefulVue {
filter.removeFilter();
}
protected handleCollectionState(newState: ICategoryCollectionState): void {
protected handleCollectionState(newState: IReadOnlyCategoryCollectionState): void {
this.events.unsubscribeAll();
this.subscribeState(newState);
}
private subscribeState(state: ICategoryCollectionState) {
private subscribeState(state: IReadOnlyCategoryCollectionState) {
this.events.register(
state.filter.filterRemoved.on(() => {
this.isSearching = false;
Expand Down
5 changes: 3 additions & 2 deletions src/presentation/components/Shared/StatefulVue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
import { IApplicationContext } from '@/application/Context/IApplicationContext';
import { buildContext } from '@/application/Context/ApplicationContextFactory';
import { IApplicationContextChangedEvent } from '@/application/Context/IApplicationContext';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { EventSubscriptionCollection } from '@/infrastructure/Events/EventSubscriptionCollection';

// @ts-ignore because https://github.com/vuejs/vue-class-component/issues/91
Expand All @@ -26,7 +26,8 @@ export abstract class StatefulVue extends Vue {
}

protected abstract handleCollectionState(
newState: ICategoryCollectionState, oldState: ICategoryCollectionState | undefined): void;
newState: IReadOnlyCategoryCollectionState,
oldState: IReadOnlyCategoryCollectionState | undefined): void;
protected getCurrentContext(): Promise<IApplicationContext> {
return StatefulVue.instance.getValue();
}
Expand Down
8 changes: 4 additions & 4 deletions src/presentation/components/TheSearchBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
import { Component, Watch } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/components/Shared/StatefulVue';
import { NonCollapsing } from '@/presentation/components/Scripts/View/Cards/NonCollapsingDirective';
import { IUserFilter } from '@/application/Context/State/Filter/IUserFilter';
import { IReadOnlyUserFilter } from '@/application/Context/State/Filter/IUserFilter';
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
@Component( {
directives: { NonCollapsing },
Expand All @@ -36,15 +36,15 @@ export default class TheSearchBar extends StatefulVue {
}
}
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState | undefined) {
protected handleCollectionState(newState: IReadOnlyCategoryCollectionState) {
const totalScripts = newState.collection.totalScripts;
this.searchPlaceHolder = `Search in ${totalScripts} scripts`;
this.searchQuery = newState.filter.currentFilter ? newState.filter.currentFilter.query : '';
this.events.unsubscribeAll();
this.subscribeFilter(newState.filter);
}
private subscribeFilter(filter: IUserFilter) {
private subscribeFilter(filter: IReadOnlyUserFilter) {
this.events.register(filter.filtered.on((result) => this.handleFiltered(result)));
this.events.register(filter.filterRemoved.on(() => this.handleFilterRemoved()));
}
Expand Down

0 comments on commit c3c5b89

Please sign in to comment.