Skip to content

Commit

Permalink
Merge pull request #27 from sybila/feat/functions_editor
Browse files Browse the repository at this point in the history
WIP: Feat/functions editor
  • Loading branch information
daemontus authored Feb 16, 2024
2 parents 353145f + 16a9133 commit 8f87ad2
Show file tree
Hide file tree
Showing 27 changed files with 1,105 additions and 5,610 deletions.
5,170 changes: 0 additions & 5,170 deletions package-lock.json

This file was deleted.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@tauri-apps/api": "^1.4.0",
"@types/cytoscape": "^3.19.15",
"@types/cytoscape-dagre": "^2.3.3",
"@types/lodash-es": "^4.17.12",
"ace-builds": "^1.32.3",
"cytoscape": "^3.27.0",
"cytoscape-dagre": "^2.5.0",
Expand All @@ -33,7 +34,7 @@
"@types/ace": "^0.0.52",
"@types/cytoscape-edgehandles": "^4.0.3",
"@types/cytoscape-popper": "^2.0.4",
"@types/lodash": "^4.14.201",
"@types/lodash": "^4.14.202",
"@types/node": "^20.3.2",
"@types/rollup-plugin-less": "^1.1.4",
"@types/uikit": "^3.14.0",
Expand Down
9 changes: 7 additions & 2 deletions src/aeon_events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ interface AeonState {
/** Set a name of variable with given ID to a given new name. */
setVariableName: (varId: string, newName: string) => void
/** Object with `original_id` of a variable and its `new_id`. */
variableIdChanged: Observable<object>
variableIdChanged: Observable<VariableIdUpdateData>
/** Set an ID of variable with given original ID to a new id. */
setVariableId: (originalId: string, newId: string) => void

Expand Down Expand Up @@ -153,6 +153,11 @@ export interface LayoutNodeDataPrototype {
py: number
}

/**
* An object representing information needed for variable id change
*/
export interface VariableIdUpdateData { original_id: string, new_id: string }

/** A function that is notified when a state value changes. */
export type OnStateValue<T> = (value: T) => void

Expand Down Expand Up @@ -576,7 +581,7 @@ export const aeonState: AeonState = {
variableCreated: new Observable<VariableData>(['model', 'variable', 'add']),
variableRemoved: new Observable<VariableData>(['model', 'variable', 'remove']),
variableNameChanged: new Observable<VariableData>(['model', 'variable', 'set_name']),
variableIdChanged: new Observable<object>(['model', 'variable', 'set_id']),
variableIdChanged: new Observable<VariableIdUpdateData>(['model', 'variable', 'set_id']),

regulationCreated: new Observable<RegulationData>(['model', 'regulation', 'add']),
regulationRemoved: new Observable<RegulationData>(['model', 'regulation', 'remove']),
Expand Down
3 changes: 1 addition & 2 deletions src/html/component/content-pane/content-pane.less
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
}

.content-pane {
height: 99%;
height: 100%;
padding: 0.5em;
white-space: nowrap;
z-index: 2;
overflow: hidden;
position: relative;
}

Expand Down
5 changes: 3 additions & 2 deletions src/html/component/content-pane/content-pane.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { css, html, LitElement, type TemplateResult, unsafeCSS } from 'lit'
import { customElement, property } from 'lit/decorators.js'
import style_less from './content-pane.less?inline'
import { ContentData, TabData } from '../../util/tab-data'
import { TabData } from '../../util/tab-data'
import { library, icon } from '@fortawesome/fontawesome-svg-core'
import '../regulations-editor/regulations-editor'
import '../functions-editor/functions-editor'
import { faLock, faLockOpen } from '@fortawesome/free-solid-svg-icons'
import { aeonState } from '../../../aeon_events'
import { ContentData } from '../../util/data-interfaces'

library.add(faLock, faLockOpen)

Expand All @@ -27,7 +28,7 @@ export class ContentPane extends LitElement {

protected render (): TemplateResult {
return html`
<div class="content-pane uk-container uk-container-expand">
<div class="content-pane">
<button class="uk-button uk-button-small pin-button" @click="${this.pin}">
${this.tab.pinned ? icon(faLock).node : icon(faLockOpen).node}
</button>
Expand Down
42 changes: 42 additions & 0 deletions src/html/component/functions-editor/editor-tile/custom-ace.conf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Mode as TextMode } from 'ace-builds/src-noconflict/mode-text'

const TextHighlightRules = ace.require('ace/mode/text_highlight_rules').TextHighlightRules

class AeonHighlightRules extends TextHighlightRules {
constructor () {
super()
this.setKeywords = (kwMap: string) => {
this.keywordRule.onMatch = this.createKeywordMapper(kwMap, 'identifier')
}
this.keywordRule = {
regex: '\\w+',
onMatch: function () { return 'text' }
}
this.$rules = {
start: [{
token: 'keyword.operator',
regex: '&|\\|'
}, {
token: 'paren.lparen',
regex: '[[({]'
}, {
token: 'paren.rparen',
regex: '[\\])}]'
},
this.keywordRule
]
}
this.normalizeRules()
}
}

class AeonMode extends TextMode {
constructor () {
super()
this.$id = 'ace/mode/aeon'
this.$behaviour = this.$defaultBehaviour
this.HighlightRules = AeonHighlightRules
}
}

export default AeonMode
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
width: 99%;
}

.remove-reg {
height: inherit;
width: 3em;
align-self: start;
}

@media (prefers-color-scheme: dark) {
.regulation:hover {
Expand Down
140 changes: 140 additions & 0 deletions src/html/component/functions-editor/editor-tile/editor-tile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { css, LitElement, type PropertyValues, unsafeCSS } from 'lit'
import { property, query, state } from 'lit/decorators.js'
import style_less from './editor-tile.less?inline'
import { Essentiality, type IFunctionData, type IRegulationData, Monotonicity } from '../../../util/data-interfaces'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faMagnifyingGlass, faTrash } from '@fortawesome/free-solid-svg-icons'
import ace, { type Ace } from 'ace-builds'
import langTools from 'ace-builds/src-noconflict/ext-language_tools'
import 'ace-builds/esm-resolver'
import AeonMode from './custom-ace.conf'
import type { DebouncedFunc } from 'lodash-es'

library.add(faTrash, faMagnifyingGlass)

export abstract class EditorTile extends LitElement {
static styles = css`${unsafeCSS(style_less)}`
@property() index = 0
@property() functions: IFunctionData[] = []
@state() variableFunction = ''
@state() variableName = ''
@query('#name-field') nameField: HTMLInputElement | undefined
declare aceEditor: ace.Ace.Editor

protected constructor () {
super()
ace.require(langTools)
}

protected init (index: number, objectList: Array<{ id: string, function: string }>): void {
const editorElement = this.shadowRoot?.getElementById('function-editor')
if (editorElement === null || editorElement === undefined) return
this.aceEditor = ace.edit(editorElement, {
enableSnippets: true,
enableLiveAutocompletion: true,
behavioursEnabled: true,
value: objectList[index].function,
placeholder: '$f_' + objectList[index].id + '(...)',
minLines: 1,
maxLines: Infinity,
wrap: 'free',
fontSize: 14,
theme: 'ace/theme/cloud_editor'
})
// @ts-expect-error ts seems to be ignoring inherited properties
this.aceEditor.getSession().setMode(new AeonMode())
this.aceEditor.container.style.lineHeight = '1.5em'
this.aceEditor.container.style.fontSize = '1em'
this.aceEditor.renderer.updateFontSize()
this.aceEditor.getSession().on('change', this.functionUpdated)
// @ts-expect-error $highlightRules exists but not defined in the d.ts file
this.aceEditor.session.getMode().$highlightRules.setKeywords({ 'constant.language': objectList.map(v => v.id).join('|') })
this.aceEditor.renderer.attachToShadowRoot()
}

protected updated (_changedProperties: PropertyValues): void {
super.updated(_changedProperties)
}

protected updateEditor (name: string, func: string): void {
if (this.nameField !== undefined) {
this.nameField.value = name
}
if (func !== this.aceEditor.getValue()) {
this.aceEditor.getSession().off('change', this.functionUpdated)
this.aceEditor.session.setValue(this.aceEditor.setValue(func, func.length - 1))
this.aceEditor.getSession().on('change', this.functionUpdated)
}
langTools.setCompleters([{
getCompletions: (_editor: Ace.Editor, _session: Ace.EditSession, _point: Ace.Point, _prefix: string, callback: Ace.CompleterCallback) => {
callback(null, this.functions.map((v): Ace.ValueCompletion => ({
meta: v.id + '(' + v.variables.map(fv => fv.source).join(', ') + ')',
value: v.id + '(' + ')',
caption: v.id
})))
}
}])
}

protected getRegulationSymbol (essential: Essentiality, monotonicity: Monotonicity): string {
let res = '-'
switch (essential) {
case Essentiality.FALSE:
res += '/'
break
case Essentiality.TRUE:
res += ''
break
default:
res += '?'
}
switch (monotonicity) {
case Monotonicity.ACTIVATION:
res += '>'
break
case Monotonicity.INHIBITION:
res += '|'
break
case Monotonicity.DUAL:
res += '*'
break
default:
res += '?'
}
return res
}

abstract readonly nameUpdated: DebouncedFunc<(name: string) => void>

abstract readonly functionUpdated: DebouncedFunc<() => void>

protected monotonicityClass (monotonicity: Monotonicity): string {
switch (monotonicity) {
case Monotonicity.INHIBITION:
return 'uk-text-danger'
case Monotonicity.ACTIVATION:
return 'uk-text-success'
case Monotonicity.DUAL:
return 'uk-text-primary'
case Monotonicity.UNSPECIFIED:
return 'uk-text-muted'
default:
return ''
}
}

abstract toggleEssentiality (regulation: IRegulationData): void
abstract toggleMonotonicity (regulation: IRegulationData): void
abstract removeVariable (): void

protected getEssentialityText (essentiality: Essentiality): string {
switch (essentiality) {
case Essentiality.FALSE:
return 'non-essential'
case Essentiality.TRUE:
return 'essential'
default:
return 'unknown'
}
}
}
Loading

0 comments on commit 8f87ad2

Please sign in to comment.