Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const registerCreateEuiMarkdownAction = (uiActions: UiActionsStart) => {
embeddable.addNewPanel<MarkdownEditorSerializedState>(
{
panelType: EUI_MARKDOWN_ID,
initialState: { content: '# hello world!' },
serializedState: { rawState: { content: '# hello world!' } },
},
true
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,12 @@

export { apiCanAddNewPanel, type CanAddNewPanel } from './interfaces/can_add_new_panel';
export {
apiHasRuntimeChildState,
apiHasSerializedChildState,
type HasRuntimeChildState,
apiHasLastSavedChildState,
type HasLastSavedChildState,
type HasSerializedChildState,
} from './interfaces/child_state';
export { childrenUnsavedChanges$ } from './interfaces/unsaved_changes/children_unsaved_changes';
export { initializeUnsavedChanges } from './interfaces/unsaved_changes/initialize_unsaved_changes';
export {
apiHasSaveNotification,
type HasSaveNotification,
} from './interfaces/has_save_notification';
export {
apiCanDuplicatePanels,
apiCanExpandPanels,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,30 @@
*/

import { SerializedPanelState } from '@kbn/presentation-publishing';
import { Subject } from 'rxjs';

export interface HasSerializedChildState<SerializedState extends object = object> {
getSerializedStateForChild: (
childId: string
) => SerializedPanelState<SerializedState> | undefined;
}

/**
* @deprecated Use `HasSerializedChildState` instead. All interactions between the container and the child should use the serialized state.
*/
export interface HasRuntimeChildState<RuntimeState extends object = object> {
getRuntimeStateForChild: (childId: string) => Partial<RuntimeState> | undefined;
}

export const apiHasSerializedChildState = <SerializedState extends object = object>(
api: unknown
): api is HasSerializedChildState<SerializedState> => {
return Boolean(api && (api as HasSerializedChildState).getSerializedStateForChild);
};
/**
* @deprecated Use `HasSerializedChildState` instead. All interactions between the container and the child should use the serialized state.
*/
export const apiHasRuntimeChildState = <RuntimeState extends object = object>(

export const apiHasLastSavedChildState = <SerializedState extends object = object>(
api: unknown
): api is HasRuntimeChildState<RuntimeState> => {
return Boolean(api && (api as HasRuntimeChildState).getRuntimeStateForChild);
): api is HasLastSavedChildState<SerializedState> => {
return (
Boolean(api && (api as HasLastSavedChildState).getLastSavedStateForChild) &&
Boolean(api && (api as HasLastSavedChildState).saveNotification$)
);
};

export interface HasLastSavedChildState<SerializedState extends object = object> {
getLastSavedStateForChild: (childId: string) => SerializedPanelState<SerializedState> | undefined;
saveNotification$: Subject<void>; // a notification that state has been saved
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,13 @@ import {
import { BehaviorSubject, combineLatest, isObservable, map, Observable, of, switchMap } from 'rxjs';
import { apiCanAddNewPanel, CanAddNewPanel } from './can_add_new_panel';

export interface PanelPackage<
SerializedStateType extends object = object,
RuntimeStateType extends object = object
> {
export interface PanelPackage<SerializedStateType extends object = object> {
panelType: string;

/**
* The serialized state of this panel.
*/
serializedState?: SerializedPanelState<SerializedStateType>;

/**
* The runtime state of this panel. @deprecated Use `serializedState` instead.
*/
initialState?: RuntimeStateType;
serializedState: SerializedPanelState<SerializedStateType>;
}

export interface PresentationContainer extends CanAddNewPanel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,43 +9,43 @@

import { combineLatest, debounceTime, distinctUntilChanged, map, of, switchMap } from 'rxjs';
import deepEqual from 'fast-deep-equal';
import { apiPublishesUnsavedChanges, PublishesUnsavedChanges } from '@kbn/presentation-publishing';
import { PresentationContainer } from '../presentation_container';
import {
apiHasUniqueId,
apiPublishesUnsavedChanges,
HasUniqueId,
PublishesUnsavedChanges,
PublishingSubject,
} from '@kbn/presentation-publishing';

export const DEBOUNCE_TIME = 100;

/**
* Create an observable stream of unsaved changes from all react embeddable children
*/
export function childrenUnsavedChanges$(children$: PresentationContainer['children$']) {
export function childrenUnsavedChanges$<Api extends unknown = unknown>(
children$: PublishingSubject<{ [key: string]: Api }>
) {
return children$.pipe(
map((children) => Object.keys(children)),
distinctUntilChanged(deepEqual),

// children may change, so make sure we subscribe/unsubscribe with switchMap
switchMap((newChildIds: string[]) => {
if (newChildIds.length === 0) return of([]);
const childrenThatPublishUnsavedChanges = Object.entries(children$.value).filter(
([childId, child]) => apiPublishesUnsavedChanges(child)
) as Array<[string, PublishesUnsavedChanges]>;
const childrenThatPublishUnsavedChanges = Object.values(children$.value).filter(
(child) => apiPublishesUnsavedChanges(child) && apiHasUniqueId(child)
) as Array<PublishesUnsavedChanges & HasUniqueId>;

return childrenThatPublishUnsavedChanges.length === 0
? of([])
: combineLatest(
childrenThatPublishUnsavedChanges.map(([childId, child]) =>
child.unsavedChanges$.pipe(map((unsavedChanges) => ({ childId, unsavedChanges })))
childrenThatPublishUnsavedChanges.map((child) =>
child.hasUnsavedChanges$.pipe(
map((hasUnsavedChanges) => ({ uuid: child.uuid, hasUnsavedChanges }))
)
)
);
}),
debounceTime(DEBOUNCE_TIME),
map((unsavedChildStates) => {
const unsavedChildrenState: { [key: string]: object } = {};
unsavedChildStates.forEach(({ childId, unsavedChanges }) => {
if (unsavedChanges) {
unsavedChildrenState[childId] = unsavedChanges;
}
});
return Object.keys(unsavedChildrenState).length ? unsavedChildrenState : undefined;
})
debounceTime(DEBOUNCE_TIME)
);
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { Observable, combineLatest, combineLatestWith, debounceTime, map } from 'rxjs';
import { StateComparators } from './types';
import { PublishingSubject } from '../publishing_subject';
import { runComparators } from './state_comparators';

export const COMPARATOR_SUBJECTS_DEBOUNCE = 100;

export const diffComparators$ = <RuntimeState extends object = object>(
lastSavedState$: PublishingSubject<RuntimeState | undefined>,
comparators: StateComparators<RuntimeState>
): Observable<Partial<RuntimeState> | undefined> => {
const comparatorKeys = Object.keys(comparators) as Array<keyof RuntimeState>;
const comparatorSubjects = comparatorKeys.map((key) => comparators[key][0]); // 0th element of tuple is the subject

return combineLatest(comparatorSubjects).pipe(
debounceTime(COMPARATOR_SUBJECTS_DEBOUNCE),
map((latestStates) =>
comparatorKeys.reduce((acc, key, index) => {
acc[key] = latestStates[index] as RuntimeState[typeof key];
return acc;
}, {} as Partial<RuntimeState>)
),
combineLatestWith(lastSavedState$),
map(([latestState, lastSavedState]) =>
runComparators(comparators, comparatorKeys, lastSavedState, latestState)
)
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
*/

export type { ComparatorFunction, ComparatorDefinition, StateComparators } from './types';
export { getInitialValuesFromComparators, runComparators } from './state_comparators';
export { diffComparators$ } from './diff_comparators';
export { getUnchangingComparator } from './fallback_comparator';
export { latestComparatorValues$, runComparators } from './state_comparators';
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { combineLatest } from 'rxjs';
import { StateComparators } from './types';
import { PublishingSubject } from '../publishing_subject';

const defaultComparator = <T>(a: T, b: T) => a === b;

Expand All @@ -23,6 +25,16 @@ export const getInitialValuesFromComparators = <StateType extends object = objec
return initialValues;
};

export const latestComparatorValues$ = <StateType extends object>(
comparators: StateComparators<StateType>
) => {
const comparatorSubjects: Array<PublishingSubject<StateType[keyof StateType]>> = [];
for (const key of Object.keys(comparators) as Array<keyof StateType>) {
comparatorSubjects.push(comparators[key][0]); // 0th element of tuple is the subject
}
return combineLatest(comparatorSubjects);
};

export const runComparators = <StateType extends object = object>(
comparators: StateComparators<StateType>,
comparatorKeys: Array<keyof StateType>,
Expand Down
Loading