diff --git a/ivette/src/dome/src/renderer/data/states.ts b/ivette/src/dome/src/renderer/data/states.ts index c1fbb827435c3b1f721268d059e9a4640d7aeb0b..3e07ef8cb453d111db4d9e2d5e47c48e5ae83c12 100644 --- a/ivette/src/dome/src/renderer/data/states.ts +++ b/ivette/src/dome/src/renderer/data/states.ts @@ -17,8 +17,49 @@ import * as JSON from './json'; const UPDATE = 'dome.states.update'; +/** State interface. */ +export type State<A> = [A, (update: A) => void]; + +/** State field of an object state. */ +export function key<A, K extends keyof A>( + state: State<A>, + key: K, +): State<A[K]> { + const [props, setProps] = state; + return [props[key], (value: A[K]) => { + const newProps = Object.assign({}, props); + newProps[key] = value; + setProps(newProps); + }]; +} + +/** State index of an array state. */ +export function index<A>( + state: State<A[]>, + index: number, +): State<A> { + const [array, setArray] = state; + return [array[index], (value: A) => { + const newArray = array.slice(); + newArray[index] = value; + setArray(newArray); + }]; +} + +/** Log state updates in the console. */ +export function debug<A>(msg: string, st: State<A>): State<A> { + const [value, setValue] = st; + return [value, (v) => { console.log(msg, v); setValue(v); }]; +} + +/** Purely local value. No hook, no events, just a ref. */ +export function local<A>(init: A): State<A> { + const ref = { current: init }; + return [ref.current, (v) => ref.current = v]; +} + /** Cross-component State. */ -export class State<A> { +export class GlobalState<A> { private value: A; private emitter: Emitter; @@ -53,8 +94,10 @@ export class State<A> { } -/** React Hook, similar to `React.useState()`. */ -export function useState<A>(s: State<A>): [A, (update: A) => void] { +/** React Hook, similar to `React.useState()`. + Assignments to the global state also update _all_ + its associated hooks and listeners. */ +export function useGlobalState<A>(s: GlobalState<A>): State<A> { const [current, setCurrent] = React.useState<A>(s.getValue); React.useEffect(() => { s.on(setCurrent); diff --git a/ivette/src/frama-c/states.ts b/ivette/src/frama-c/states.ts index ea1737caee51e08a7610dc7560ffba30cf91c236..3474e9acd6cd3c21998f410e6a8a3d1381e1733f 100644 --- a/ivette/src/frama-c/states.ts +++ b/ivette/src/frama-c/states.ts @@ -13,7 +13,7 @@ import React from 'react'; import * as Dome from 'dome'; import * as Json from 'dome/data/json'; import { Order } from 'dome/data/compare'; -import * as GlobalStates from 'dome/data/states'; +import { GlobalState, useGlobalState } from 'dome/data/states'; import { useModel } from 'dome/table/models'; import { CompactModel } from 'dome/table/arrays'; import * as Server from './server'; @@ -534,7 +534,7 @@ function reducer(s: Selection, action: SelectionActions): Selection { } } -const GlobalSelection = new GlobalStates.State<Selection>({ +const GlobalSelection = new GlobalState<Selection>({ current: undefined, prevSelections: [], nextSelections: [], @@ -544,7 +544,7 @@ const GlobalSelection = new GlobalStates.State<Selection>({ Current selection. */ export function useSelection(): [Selection, (a: SelectionActions) => void] { - const [selection, setSelection] = GlobalStates.useState(GlobalSelection); + const [selection, setSelection] = useGlobalState(GlobalSelection); function update(action: SelectionActions) { const nextSelection = reducer(selection, action);