diff --git a/src-tauri/src/sketchbook/_sketch/_impl_session_state.rs b/src-tauri/src/sketchbook/_sketch/_impl_session_state.rs index 978990c..de1f351 100644 --- a/src-tauri/src/sketchbook/_sketch/_impl_session_state.rs +++ b/src-tauri/src/sketchbook/_sketch/_impl_session_state.rs @@ -2,7 +2,7 @@ use crate::app::event::Event; use crate::app::state::{Consumed, SessionHelper, SessionState}; use crate::app::DynError; use crate::sketchbook::data_structs::SketchData; -use crate::sketchbook::event_utils::make_state_change; +use crate::sketchbook::event_utils::{make_reversible, make_state_change}; use crate::sketchbook::{JsonSerde, Sketch}; use std::fs::File; use std::io::Read; @@ -100,6 +100,21 @@ impl SessionState for Sketch { state_change, reset: false, }) + } else if Self::starts_with("set_annotation", at_path).is_some() { + let new_annotation = Self::clone_payload_str(event, "sketch")?; + let orig_annotation = self.get_annotation().to_string(); + if new_annotation == orig_annotation { + return Ok(Consumed::NoChange); + } + + // set the annotation and prepare state-change + reverse events + self.set_annotation(&new_annotation); + let payload = serde_json::to_string(&new_annotation).unwrap(); + let state_change = Event::build(&["sketch", "set_annotation"], Some(&payload)); + let mut reverse_event = event.clone(); + reverse_event.payload = Some(orig_annotation); + + Ok(make_reversible(state_change, event, reverse_event)) } else if Self::starts_with("assert_consistency", at_path).is_some() { // this is a "synthetic" event that either returns an error, or Consumed::NoChange self.assert_consistency()?; diff --git a/src/aeon_state.ts b/src/aeon_state.ts index 838ea43..8ee1e2a 100644 --- a/src/aeon_state.ts +++ b/src/aeon_state.ts @@ -177,8 +177,7 @@ interface AeonState { /** Run the explicit consistency check on the sketch. */ checkConsistency: () => void - /** Results of an explicit consistency check. */ - // TODO: make properly (now just a string) + /** Results of an explicit consistency check (a summary message). */ consistencyResults: Observable /** Export the sketch data to a file. */ exportSketch: (path: string) => void @@ -192,6 +191,10 @@ interface AeonState { newSketch: () => void /** The whole replaced sketch instance (after importing or starting a new sketch). */ sketchReplaced: Observable + /** Set annotation of the whole sketch. */ + setAnnotation: (annotation: string) => void + /** Annotation of the whole sketch was changed. */ + annotationChanged: Observable /** The state of the main model. */ model: { @@ -578,6 +581,10 @@ export const aeonState: AeonState = { }, sketch: { sketchRefreshed: new Observable(['sketch', 'get_whole_sketch']), + consistencyResults: new Observable(['sketch', 'consistency_results']), + sketchReplaced: new Observable(['sketch', 'set_all']), + annotationChanged: new Observable(['sketch', 'set_annotation']), + refreshSketch (): void { aeonEvents.refresh(['sketch', 'get_whole_sketch']) }, @@ -587,7 +594,6 @@ export const aeonState: AeonState = { payload: path }) }, - sketchReplaced: new Observable(['sketch', 'set_all']), importSketch (path: string): void { aeonEvents.emitAction({ path: ['sketch', 'import_sketch'], @@ -618,7 +624,12 @@ export const aeonState: AeonState = { payload: null }) }, - consistencyResults: new Observable(['sketch', 'consistency_results']), + setAnnotation (annotation: string): void { + aeonEvents.emitAction({ + path: ['sketch', 'set_annotation'], + payload: annotation + }) + }, model: { modelRefreshed: new Observable(['sketch', 'model', 'get_whole_model']), diff --git a/src/html/component-editor/annotations-tab/annotations-tab.less b/src/html/component-editor/annotations-tab/annotations-tab.less index eccba83..9fa28c1 100644 --- a/src/html/component-editor/annotations-tab/annotations-tab.less +++ b/src/html/component-editor/annotations-tab/annotations-tab.less @@ -48,3 +48,28 @@ .header h3 { color: @text-dark; } + +.sketch-annotation { + width: 100%; + padding: 0; + border: none; + background: none; + resize: vertical; + font-size: inherit; + font-family: inherit; + color: inherit; + outline: none; +} + +textarea::placeholder { + font-weight: inherit; + color: rgba(255, 255, 255, 0.7); +} + +.sketch-annotation:focus { + border: 1px solid lightgray; +} + +.placeholder { + color: rgba(255, 255, 255, 0.7); +} diff --git a/src/html/component-editor/annotations-tab/annotations-tab.ts b/src/html/component-editor/annotations-tab/annotations-tab.ts index ea1e49e..2f448fc 100644 --- a/src/html/component-editor/annotations-tab/annotations-tab.ts +++ b/src/html/component-editor/annotations-tab/annotations-tab.ts @@ -4,48 +4,60 @@ import style_less from './annotations-tab.less?inline' import { ContentData, type IObservationSet } from '../../util/data-interfaces' import './annotation-tile/annotation-tile' import './annotation-tile/dataset-tile' +import { aeonState } from '../../../aeon_state' @customElement('annotations-tab') export class AnnotationsTab extends LitElement { static styles = css`${unsafeCSS(style_less)}` @property() contentData = ContentData.create() - addSketchAnnot (): void { - // TODO + constructor () { + super() + aeonState.sketch.annotationChanged.addEventListener(this.#annotationChanged.bind(this)) } - formatSketchAnnotation (): TemplateResult<1> { - return html`No annotations available for the sketch.` + #annotationChanged (annotation: string): void { + // propagate the current version of annotation via event that will be captured by root component + this.dispatchEvent(new CustomEvent('save-annotation', { + bubbles: true, + composed: true, + detail: { annotation } + })) } - addVarAnnot (): void { - // TODO + private changeAnnotation (event: Event): void { + const target = event.target as HTMLTextAreaElement + aeonState.sketch.setAnnotation(target.value) + } + + formatSketchAnnotation (): TemplateResult<1> { + return html` + + ` } formatVarAnnotations (): TemplateResult<1> { const annotatedVars = this.contentData.variables .filter(variable => variable.annotation.trim() !== '') if (annotatedVars.length === 0) { - return html`

No annotations available for variables.

` + return html`

No annotations available for variables.

` } - return html`${annotatedVars.map(variable => this.renderAnnotationTile(variable.id, variable.annotation))}` - } - - addFnAnnot (): void { - // TODO + return html`
${annotatedVars.map(variable => this.renderAnnotationTile(variable.id, variable.annotation))}
` } formatFnAnnotations (): TemplateResult<1> { const annotatedFns = this.contentData.functions .filter(func => func.annotation.trim() !== '') if (annotatedFns.length === 0) { - return html`

No annotations available for functions.

` + return html`

No annotations available for functions.

` } - return html`${annotatedFns.map(func => this.renderAnnotationTile(func.id, func.annotation))}` - } - - addDatasetAnnot (): void { - // TODO + return html`
${annotatedFns.map(func => this.renderAnnotationTile(func.id, func.annotation))}
` } formatDatasetAnnotations (): TemplateResult<1> { @@ -53,35 +65,27 @@ export class AnnotationsTab extends LitElement { .filter(d => d.annotation.trim() !== '' || d.observations.some(obs => obs.annotation.trim() !== '')) if (annotatedDatasets.length === 0) { - return html`

No annotations available for datasets or observations.

` + return html`

No annotations available for datasets or observations.

` } - return html`${annotatedDatasets.map(dataset => this.renderDatasetTile(dataset))}` - } - - addDynPropAnnot (): void { - // TODO + return html`
${annotatedDatasets.map(dataset => this.renderDatasetTile(dataset))}
` } formatDynPropAnnotations (): TemplateResult<1> { const annotatedProps = this.contentData.dynamicProperties .filter(dynProp => dynProp.annotation.trim() !== '') if (annotatedProps.length === 0) { - return html`

No annotations available for dynamic properties.

` + return html`

No annotations available for dynamic properties.

` } - return html`${annotatedProps.map(dynProp => this.renderAnnotationTile(dynProp.id, dynProp.annotation))}` - } - - addStatPropAnnot (): void { - // TODO + return html`
${annotatedProps.map(dynProp => this.renderAnnotationTile(dynProp.id, dynProp.annotation))}
` } formatStatPropAnnotations (): TemplateResult<1> { const annotatedProps = this.contentData.staticProperties .filter(dynProp => dynProp.annotation.trim() !== '') if (annotatedProps.length === 0) { - return html`

No annotations available for static properties.

` + return html`

No annotations available for static properties.

` } - return html`${annotatedProps.map(statProp => this.renderAnnotationTile(statProp.id, statProp.annotation))}` + return html`
${annotatedProps.map(statProp => this.renderAnnotationTile(statProp.id, statProp.annotation))}
` } private renderAnnotationTile (id: string, content: string): TemplateResult<1> { diff --git a/src/html/component-editor/root-component/root-component.ts b/src/html/component-editor/root-component/root-component.ts index fb9ecf0..ecb8e0c 100644 --- a/src/html/component-editor/root-component/root-component.ts +++ b/src/html/component-editor/root-component/root-component.ts @@ -90,6 +90,7 @@ export default class RootComponent extends LitElement { this.addEventListener('save-observations', this.saveObservationData.bind(this)) this.addEventListener('save-dynamic-properties', this.saveDynamicPropertyData.bind(this)) this.addEventListener('save-static-properties', this.saveStaticPropertyData.bind(this)) + this.addEventListener('save-annotation', this.saveAnnotationData.bind(this)) // Since function ID change can affect many parts of the model (update fns, other uninterpreted fns, ...), // the event fetches the whole updated model data, and we make the change here in the root component. @@ -148,6 +149,12 @@ export default class RootComponent extends LitElement { this.saveDynamicProperties(properties) } + saveAnnotationData (event: Event): void { + // update annotation propagated from AnnotationTab + const annotation: string = (event as CustomEvent).detail.annotation + this.saveAnnotation(annotation) + } + private saveDynamicProperties (dynamicProperties: DynamicProperty[]): void { dynamicProperties.sort((a, b) => (a.id > b.id ? 1 : -1)) this.data = this.data.copy({ dynamicProperties }) @@ -182,6 +189,10 @@ export default class RootComponent extends LitElement { this.data = this.data.copy({ layout }) } + private saveAnnotation (annotation: string): void { + this.data = this.data.copy({ annotation }) + } + // Wrapper to save all components of the model at the same time. // Saving everything at the same time can help dealing with inconsistencies. private saveWholeModel ( @@ -205,7 +216,8 @@ export default class RootComponent extends LitElement { layout: ILayoutData, observations: IObservationSet[], staticProperties: StaticProperty[], - dynamicProperties: DynamicProperty[] + dynamicProperties: DynamicProperty[], + annotation: string ): void { functions.sort((a, b) => (a.id > b.id ? 1 : -1)) variables.sort((a, b) => (a.id > b.id ? 1 : -1)) @@ -213,7 +225,16 @@ export default class RootComponent extends LitElement { staticProperties.sort((a, b) => (a.id > b.id ? 1 : -1)) dynamicProperties.sort((a, b) => (a.id > b.id ? 1 : -1)) observations.sort((a, b) => (a.id > b.id ? 1 : -1)) - this.data = this.data.copy({ functions, variables, regulations, layout, staticProperties, dynamicProperties, observations }) + this.data = this.data.copy({ + functions, + variables, + regulations, + layout, + staticProperties, + dynamicProperties, + observations, + annotation + }) } // Set variable data (currently, sets a name and annotation). @@ -410,7 +431,16 @@ export default class RootComponent extends LitElement { const regulations = sketch.model.regulations.map(r => convertToIRegulation(r)) const layout = convertToILayout(sketch.model.layouts[0].nodes) - this.saveWholeSketch(functions, variables, regulations, layout, datasets, sketch.stat_properties, sketch.dyn_properties) + this.saveWholeSketch( + functions, + variables, + regulations, + layout, + datasets, + sketch.stat_properties, + sketch.dyn_properties, + sketch.annotation + ) } // refresh all components of the model, and save them at the same time diff --git a/src/html/util/data-interfaces.ts b/src/html/util/data-interfaces.ts index 3776bb1..d011a75 100644 --- a/src/html/util/data-interfaces.ts +++ b/src/html/util/data-interfaces.ts @@ -45,6 +45,7 @@ export class ContentData extends Data { observations: IObservationSet[] = [] dynamicProperties: DynamicProperty[] = [] staticProperties: StaticProperty[] = [] + annotation: string = '' } export interface IFunctionData {