diff --git a/_old/domain/entities/Assumption.mts b/_old/domain/entities/Assumption.mts deleted file mode 100644 index 7a9bf727..00000000 --- a/_old/domain/entities/Assumption.mts +++ /dev/null @@ -1,3 +0,0 @@ -import Constraint from './Constraint.mjs'; - -export default class Assumption extends Constraint { } \ No newline at end of file diff --git a/_old/domain/entities/Behavior.mts b/_old/domain/entities/Behavior.mts deleted file mode 100644 index 2147a244..00000000 --- a/_old/domain/entities/Behavior.mts +++ /dev/null @@ -1,3 +0,0 @@ -import Requirement from './Requirement.mjs'; - -export default class Behavior extends Requirement { } \ No newline at end of file diff --git a/_old/domain/entities/Component.mts b/_old/domain/entities/Component.mts deleted file mode 100644 index 78f72d9c..00000000 --- a/_old/domain/entities/Component.mts +++ /dev/null @@ -1,3 +0,0 @@ -import Requirement from './Requirement.mjs'; - -export default class Component extends Requirement { } \ No newline at end of file diff --git a/_old/domain/entities/Environment.mts b/_old/domain/entities/Environment.mts deleted file mode 100644 index e26c47f1..00000000 --- a/_old/domain/entities/Environment.mts +++ /dev/null @@ -1,14 +0,0 @@ -import Constraint from './Constraint.mjs'; - -/** - * The set of entities (people, organizations, regulations, devices and other material objects, other systems) - * external to the project or system but with the potential to affect it or be affected by it. - */ -export default class Environment { - private _constraints: Constraint[] = []; - - /** - * The requirements of the environment - */ - get constraints(): Constraint[] { return this._constraints; } -} \ No newline at end of file diff --git a/_old/domain/entities/Project.mts b/_old/domain/entities/Project.mts deleted file mode 100644 index f330fc15..00000000 --- a/_old/domain/entities/Project.mts +++ /dev/null @@ -1,30 +0,0 @@ -import Artifact from './Artifact.mjs'; -import Requirement from './Requirement.mjs'; - -/* -const projectContract = new Contract({ - [invariant]: self => - self.requirements.every(req => req.project === self) && - self.artifacts.every(art => art.upTraceable) && // upTraceability - // downTraceability: For every requirement at least one artifact follows from it. - self.requirements.every(req => self.artifacts.some(art => art.followsFrom === req)) -}); -*/ - -/** - * The set of human processes involved in the planning, - * construction, revision, and operation of a system. - * Associated with a single system. - */ -export default class Project { - accessor artifacts!: Artifact[]; - accessor downTraceable!: boolean; - // accessor id!: Uuid; - accessor requirements!: Requirement[]; - accessor title!: string; - accessor upTraceable!: boolean; - - constructor(params: Project) { - Object.assign(this, params); - } -} \ No newline at end of file diff --git a/_old/domain/entities/Requirement.mts b/_old/domain/entities/Requirement.mts index e7264848..3b6e7cf0 100644 --- a/_old/domain/entities/Requirement.mts +++ b/_old/domain/entities/Requirement.mts @@ -1,6 +1,5 @@ // import { Contract, Contracted, implies, invariant } from '@final-hill/decorator-contracts'; import { type Predicate } from './Predicate.mjs'; -import Project from './Project.mjs'; // const requirementContract = new Contract({ // [invariant]: self => implies( diff --git a/_old/domain/entities/UseCase.mts b/_old/domain/entities/UseCase.mts deleted file mode 100644 index 6718b1cb..00000000 --- a/_old/domain/entities/UseCase.mts +++ /dev/null @@ -1,3 +0,0 @@ -import Scenario from './Scenario.mjs'; - -export default class UseCase extends Scenario { } \ No newline at end of file diff --git a/src/data/ComponentRepository.mts b/src/data/ComponentRepository.mts new file mode 100644 index 00000000..aff92910 --- /dev/null +++ b/src/data/ComponentRepository.mts @@ -0,0 +1,11 @@ +import StorageRepository from './StorageRepository.mjs'; +import pkg from '~/../package.json' with { type: 'json' }; +import type { SemVerString } from '~/lib/SemVer.mjs'; +import { ComponentToJsonMapper } from '~/mappers/ComponentToJsonMapper.mjs'; +import type Component from '~/domain/Component.mjs'; + +export default class BehaviorRepository extends StorageRepository { + constructor(storage: Storage) { + super('components', storage, new ComponentToJsonMapper(pkg.version as SemVerString)); + } +} \ No newline at end of file diff --git a/src/domain/Component.mts b/src/domain/Component.mts new file mode 100644 index 00000000..931777c7 --- /dev/null +++ b/src/domain/Component.mts @@ -0,0 +1,26 @@ +import type { Properties } from '~/types/Properties.mjs'; +import Requirement from './Requirement.mjs'; + +/** + * A Component is a self-contained element in the Environment that provides an interface + * which can be used by a System to interact with. + */ +export default class Component extends Requirement { + name: string; + description: string; + + constructor(properties: Omit, 'interfaceDefinition'>) { + super(properties); + + this.name = properties.name; + this.description = properties.description; + } + + get interfaceDefinition(): string { + return this.statement; + } + + set interfaceDefinition(value: string) { + this.statement = value; + } +} \ No newline at end of file diff --git a/src/domain/Environment.mts b/src/domain/Environment.mts index cffa7f8b..1540af2d 100644 --- a/src/domain/Environment.mts +++ b/src/domain/Environment.mts @@ -15,6 +15,7 @@ export default class Environment extends Entity { invariantIds: Uuid[]; assumptionIds: Uuid[]; effectIds: Uuid[]; + componentIds: Uuid[]; constructor(options: Properties) { super(options); @@ -23,5 +24,6 @@ export default class Environment extends Entity { this.invariantIds = options.invariantIds; this.assumptionIds = options.assumptionIds; this.effectIds = options.effectIds; + this.componentIds = options.componentIds; } } \ No newline at end of file diff --git a/src/mappers/ComponentToJsonMapper.mts b/src/mappers/ComponentToJsonMapper.mts new file mode 100644 index 00000000..0ad99f12 --- /dev/null +++ b/src/mappers/ComponentToJsonMapper.mts @@ -0,0 +1,28 @@ +import SemVer from '~/lib/SemVer.mjs'; +import type { RequirementJson } from './RequirementToJsonMapper.mjs'; +import RequirementToJsonMapper from './RequirementToJsonMapper.mjs'; +import Component from '~/domain/Component.mjs'; + +export interface ComponentJson extends RequirementJson { + name: string; + description: string; +} + +export class ComponentToJsonMapper extends RequirementToJsonMapper { + override mapFrom(target: ComponentJson): Component { + const version = new SemVer(target.serializationVersion); + + if (version.gte('0.4.0')) + return new Component(target); + + throw new Error(`Unsupported serialization version: ${version}`); + } + + override mapTo(source: Component): ComponentJson { + return { + ...super.mapTo(source), + name: source.name, + description: source.description, + }; + } +} \ No newline at end of file diff --git a/src/mappers/EnvironmentToJsonMapper.mts b/src/mappers/EnvironmentToJsonMapper.mts index 0216984b..5b00b153 100644 --- a/src/mappers/EnvironmentToJsonMapper.mts +++ b/src/mappers/EnvironmentToJsonMapper.mts @@ -9,6 +9,7 @@ export interface EnvironmentJson extends EntityJson { invariantIds: Uuid[]; assumptionIds: Uuid[]; effectIds: Uuid[]; + componentIds: Uuid[]; } export default class EnvironmentToJsonMapper extends EntityToJsonMapper { @@ -21,7 +22,8 @@ export default class EnvironmentToJsonMapper extends EntityToJsonMapper { constraintIds: target.constraintIds ?? [], invariantIds: target.invariantIds ?? [], assumptionIds: target.assumptionIds ?? [], - effectIds: target.effectIds ?? [] + effectIds: target.effectIds ?? [], + componentIds: target.componentIds ?? [] }); throw new Error(`Unsupported serialization version: ${version}`); @@ -34,7 +36,8 @@ export default class EnvironmentToJsonMapper extends EntityToJsonMapper { constraintIds: source.constraintIds, invariantIds: source.invariantIds, assumptionIds: source.assumptionIds, - effectIds: source.effectIds + effectIds: source.effectIds, + componentIds: source.componentIds }; } } \ No newline at end of file diff --git a/src/presentation/Application.mts b/src/presentation/Application.mts index 0deb6c31..b4fac65d 100644 --- a/src/presentation/Application.mts +++ b/src/presentation/Application.mts @@ -71,6 +71,7 @@ export default class Application extends Container { (await import('./pages/solution/environment/InvariantsPage.mjs')).default, (await import('./pages/solution/environment/AssumptionsPage.mjs')).default, (await import('./pages/solution/environment/EffectsPage.mjs')).default, + (await import('./pages/solution/environment/ComponentsPage.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/components/DataTable.mts b/src/presentation/components/DataTable.mts index 993021d9..394ab563 100644 --- a/src/presentation/components/DataTable.mts +++ b/src/presentation/components/DataTable.mts @@ -13,7 +13,7 @@ interface BaseDataColumn { } interface TextHiddenDataColumn { - formType: 'text' | 'hidden'; + formType: 'text' | 'hidden' | 'textarea'; } interface NumberRangeDataColumn { @@ -47,7 +47,7 @@ const show = ((item: HTMLElement) => item.hidden = false), hide = ((item: HTMLElement) => item.hidden = true), disable = ((item: HTMLInputElement | HTMLSelectElement) => item.disabled = true), enable = ((item: HTMLInputElement | HTMLSelectElement) => item.disabled = false), - { button, caption, form, input, option, select, span, table, tbody, td, template, th, thead, tr } = html; + { button, caption, form, input, option, select, span, table, textarea, tbody, td, template, th, thead, tr } = html; export class DataTable extends Component { static { @@ -332,6 +332,12 @@ export class DataTable extends Component { `${col.step}` : '1', form: this.#frmDataTableCreate, [renderIf]: col.formType == 'number' || col.formType == 'range' + }), + textarea({ + name: id, + form: this.#frmDataTableCreate, + required: col.required, + [renderIf]: col.formType == 'textarea' }) ])) ); @@ -362,6 +368,14 @@ export class DataTable extends Component { name: id, defaultValue: (item as any)[id] }, []), + textarea({ + [renderIf]: col.formType == 'textarea', + form: this.#frmDataTableUpdate, + name: id, + disabled: true, + required: col.required, + defaultValue: (item as any)[id] + }, []), input({ form: this.#frmDataTableUpdate, type: col.formType, diff --git a/src/presentation/pages/solution/NewSolutionPage.mts b/src/presentation/pages/solution/NewSolutionPage.mts index 2dfc84f2..05d81550 100644 --- a/src/presentation/pages/solution/NewSolutionPage.mts +++ b/src/presentation/pages/solution/NewSolutionPage.mts @@ -112,7 +112,8 @@ export default class NewSolutionPage extends Page { constraintIds: [], invariantIds: [], assumptionIds: [], - effectIds: [] + effectIds: [], + componentIds: [] }), goals = new Goals({ id: solution.goalsId, diff --git a/src/presentation/pages/solution/environment/ComponentsPage.mts b/src/presentation/pages/solution/environment/ComponentsPage.mts new file mode 100644 index 00000000..526f0067 --- /dev/null +++ b/src/presentation/pages/solution/environment/ComponentsPage.mts @@ -0,0 +1,85 @@ +import type { Uuid } from '~/types/Uuid.mjs'; +import type Environment from '~/domain/Environment.mjs'; +import Component from '~/domain/Component.mjs'; +import SolutionRepository from '~/data/SolutionRepository.mjs'; +import EnvironmentRepository from '~/data/EnvironmentRepository.mjs'; +import ComponentRepository from '~/data/ComponentRepository.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 ComponentsPage extends Page { + static override route = '/:solution/environment/components'; + static { + customElements.define('x-page-components', this); + } + + #solutionRepository = new SolutionRepository(localStorage); + #environmentRepository = new EnvironmentRepository(localStorage); + #componentRepository = new ComponentRepository(localStorage); + #environment?: Environment; + + constructor() { + super({ title: 'Components' }, []); + + const dataTable = new DataTable({ + columns: { + id: { headerText: 'ID', readonly: true, formType: 'hidden', unique: true }, + name: { headerText: 'Name', required: true, formType: 'text', unique: true }, + description: { headerText: 'Description', formType: 'text' }, + interfaceDefinition: { headerText: 'Interface Definition', formType: 'textarea' } + }, + select: async () => { + if (!this.#environment) + return []; + + return await this.#componentRepository.getAll(t => this.#environment!.componentIds.includes(t.id)); + }, + onCreate: async item => { + const component = new Component({ + id: self.crypto.randomUUID(), + name: item.name, + description: item.description, + statement: item.interfaceDefinition + }); + this.#environment!.componentIds.push(component.id); + await Promise.all([ + this.#componentRepository.add(component), + this.#environmentRepository.update(this.#environment!) + ]); + }, + onUpdate: async item => { + await this.#componentRepository.update(new Component({ + ...item + })); + }, + onDelete: async id => { + this.#environment!.componentIds = this.#environment!.componentIds.filter(x => x !== id); + await Promise.all([ + this.#componentRepository.delete(id), + this.#environmentRepository.update(this.#environment!) + ]); + } + }); + + this.append( + p(` + Components are self-contained elements in the Environment that provide + an interface which can be used by a System to interact with. + `), + dataTable + ); + + this.#environmentRepository.addEventListener('update', () => dataTable.renderData()); + this.#componentRepository.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 e80bf713..8c5f1fff 100644 --- a/src/presentation/pages/solution/environment/EnvironmentsIndexPage.mts +++ b/src/presentation/pages/solution/environment/EnvironmentsIndexPage.mts @@ -31,6 +31,11 @@ export default class EnvironmentsIndexPage extends Page { icon: 'anchor', href: `${location.pathname}/constraints` }), + new MiniCard({ + title: 'Components', + icon: 'grid', + href: `${location.pathname}/components` + }), new MiniCard({ title: 'Invariants', icon: 'lock',