Skip to content

Commit

Permalink
Implemented invariants page (#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
mlhaufe authored Jan 12, 2024
1 parent bfc9b11 commit d2b7a20
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 6 deletions.
3 changes: 0 additions & 3 deletions _old/domain/entities/Invariant.mts

This file was deleted.

11 changes: 11 additions & 0 deletions src/data/InvariantRepository.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type Invariant from '~/domain/Invariant.mjs';
import StorageRepository from './StorageRepository.mjs';
import type { SemVerString } from '~/lib/SemVer.mjs';
import InvariantToJsonMapper from '~/mappers/InvariantToJsonMapper.mjs';
import pkg from '~/../package.json' with { type: 'json' };

export default class InvariantRepository extends StorageRepository<Invariant> {
constructor(storage: Storage) {
super('invariants', storage, new InvariantToJsonMapper(pkg.version as SemVerString));
}
}
2 changes: 2 additions & 0 deletions src/domain/Environment.mts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ import type { Properties } from '~/types/Properties.mjs';
export default class Environment extends Entity {
glossaryIds: Uuid[];
constraintIds: Uuid[];
invariantIds: Uuid[];

constructor(options: Properties<Environment>) {
super(options);
this.glossaryIds = options.glossaryIds;
this.constraintIds = options.constraintIds;
this.invariantIds = options.invariantIds;
}
}
6 changes: 6 additions & 0 deletions src/domain/Invariant.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Requirement from './Requirement.mjs';

/**
* An invariant is an environment property that must be maintained
*/
export default class Invariant extends Requirement { }
7 changes: 5 additions & 2 deletions src/mappers/EnvironmentToJsonMapper.mts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import SemVer from '~/lib/SemVer.mjs';
export interface EnvironmentJson extends EntityJson {
glossaryIds: Uuid[];
constraintIds: Uuid[];
invariantIds: Uuid[];
}

export default class EnvironmentToJsonMapper extends EntityToJsonMapper {
Expand All @@ -15,7 +16,8 @@ export default class EnvironmentToJsonMapper extends EntityToJsonMapper {
if (version.gte('0.3.0'))
return new Environment({
...target,
constraintIds: target.constraintIds ?? []
constraintIds: target.constraintIds ?? [],
invariantIds: target.invariantIds ?? []
});

throw new Error(`Unsupported serialization version: ${version}`);
Expand All @@ -25,7 +27,8 @@ export default class EnvironmentToJsonMapper extends EntityToJsonMapper {
return {
...super.mapTo(source),
glossaryIds: source.glossaryIds,
constraintIds: source.constraintIds
constraintIds: source.constraintIds,
invariantIds: source.invariantIds
};
}
}
20 changes: 20 additions & 0 deletions src/mappers/InvariantToJsonMapper.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Invariant from '~/domain/Invariant.mjs';
import RequirementToJsonMapper, { type RequirementJson } from './RequirementToJsonMapper.mjs';
import SemVer from '~/lib/SemVer.mjs';

export interface InvariantJson extends RequirementJson { }

export default class InvariantToJsonMapper extends RequirementToJsonMapper {
override mapFrom(target: InvariantJson): Invariant {
const version = new SemVer(target.serializationVersion);

if (version.gte('0.4.0'))
return new Invariant(target);

throw new Error(`Unsupported serialization version: ${version}`);
}

override mapTo(source: Invariant): InvariantJson {
return super.mapTo(source);
}
}
1 change: 1 addition & 0 deletions src/presentation/Application.mts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export default class Application extends Container {
(await import('./pages/solution/environment/EnvironmentsIndexPage.mjs')).default,
(await import('./pages/solution/environment/GlossaryPage.mjs')).default,
(await import('./pages/solution/environment/ConstraintsPage.mjs')).default,
(await import('./pages/solution/environment/InvariantsPage.mjs')).default,
(await import('./pages/solution/goals/GoalsIndexPage.mjs')).default,
(await import('./pages/solution/goals/RationalePage.mjs')).default,
(await import('./pages/solution/goals/FunctionalityPage.mjs')).default,
Expand Down
3 changes: 2 additions & 1 deletion src/presentation/pages/solution/NewSolutionPage.mts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ export default class NewSolutionPage extends Page {
environment = new Environment({
id: solution.environmentId,
glossaryIds: [],
constraintIds: []
constraintIds: [],
invariantIds: [],
}),
goals = new Goals({
id: solution.goalsId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ export default class EnvironmentsIndexPage extends Page {
title: 'Constraints',
icon: 'anchor',
href: `${location.pathname}/constraints`
}),
new MiniCard({
title: 'Invariants',
icon: 'lock',
href: `${location.pathname}/invariants`
})
])
);
Expand Down
78 changes: 78 additions & 0 deletions src/presentation/pages/solution/environment/InvariantsPage.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type { Uuid } from '~/types/Uuid.mjs';
import type Environment from '~/domain/Environment.mjs';
import Invariant from '~/domain/Invariant.mjs';
import SolutionRepository from '~/data/SolutionRepository.mjs';
import EnvironmentRepository from '~/data/EnvironmentRepository.mjs';
import InvariantRepository from '~/data/InvariantRepository.mjs';
import Page from '~/presentation/pages/Page.mjs';
import { DataTable } from '~/presentation/components/DataTable.mjs';
import html from '~/presentation/lib/html.mjs';

const { p } = html;

export default class InvariantsPage extends Page {
static override route = '/:solution/environment/invariants';
static {
customElements.define('x-page-invariants', this);
}

#solutionRepository = new SolutionRepository(localStorage);
#environmentRepository = new EnvironmentRepository(localStorage);
#invariantRepository = new InvariantRepository(localStorage);
#environment?: Environment;

constructor() {
super({ title: 'Constraints' }, []);

const dataTable = new DataTable<Invariant>({
columns: {
id: { headerText: 'ID', readonly: true, formType: 'hidden', unique: true },
statement: { headerText: 'Statement', required: true, formType: 'text', unique: true }
},
select: async () => {
if (!this.#environment)
return [];

return await this.#invariantRepository.getAll(t => this.#environment!.invariantIds.includes(t.id));
},
onCreate: async item => {
const invariant = new Invariant({ ...item, id: self.crypto.randomUUID() });
this.#environment!.invariantIds.push(invariant.id);
await Promise.all([
this.#invariantRepository.add(invariant),
this.#environmentRepository.update(this.#environment!)
]);
},
onUpdate: async item => {
await this.#invariantRepository.update(new Invariant({
...item
}));
},
onDelete: async id => {
this.#environment!.invariantIds = this.#environment!.invariantIds.filter(x => x !== id);
await Promise.all([
this.#invariantRepository.delete(id),
this.#environmentRepository.update(this.#environment!)
]);
}
});

this.append(
p(`
Invariants are properties that must always be true. They are used to
constrain the possible states of a system.
`),
dataTable
);

this.#environmentRepository.addEventListener('update', () => dataTable.renderData());
this.#invariantRepository.addEventListener('update', () => dataTable.renderData());
const solutionId = this.urlParams['solution'] as Uuid;
this.#solutionRepository.getBySlug(solutionId).then(solution => {
this.#environmentRepository.get(solution!.environmentId).then(environment => {
this.#environment = environment;
dataTable.renderData();
});
});
}
}

0 comments on commit d2b7a20

Please sign in to comment.