diff --git a/_old/domain/entities/Constraint.mts b/_old/domain/entities/Constraint.mts deleted file mode 100644 index a8392eef..00000000 --- a/_old/domain/entities/Constraint.mts +++ /dev/null @@ -1,3 +0,0 @@ -import Requirement from './Requirement.mjs'; - -export default class Constraint extends Requirement { } \ No newline at end of file diff --git a/src/data/ConstraintRepository.mts b/src/data/ConstraintRepository.mts new file mode 100644 index 00000000..7407bb92 --- /dev/null +++ b/src/data/ConstraintRepository.mts @@ -0,0 +1,11 @@ +import Constraint from '~/domain/Constraint.mjs'; +import StorageRepository from './StorageRepository.mjs'; +import ConstraintToJsonMapper from '~/mappers/ConstraintToJsonMapper.mjs'; +import pkg from '~/../package.json' with { type: 'json' }; +import type { SemVerString } from '~/lib/SemVer.mjs'; + +export default class ConstraintRepository extends StorageRepository { + constructor(storage: Storage) { + super('constraints', storage, new ConstraintToJsonMapper(pkg.version as SemVerString)); + } +} \ No newline at end of file diff --git a/src/domain/Constraint.mts b/src/domain/Constraint.mts new file mode 100644 index 00000000..f27259d9 --- /dev/null +++ b/src/domain/Constraint.mts @@ -0,0 +1,18 @@ +import type { Properties } from '~/types/Properties.mjs'; +import Requirement from './Requirement.mjs'; + +export enum ConstraintCategory { + BusinessRule = 'Business Rule', + PhysicalLaw = 'Physical Law', + EngineeringDecision = 'Engineering Decision' +} + +export default class Constraint extends Requirement { + constructor(options: Properties) { + super(options); + + this.category = options.category; + } + + category: ConstraintCategory; +} \ No newline at end of file diff --git a/src/domain/Environment.mts b/src/domain/Environment.mts index 3bda6db4..8be76fec 100644 --- a/src/domain/Environment.mts +++ b/src/domain/Environment.mts @@ -11,9 +11,11 @@ import type { Properties } from '~/types/Properties.mjs'; */ export default class Environment extends Entity { glossaryIds: Uuid[]; + constraintIds: Uuid[]; constructor(options: Properties) { super(options); this.glossaryIds = options.glossaryIds; + this.constraintIds = options.constraintIds; } } \ No newline at end of file diff --git a/src/mappers/ConstraintToJsonMapper.mts b/src/mappers/ConstraintToJsonMapper.mts new file mode 100644 index 00000000..7b02e4e5 --- /dev/null +++ b/src/mappers/ConstraintToJsonMapper.mts @@ -0,0 +1,25 @@ +import Constraint, { ConstraintCategory } from '~/domain/Constraint.mjs'; +import RequirementToJsonMapper, { type RequirementJson } from './RequirementToJsonMapper.mjs'; +import SemVer from '~/lib/SemVer.mjs'; + +export interface ConstraintJson extends RequirementJson { + category: ConstraintCategory; +} + +export default class ConstraintToJsonMapper extends RequirementToJsonMapper { + override mapFrom(target: ConstraintJson): Constraint { + const version = new SemVer(target.serializationVersion); + + if (version.gte('0.4.0')) + return new Constraint(target); + + throw new Error(`Unsupported serialization version: ${version}`); + } + + override mapTo(source: Constraint): ConstraintJson { + return { + ...super.mapTo(source), + category: source.category + }; + } +} \ No newline at end of file diff --git a/src/mappers/EnvironmentToJsonMapper.mts b/src/mappers/EnvironmentToJsonMapper.mts index 88577e68..c5b425b6 100644 --- a/src/mappers/EnvironmentToJsonMapper.mts +++ b/src/mappers/EnvironmentToJsonMapper.mts @@ -5,6 +5,7 @@ import SemVer from '~/lib/SemVer.mjs'; export interface EnvironmentJson extends EntityJson { glossaryIds: Uuid[]; + constraintIds: Uuid[]; } export default class EnvironmentToJsonMapper extends EntityToJsonMapper { @@ -12,7 +13,10 @@ export default class EnvironmentToJsonMapper extends EntityToJsonMapper { const version = new SemVer(target.serializationVersion); if (version.gte('0.3.0')) - return new Environment(target); + return new Environment({ + ...target, + constraintIds: target.constraintIds ?? [] + }); throw new Error(`Unsupported serialization version: ${version}`); } @@ -20,7 +24,8 @@ export default class EnvironmentToJsonMapper extends EntityToJsonMapper { override mapTo(source: Environment): EnvironmentJson { return { ...super.mapTo(source), - glossaryIds: source.glossaryIds + glossaryIds: source.glossaryIds, + constraintIds: source.constraintIds }; } } \ No newline at end of file diff --git a/src/presentation/Application.mts b/src/presentation/Application.mts index d1527485..15082eb9 100644 --- a/src/presentation/Application.mts +++ b/src/presentation/Application.mts @@ -67,6 +67,7 @@ export default class Application extends Container { (await import('./pages/solution/project/ProjectsIndexPage.mjs')).default, (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/goals/GoalsIndexPage.mjs')).default, (await import('./pages/solution/goals/RationalePage.mjs')).default, (await import('./pages/solution/goals/FunctionalityPage.mjs')).default, diff --git a/src/presentation/pages/solution/NewSolutionPage.mts b/src/presentation/pages/solution/NewSolutionPage.mts index a695f338..71ba1a06 100644 --- a/src/presentation/pages/solution/NewSolutionPage.mts +++ b/src/presentation/pages/solution/NewSolutionPage.mts @@ -108,7 +108,8 @@ export default class NewSolutionPage extends Page { }), environment = new Environment({ id: solution.environmentId, - glossaryIds: [] + glossaryIds: [], + constraintIds: [] }), goals = new Goals({ id: solution.goalsId, diff --git a/src/presentation/pages/solution/SolutionIndexPage.mts b/src/presentation/pages/solution/SolutionIndexPage.mts index a7ca9b9b..3d50ff6a 100644 --- a/src/presentation/pages/solution/SolutionIndexPage.mts +++ b/src/presentation/pages/solution/SolutionIndexPage.mts @@ -7,6 +7,7 @@ export default class SolutionIndexPage extends Page { static { customElements.define('x-page-solution-index', this); } + constructor() { super({ title: 'Solutions' }, [ new PegsCards({ diff --git a/src/presentation/pages/solution/environment/ConstraintsPage.mts b/src/presentation/pages/solution/environment/ConstraintsPage.mts new file mode 100644 index 00000000..8e106de9 --- /dev/null +++ b/src/presentation/pages/solution/environment/ConstraintsPage.mts @@ -0,0 +1,82 @@ +import type { Uuid } from '~/types/Uuid.mjs'; +import type Environment from '~/domain/Environment.mjs'; +import Constraint, { ConstraintCategory } from '~/domain/Constraint.mjs'; +import SolutionRepository from '~/data/SolutionRepository.mjs'; +import EnvironmentRepository from '~/data/EnvironmentRepository.mjs'; +import ConstraintRepository from '~/data/ConstraintRepository.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 ConstraintsPage extends Page { + static override route = '/:solution/environment/constraints'; + static { + customElements.define('x-page-constraints', this); + } + + #solutionRepository = new SolutionRepository(localStorage); + #environmentRepository = new EnvironmentRepository(localStorage); + #constraintRepository = new ConstraintRepository(localStorage); + #environment?: Environment; + + constructor() { + super({ title: 'Constraints' }, []); + + const dataTable = new DataTable({ + columns: { + id: { headerText: 'ID', readonly: true, formType: 'hidden', unique: true }, + statement: { headerText: 'Statement', required: true, formType: 'text', unique: true }, + category: { + headerText: 'Category', formType: 'select', + options: Object.values(ConstraintCategory).map(x => ({ value: x, text: x })) + } + }, + select: async () => { + if (!this.#environment) + return []; + + return await this.#constraintRepository.getAll(t => this.#environment!.constraintIds.includes(t.id)); + }, + onCreate: async item => { + const constraint = new Constraint({ ...item, id: self.crypto.randomUUID() }); + this.#environment!.constraintIds.push(constraint.id); + await Promise.all([ + this.#constraintRepository.add(constraint), + this.#environmentRepository.update(this.#environment!) + ]); + }, + onUpdate: async item => { + await this.#constraintRepository.update(new Constraint({ + ...item + })); + }, + onDelete: async id => { + this.#environment!.constraintIds = this.#environment!.constraintIds.filter(x => x !== id); + await Promise.all([ + this.#constraintRepository.delete(id), + this.#environmentRepository.update(this.#environment!) + ]); + } + }); + + this.append( + p(` + Environmental constraints are the limitations and obligations that + the environment imposes on the project and system. + `), + dataTable + ); + + this.#environmentRepository.addEventListener('update', () => dataTable.renderData()); + this.#constraintRepository.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(); + }); + }); + } +} \ No newline at end of file diff --git a/src/presentation/pages/solution/environment/EnvironmentsIndexPage.mts b/src/presentation/pages/solution/environment/EnvironmentsIndexPage.mts index 831a6b42..c7ac6204 100644 --- a/src/presentation/pages/solution/environment/EnvironmentsIndexPage.mts +++ b/src/presentation/pages/solution/environment/EnvironmentsIndexPage.mts @@ -26,6 +26,11 @@ export default class EnvironmentsIndexPage extends Page { icon: 'list', href: `${location.pathname}/glossary` }), + new MiniCard({ + title: 'Constraints', + icon: 'anchor', + href: `${location.pathname}/constraints` + }) ]) ); }