Skip to content

Commit

Permalink
Refactor annotations tab, allow editing sketch annotation.
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrej33 committed Nov 1, 2024
1 parent 647766e commit 211728f
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 40 deletions.
17 changes: 16 additions & 1 deletion src-tauri/src/sketchbook/_sketch/_impl_session_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()?;
Expand Down
19 changes: 15 additions & 4 deletions src/aeon_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>
/** Export the sketch data to a file. */
exportSketch: (path: string) => void
Expand All @@ -192,6 +191,10 @@ interface AeonState {
newSketch: () => void
/** The whole replaced sketch instance (after importing or starting a new sketch). */
sketchReplaced: Observable<SketchData>
/** Set annotation of the whole sketch. */
setAnnotation: (annotation: string) => void
/** Annotation of the whole sketch was changed. */
annotationChanged: Observable<string>

/** The state of the main model. */
model: {
Expand Down Expand Up @@ -578,6 +581,10 @@ export const aeonState: AeonState = {
},
sketch: {
sketchRefreshed: new Observable<SketchData>(['sketch', 'get_whole_sketch']),
consistencyResults: new Observable<string>(['sketch', 'consistency_results']),
sketchReplaced: new Observable<SketchData>(['sketch', 'set_all']),
annotationChanged: new Observable<string>(['sketch', 'set_annotation']),

refreshSketch (): void {
aeonEvents.refresh(['sketch', 'get_whole_sketch'])
},
Expand All @@ -587,7 +594,6 @@ export const aeonState: AeonState = {
payload: path
})
},
sketchReplaced: new Observable<SketchData>(['sketch', 'set_all']),
importSketch (path: string): void {
aeonEvents.emitAction({
path: ['sketch', 'import_sketch'],
Expand Down Expand Up @@ -618,7 +624,12 @@ export const aeonState: AeonState = {
payload: null
})
},
consistencyResults: new Observable<string>(['sketch', 'consistency_results']),
setAnnotation (annotation: string): void {
aeonEvents.emitAction({
path: ['sketch', 'set_annotation'],
payload: annotation
})
},

model: {
modelRefreshed: new Observable<ModelData>(['sketch', 'model', 'get_whole_model']),
Expand Down
25 changes: 25 additions & 0 deletions src/html/component-editor/annotations-tab/annotations-tab.less
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
68 changes: 36 additions & 32 deletions src/html/component-editor/annotations-tab/annotations-tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,84 +4,88 @@ 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`
<textarea
class="sketch-annotation"
.value="${this.contentData.annotation}"
@focusout="${this.changeAnnotation}"
placeholder="Click to annotate the sketch..."
rows="2"
></textarea>
`
}

formatVarAnnotations (): TemplateResult<1> {
const annotatedVars = this.contentData.variables
.filter(variable => variable.annotation.trim() !== '')
if (annotatedVars.length === 0) {
return html`<p>No annotations available for variables.</p>`
return html`<div class="placeholder"><p>No annotations available for variables.</p></div>`
}
return html`${annotatedVars.map(variable => this.renderAnnotationTile(variable.id, variable.annotation))}`
}

addFnAnnot (): void {
// TODO
return html`<div>${annotatedVars.map(variable => this.renderAnnotationTile(variable.id, variable.annotation))}</div>`
}

formatFnAnnotations (): TemplateResult<1> {
const annotatedFns = this.contentData.functions
.filter(func => func.annotation.trim() !== '')
if (annotatedFns.length === 0) {
return html`<p>No annotations available for functions.</p>`
return html`<div class="placeholder"><p>No annotations available for functions.</p></div>`
}
return html`${annotatedFns.map(func => this.renderAnnotationTile(func.id, func.annotation))}`
}

addDatasetAnnot (): void {
// TODO
return html`<div>${annotatedFns.map(func => this.renderAnnotationTile(func.id, func.annotation))}</div>`
}

formatDatasetAnnotations (): TemplateResult<1> {
const annotatedDatasets = this.contentData.observations
.filter(d => d.annotation.trim() !== '' || d.observations.some(obs => obs.annotation.trim() !== ''))

if (annotatedDatasets.length === 0) {
return html`<p>No annotations available for datasets or observations.</p>`
return html`<div class="placeholder"><p>No annotations available for datasets or observations.</p></div>`
}
return html`${annotatedDatasets.map(dataset => this.renderDatasetTile(dataset))}`
}

addDynPropAnnot (): void {
// TODO
return html`<div>${annotatedDatasets.map(dataset => this.renderDatasetTile(dataset))}</div>`
}

formatDynPropAnnotations (): TemplateResult<1> {
const annotatedProps = this.contentData.dynamicProperties
.filter(dynProp => dynProp.annotation.trim() !== '')
if (annotatedProps.length === 0) {
return html`<p>No annotations available for dynamic properties.</p>`
return html`<div class="placeholder"><p>No annotations available for dynamic properties.</p></div>`
}
return html`${annotatedProps.map(dynProp => this.renderAnnotationTile(dynProp.id, dynProp.annotation))}`
}

addStatPropAnnot (): void {
// TODO
return html`<div>${annotatedProps.map(dynProp => this.renderAnnotationTile(dynProp.id, dynProp.annotation))}</div>`
}

formatStatPropAnnotations (): TemplateResult<1> {
const annotatedProps = this.contentData.staticProperties
.filter(dynProp => dynProp.annotation.trim() !== '')
if (annotatedProps.length === 0) {
return html`<p>No annotations available for static properties.</p>`
return html`<div class="placeholder"><p>No annotations available for static properties.</p></div>`
}
return html`${annotatedProps.map(statProp => this.renderAnnotationTile(statProp.id, statProp.annotation))}`
return html`<div>${annotatedProps.map(statProp => this.renderAnnotationTile(statProp.id, statProp.annotation))}</div>`
}

private renderAnnotationTile (id: string, content: string): TemplateResult<1> {
Expand Down
36 changes: 33 additions & 3 deletions src/html/component-editor/root-component/root-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 })
Expand Down Expand Up @@ -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 (
Expand All @@ -205,15 +216,25 @@ 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))
regulations.sort((a, b) => (a.source + a.target > b.source + b.target ? 1 : -1))
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).
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/html/util/data-interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export class ContentData extends Data {
observations: IObservationSet[] = []
dynamicProperties: DynamicProperty[] = []
staticProperties: StaticProperty[] = []
annotation: string = ''
}

export interface IFunctionData {
Expand Down

0 comments on commit 211728f

Please sign in to comment.