import React from "react"; import { observable, computed, action, runInAction, reaction, toJS, makeObservable, IReactionDisposer } from "mobx"; import { observer } from "mobx-react"; import { objectEqual, formatDateTimeLong } from "eez-studio-shared/util"; import { beginTransaction, commitTransaction, IStore } from "eez-studio-shared/store"; import { TIME_UNIT } from "eez-studio-shared/units"; import { Dialog, showDialog } from "eez-studio-ui/dialog"; import { TextInputProperty, ColorInputProperty, PropertyList, SelectFromListProperty } from "eez-studio-ui/properties"; import { IListNode, ListItem } from "eez-studio-ui/list"; import { Icon } from "eez-studio-ui/icon"; import { ChartMode, IAxisModel, ChartsController, MeasurementsModel, IChartsController } from "eez-studio-ui/chart/chart"; import { RulersModel, IRulersModel } from "eez-studio-ui/chart/rulers"; import { logUpdate, IActivityLogEntry, getHistoryItemById, activityLogStore } from "instrument/window/history/activity-log"; import { ChartPreview } from "instrument/window/chart-preview"; import { HistoryItem } from "instrument/window/history/item"; import type { Waveform } from "instrument/window/waveform/generic"; import { WaveformDefinitionProperties } from "instrument/window/waveform/WaveformDefinitionProperties"; import { WaveformAxisModel } from "instrument/window/waveform/WaveformAxisModel"; import { ViewOptions } from "instrument/window/waveform/ViewOptions"; import { WaveformTimeAxisModel } from "instrument/window/waveform/time-axis"; import { IToolbarOptions, WaveformToolbar } from "instrument/window/waveform/toolbar"; import type { ChartsDisplayOption } from "instrument/window/lists/common-tools"; import type { IAppStore } from "instrument/window/history/history"; //////////////////////////////////////////////////////////////////////////////// export type IWaveformLink = { id: string; label: string; color: string; colorInverse: string; }; //////////////////////////////////////////////////////////////////////////////// export class MultiWaveformChartsController extends ChartsController { constructor( public multiWaveform: MultiWaveform, mode: ChartMode, xAxisModel: IAxisModel, viewOptions: ViewOptions ) { super(mode, xAxisModel, viewOptions); } get chartViewOptionsProps() { return { showRenderAlgorithm: true, showShowSampledDataOption: false }; } get supportRulers() { return true; } isMultiWaveformChartsController = true; } //////////////////////////////////////////////////////////////////////////////// export const ChartHistoryItemComponent = observer( class ChartHistoryItemComponent extends React.Component<{ appStore: IAppStore; historyItem: MultiWaveform; }> { setVisibleTimeoutId: any; render() { return (

{formatDateTimeLong( this.props.historyItem.date )}

); } } ); //////////////////////////////////////////////////////////////////////////////// interface ILinkedWaveform { waveformLink: IWaveformLink; waveform: Waveform; yAxisModel: IAxisModel; } export class MultiWaveform extends HistoryItem { dispose1: IReactionDisposer | undefined; dispose2: IReactionDisposer | undefined; dispose3: IReactionDisposer | undefined; dispose4: IReactionDisposer | undefined; constructor( store: IStore, activityLogEntry: IActivityLogEntry, private options?: { toolbar?: IToolbarOptions } ) { super(store, activityLogEntry); makeObservable(this, { waveformLinks: observable, messageObject: computed, measurements: computed, linkedWaveforms: computed, longestWaveform: computed, xAxisUnit: computed, samplingRate: computed, length: computed }); const message = JSON.parse(this.message); this.waveformLinks = message.waveformLinks || message; // update waveformLinks when message changes this.dispose1 = reaction( () => ({ message: JSON.parse(this.message), waveformLinks: toJS(this.waveformLinks) }), arg => { if ( !objectEqual(arg.message.waveformLinks, arg.waveformLinks) ) { runInAction( () => (this.waveformLinks = arg.message.waveformLinks) ); } } ); this.viewOptions = new ViewOptions( message.viewOptions || this.linkedWaveforms[0]?.waveform.viewOptions ); // save viewOptions when changed this.dispose2 = reaction( () => ({ message: JSON.parse(this.message), viewOptions: toJS(this.viewOptions) }), arg => { if (!objectEqual(arg.message.viewOptions, arg.viewOptions)) { logUpdate( this.store, { id: this.id, oid: this.oid, message: JSON.stringify( Object.assign(arg.message, { viewOptions: arg.viewOptions }) ) }, { undoable: false } ); } } ); this.rulers = new RulersModel(message.rulers); this.rulers.initYRulers(this.waveformLinks.length); // save rulers when changed this.dispose3 = reaction( () => toJS(this.rulers), rulers => { if (rulers.pauseDbUpdate) { return; } delete rulers.pauseDbUpdate; const message = JSON.parse(this.message); if (!objectEqual(message.rulers, rulers)) { logUpdate( this.store, { id: this.id, oid: this.oid, message: JSON.stringify( Object.assign(message, { rulers }) ) }, { undoable: false } ); } } ); // save measurements when changed this.dispose4 = reaction( () => toJS(this.measurements), measurements => { const message = JSON.parse(this.message); if (!objectEqual(message.measurements, measurements)) { const messageStr = JSON.stringify( Object.assign(message, { measurements }) ); runInAction(() => (this.message = messageStr)); logUpdate( this.store, { id: this.id, oid: this.oid, message: messageStr }, { undoable: false } ); } } ); } xAxisModel = new WaveformTimeAxisModel(this); waveformLinks: IWaveformLink[]; viewOptions: ViewOptions; rulers: IRulersModel; get messageObject() { return JSON.parse(this.message); } get measurements() { return new MeasurementsModel(this.messageObject.measurements); } get linkedWaveforms() { return this.waveformLinks .map(waveformLink => { let waveform = getHistoryItemById( this.store, waveformLink.id )! as Waveform; if (!waveform) { waveform = getHistoryItemById( activityLogStore, waveformLink.id )! as Waveform; if (waveform) { console.log(waveformLink.id); } } return { waveformLink, waveform, yAxisModel: new WaveformAxisModel(waveform, waveformLink) }; }) .filter(waveformLink => !!waveformLink.waveform); } get longestWaveform() { let longestWaveform; let maxTime; for (let i = 0; i < this.linkedWaveforms.length; i++) { const waveform = this.linkedWaveforms[i].waveform; let time = waveform.length / waveform.samplingRate; if (maxTime === undefined || time > maxTime) { longestWaveform = waveform; maxTime = time; } } return longestWaveform; } get xAxisUnit() { return TIME_UNIT; } get samplingRate() { return this.longestWaveform ? this.longestWaveform.samplingRate : 1; } get length() { return this.longestWaveform ? this.longestWaveform.length : 0; } chartsController: ChartsController; createChartsController( appStore: IAppStore, displayOption: ChartsDisplayOption, mode: ChartMode ): ChartsController { if (this.chartsController) { this.chartsController.destroy(); } const chartsController = new MultiWaveformChartsController( this, mode, this.xAxisModel, this.viewOptions ); this.chartsController = chartsController; this.xAxisModel.chartsController = chartsController; chartsController.chartControllers = this.linkedWaveforms.map( (linkedWaveform: ILinkedWaveform, i: number) => { const chartController = linkedWaveform.waveform.createChartController( chartsController, linkedWaveform.waveform.id, linkedWaveform.yAxisModel ); return chartController; } ); chartsController.createRulersController(this.rulers); chartsController.createMeasurementsController(this.measurements); return chartsController; } renderToolbar(chartsController: IChartsController): React.ReactNode { return ( ); } openConfigurationDialog() { showDialog(); } get xAxisDefaultSubdivisionOffset(): number | undefined { return this.linkedWaveforms[0]?.waveform.xAxisDefaultSubdivisionOffset; } get xAxisDefaultSubdivisionScale() { return this.linkedWaveforms[0]?.waveform.xAxisDefaultSubdivisionScale; } getListItemElement(appStore: IAppStore): React.ReactNode { return ( ); } isZoomable = true; override dispose() { super.dispose(); if (this.dispose1) { this.dispose1(); } if (this.dispose2) { this.dispose2(); } if (this.dispose3) { this.dispose3(); } if (this.dispose4) { this.dispose4(); } } } //////////////////////////////////////////////////////////////////////////////// class WaveformLinkProperties { constructor(public linkedWaveform: ILinkedWaveform) { makeObservable(this, { props: observable, errors: observable }); this.props = Object.assign( { label: this.linkedWaveform.yAxisModel.label, color: this.linkedWaveform.yAxisModel.color, colorInverse: this.linkedWaveform.yAxisModel.colorInverse }, this.linkedWaveform.waveformLink ); } props: IWaveformLink; errors: boolean = false; async checkValidity() { return true; } render() { return [ (this.props.label = value))} />, (this.props.color = value))} />, (this.props.colorInverse = value) )} /> ]; } } //////////////////////////////////////////////////////////////////////////////// interface IJoinedWaveformLinkAndDefinitionProperties { linkedWaveform: ILinkedWaveform; waveformLinkProperties: WaveformLinkProperties; waveformDefinitionProperties: WaveformDefinitionProperties; } const MultiWaveformConfigurationDialog = observer( class MultiWaveformConfigurationDialog extends React.Component<{ multiWaveform: MultiWaveform; }> { waveforms: IJoinedWaveformLinkAndDefinitionProperties[] = this.props.multiWaveform.linkedWaveforms.map( (linkedWaveform: ILinkedWaveform, i: number) => { return { linkedWaveform, waveformLinkProperties: new WaveformLinkProperties( linkedWaveform ), waveformDefinitionProperties: new WaveformDefinitionProperties( linkedWaveform.waveform.waveformDefinition ) }; } ); selectedWaveform: IJoinedWaveformLinkAndDefinitionProperties = this.waveforms[0]; constructor(props: any) { super(props); makeObservable(this, { waveforms: observable, selectedWaveform: observable.shallow, waveformListNodes: computed, selectWaveform: action.bound }); } get waveformListNodes(): IListNode[] { return this.waveforms.map( joinedWaveformLinkAndDefinitionProperties => ({ id: joinedWaveformLinkAndDefinitionProperties.linkedWaveform .waveformLink.id, data: joinedWaveformLinkAndDefinitionProperties, selected: joinedWaveformLinkAndDefinitionProperties === this.selectedWaveform }) ); } renderWaveformListNode = (node: IListNode) => { let waveformLinkProperties = node.data as IJoinedWaveformLinkAndDefinitionProperties; const errors = waveformLinkProperties.waveformLinkProperties.errors || (waveformLinkProperties.waveformDefinitionProperties && waveformLinkProperties.waveformDefinitionProperties.errors); return ( " } rightIcon={errors ? "material:error_outline" : undefined} rightIconClassName="text-danger" /> ); }; selectWaveform(node: IListNode) { this.selectedWaveform = node.data; } handleSubmit = async () => { let anyError = false; for (let i = 0; i < this.waveforms.length; i++) { const waveformLinkProperties = this.waveforms[i]; if ( !(await waveformLinkProperties.waveformLinkProperties.checkValidity()) ) { anyError = true; } const waveformProperties = waveformLinkProperties.waveformDefinitionProperties; if (waveformProperties) { if (!(await waveformProperties.checkValidity())) { anyError = true; } } } if (anyError) { return false; } const changedHistoryItems: { id: string; oid: string; message: string; }[] = []; // update chart history item (if changed) const waveformLinks = this.waveforms.map( joinedWaveformLinkAndDefinitionProperties => ({ id: joinedWaveformLinkAndDefinitionProperties.linkedWaveform .waveformLink.id, label: joinedWaveformLinkAndDefinitionProperties .waveformLinkProperties.props.label, color: joinedWaveformLinkAndDefinitionProperties .waveformLinkProperties.props.color, colorInverse: joinedWaveformLinkAndDefinitionProperties .waveformLinkProperties.props.colorInverse }) ); if ( !objectEqual( waveformLinks, this.waveforms.map(x => x.linkedWaveform.waveformLink) ) ) { const multiWaveformHistoryItemMessage = Object.assign( JSON.parse(this.props.multiWaveform.message), { waveformLinks } ); changedHistoryItems.push({ id: this.props.multiWaveform.id, oid: this.props.multiWaveform.oid, message: JSON.stringify(multiWaveformHistoryItemMessage) }); } // update waveform history item's (if changed) this.waveforms.forEach( joinedWaveformLinkAndDefinitionProperties => { const waveformHistoryItemMessage = JSON.parse( joinedWaveformLinkAndDefinitionProperties.linkedWaveform .waveform.message ); if ( !objectEqual( joinedWaveformLinkAndDefinitionProperties .waveformDefinitionProperties.propsValidated, waveformHistoryItemMessage.waveformDefinition ) ) { const newWaveformHistoryItemMessage = Object.assign( waveformHistoryItemMessage, { waveformDefinition: joinedWaveformLinkAndDefinitionProperties .waveformDefinitionProperties .propsValidated } ); changedHistoryItems.push({ id: joinedWaveformLinkAndDefinitionProperties .linkedWaveform.waveform.id, oid: joinedWaveformLinkAndDefinitionProperties .linkedWaveform.waveform.oid, message: JSON.stringify( newWaveformHistoryItemMessage ) }); } } ); if (changedHistoryItems.length > 0) { beginTransaction("Edit chart configuration"); changedHistoryItems.forEach(changedHistoryItem => { logUpdate( this.props.multiWaveform.store, changedHistoryItem, { undoable: true } ); }); commitTransaction(); } return true; }; render() { return (
{this.selectedWaveform.waveformLinkProperties.render()} {this.selectedWaveform .waveformDefinitionProperties && this.selectedWaveform.waveformDefinitionProperties.render()}
); } } );