Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -371,10 +371,10 @@ export const ReactControlExample = ({
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
onClick={async () => {
onClick={() => {
if (controlGroupApi) {
saveNotification$.next();
setControlGroupSerializedState(await controlGroupApi.serializeState());
setControlGroupSerializedState(controlGroupApi.serializeState());
}
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

import { BehaviorSubject, Subject, combineLatest, map, merge } from 'rxjs';
import { v4 as generateId } from 'uuid';
import { asyncForEach } from '@kbn/std';
import { TimeRange } from '@kbn/es-query';
import {
PanelPackage,
Expand Down Expand Up @@ -146,14 +145,14 @@ export function getPageApi() {
},
onSave: async () => {
const panelsState: LastSavedState['panelsState'] = [];
await asyncForEach(panels$.value, async ({ id, type }) => {
panels$.value.forEach(({ id, type }) => {
try {
const childApi = children$.value[id];
if (apiHasSerializableState(childApi)) {
panelsState.push({
id,
type,
panelState: await childApi.serializeState(),
panelState: childApi.serializeState(),
});
}
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { ViewMode } from '@kbn/presentation-publishing';
import { ReactEmbeddableRenderer } from '@kbn/embeddable-plugin/public';
import { BehaviorSubject, Subject } from 'rxjs';
import useMountedState from 'react-use/lib/useMountedState';
import { SAVED_BOOK_ID } from '../../react_embeddables/saved_book/constants';
import {
BookApi,
Expand All @@ -32,7 +31,6 @@ import { lastSavedStateSessionStorage } from './last_saved_state';
import { unsavedChangesSessionStorage } from './unsaved_changes';

export const StateManagementExample = ({ uiActions }: { uiActions: UiActionsStart }) => {
const isMounted = useMountedState();
const saveNotification$ = useMemo(() => {
return new Subject<void>();
}, []);
Expand Down Expand Up @@ -123,16 +121,13 @@ export const StateManagementExample = ({ uiActions }: { uiActions: UiActionsStar
<EuiFlexItem grow={false}>
<EuiButton
disabled={isSaving || !hasUnsavedChanges}
onClick={async () => {
onClick={() => {
if (!bookApi) {
return;
}

setIsSaving(true);
const bookSerializedState = await bookApi.serializeState();
if (!isMounted()) {
return;
}
const bookSerializedState = bookApi.serializeState();
lastSavedStateSessionStorage.save(bookSerializedState);
saveNotification$.next(); // signals embeddable unsaved change tracking to update last saved state
setHasUnsavedChanges(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { CoreStart } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import { apiCanAddNewPanel } from '@kbn/presentation-containers';
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
import { IncompatibleActionError, ADD_PANEL_TRIGGER } from '@kbn/ui-actions-plugin/public';
import { ADD_PANEL_TRIGGER, IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
import { UiActionsPublicStart } from '@kbn/ui-actions-plugin/public/plugin';
import { embeddableExamplesGrouping } from '../embeddable_examples_grouping';
import {
Expand All @@ -21,7 +21,6 @@ import {
} from './book_state';
import { ADD_SAVED_BOOK_ACTION_ID, SAVED_BOOK_ID } from './constants';
import { openSavedBookEditor } from './saved_book_editor';
import { saveBookAttributes } from './saved_book_library';
import { BookRuntimeState } from './types';

export const registerCreateSavedBookAction = (uiActions: UiActionsPublicStart, core: CoreStart) => {
Expand All @@ -36,19 +35,17 @@ export const registerCreateSavedBookAction = (uiActions: UiActionsPublicStart, c
if (!apiCanAddNewPanel(embeddable)) throw new IncompatibleActionError();
const newPanelStateManager = stateManagerFromAttributes(defaultBookAttributes);

const { addToLibrary } = await openSavedBookEditor(newPanelStateManager, true, core, {
parentApi: embeddable,
const { savedBookId } = await openSavedBookEditor({
attributesManager: newPanelStateManager,
parent: embeddable,
isCreate: true,
core,
});

const initialState: BookRuntimeState = await (async () => {
const bookAttributes = serializeBookAttributes(newPanelStateManager);
// if we're adding this to the library, we only need to return the by reference state.
if (addToLibrary) {
const savedBookId = await saveBookAttributes(undefined, bookAttributes);
return { savedBookId, ...bookAttributes };
}
return bookAttributes;
})();
const bookAttributes = serializeBookAttributes(newPanelStateManager);
const initialState: BookRuntimeState = savedBookId
? { savedBookId, ...bookAttributes }
: { ...bookAttributes };

embeddable.addNewPanel<BookRuntimeState>({
panelType: SAVED_BOOK_ID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,35 +27,42 @@ import { OverlayRef } from '@kbn/core-mount-utils-browser';
import { i18n } from '@kbn/i18n';
import { tracksOverlays } from '@kbn/presentation-containers';
import {
apiHasParentApi,
apiHasInPlaceLibraryTransforms,
apiHasUniqueId,
useBatchedOptionalPublishingSubjects,
} from '@kbn/presentation-publishing';
import { toMountPoint } from '@kbn/react-kibana-mount';
import React from 'react';
import React, { useState } from 'react';
import { serializeBookAttributes } from './book_state';
import { BookAttributesManager } from './types';
import { BookApi, BookAttributesManager } from './types';
import { saveBookAttributes } from './saved_book_library';

export const openSavedBookEditor = (
attributesManager: BookAttributesManager,
isCreate: boolean,
core: CoreStart,
api: unknown
): Promise<{ addToLibrary: boolean }> => {
export const openSavedBookEditor = ({
attributesManager,
isCreate,
core,
parent,
api,
}: {
attributesManager: BookAttributesManager;
isCreate: boolean;
core: CoreStart;
parent?: unknown;
api?: BookApi;
}): Promise<{ savedBookId?: string }> => {
return new Promise((resolve) => {
const closeOverlay = (overlayRef: OverlayRef) => {
if (apiHasParentApi(api) && tracksOverlays(api.parentApi)) {
api.parentApi.clearOverlays();
}
if (tracksOverlays(parent)) parent.clearOverlays();
overlayRef.close();
};

const initialState = serializeBookAttributes(attributesManager);
const overlay = core.overlays.openFlyout(
toMountPoint(
<SavedBookEditor
attributesManager={attributesManager}
api={api}
isCreate={isCreate}
attributesManager={attributesManager}
onCancel={() => {
// set the state back to the initial state and reject
attributesManager.authorName.next(initialState.authorName);
Expand All @@ -64,16 +71,23 @@ export const openSavedBookEditor = (
attributesManager.numberOfPages.next(initialState.numberOfPages);
closeOverlay(overlay);
}}
onSubmit={(addToLibrary: boolean) => {
onSubmit={async (addToLibrary: boolean) => {
const savedBookId = addToLibrary
? await saveBookAttributes(
apiHasInPlaceLibraryTransforms(api) ? api.libraryId$.value : undefined,
serializeBookAttributes(attributesManager)
)
: undefined;

closeOverlay(overlay);
resolve({ addToLibrary });
resolve({ savedBookId });
}}
/>,
core
),
{
type: isCreate ? 'overlay' : 'push',
size: isCreate ? 'm' : 's',
size: 'm',
onClose: () => closeOverlay(overlay),
}
);
Expand All @@ -83,9 +97,7 @@ export const openSavedBookEditor = (
* if our parent needs to know about the overlay, notify it. This allows the parent to close the overlay
* when navigating away, or change certain behaviors based on the overlay being open.
*/
if (apiHasParentApi(api) && tracksOverlays(api.parentApi)) {
api.parentApi.openOverlay(overlay, overlayOptions);
}
if (tracksOverlays(parent)) parent.openOverlay(overlay, overlayOptions);
});
};

Expand All @@ -94,19 +106,24 @@ export const SavedBookEditor = ({
isCreate,
onSubmit,
onCancel,
api,
}: {
attributesManager: BookAttributesManager;
isCreate: boolean;
onSubmit: (addToLibrary: boolean) => void;
onSubmit: (addToLibrary: boolean) => Promise<void>;
onCancel: () => void;
api?: BookApi;
}) => {
const [addToLibrary, setAddToLibrary] = React.useState(false);
const [authorName, synopsis, bookTitle, numberOfPages] = useBatchedOptionalPublishingSubjects(
attributesManager.authorName,
attributesManager.bookSynopsis,
attributesManager.bookTitle,
attributesManager.numberOfPages
);
const [libraryId, authorName, synopsis, bookTitle, numberOfPages] =
useBatchedOptionalPublishingSubjects(
api?.libraryId$,
attributesManager.authorName,
attributesManager.bookSynopsis,
attributesManager.bookTitle,
attributesManager.numberOfPages
);
const [addToLibrary, setAddToLibrary] = useState(Boolean(libraryId));
const [saving, setSaving] = useState(false);

return (
<>
Expand All @@ -130,6 +147,7 @@ export const SavedBookEditor = ({
})}
>
<EuiFieldText
disabled={saving}
value={authorName ?? ''}
onChange={(e) => attributesManager.authorName.next(e.target.value)}
/>
Expand All @@ -140,6 +158,7 @@ export const SavedBookEditor = ({
})}
>
<EuiFieldText
disabled={saving}
value={bookTitle ?? ''}
onChange={(e) => attributesManager.bookTitle.next(e.target.value)}
/>
Expand All @@ -150,6 +169,7 @@ export const SavedBookEditor = ({
})}
>
<EuiFieldNumber
disabled={saving}
value={numberOfPages ?? ''}
onChange={(e) => attributesManager.numberOfPages.next(+e.target.value)}
/>
Expand All @@ -160,6 +180,7 @@ export const SavedBookEditor = ({
})}
>
<EuiTextArea
disabled={saving}
value={synopsis ?? ''}
onChange={(e) => attributesManager.bookSynopsis.next(e.target.value)}
/>
Expand All @@ -168,27 +189,33 @@ export const SavedBookEditor = ({
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiButtonEmpty iconType="cross" onClick={onCancel} flush="left">
<EuiButtonEmpty disabled={saving} iconType="cross" onClick={onCancel} flush="left">
{i18n.translate('embeddableExamples.savedBook.editor.cancel', {
defaultMessage: 'Discard changes',
})}
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="m" alignItems="center" responsive={false}>
{isCreate && (
<EuiFlexItem grow={false}>
<EuiSwitch
label={i18n.translate('embeddableExamples.savedBook.editor.addToLibrary', {
defaultMessage: 'Save to library',
})}
checked={addToLibrary}
onChange={() => setAddToLibrary(!addToLibrary)}
/>
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<EuiButton onClick={() => onSubmit(addToLibrary)} fill>
<EuiSwitch
label={i18n.translate('embeddableExamples.savedBook.editor.addToLibrary', {
defaultMessage: 'Save to library',
})}
checked={addToLibrary}
disabled={saving}
onChange={() => setAddToLibrary(!addToLibrary)}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
isLoading={saving}
onClick={() => {
setSaving(true);
onSubmit(addToLibrary);
}}
fill
>
{isCreate
? i18n.translate('embeddableExamples.savedBook.editor.create', {
defaultMessage: 'Create book',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const saveBookAttributes = async (
maybeId?: string,
attributes?: BookAttributes
): Promise<string> => {
await new Promise((r) => setTimeout(r, 100)); // simulate save to network.
await new Promise((r) => setTimeout(r, 500)); // simulate save to network.
const id = maybeId ?? v4();
storage.set(id, attributes);
return id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,22 @@ export const getSavedBookEmbeddableFactory = (core: CoreStart) => {
{
...titlesApi,
onEdit: async () => {
openSavedBookEditor(bookAttributesManager, false, core, api);
openSavedBookEditor({
attributesManager: bookAttributesManager,
parent: api.parentApi,
isCreate: false,
core,
api,
}).then((result) => {
savedBookId$.next(result.savedBookId);
});
},
isEditingEnabled: () => true,
getTypeDisplayName: () =>
i18n.translate('embeddableExamples.savedbook.editBook.displayName', {
defaultMessage: 'book',
}),
serializeState: async () => {
serializeState: () => {
if (!Boolean(savedBookId$.value)) {
// if this book is currently by value, we serialize the entire state.
const bookByValueState: BookByValueSerializedState = {
Expand All @@ -98,16 +106,11 @@ export const getSavedBookEmbeddableFactory = (core: CoreStart) => {
return { rawState: bookByValueState };
}

// if this book is currently by reference, we serialize the reference and write to the external store.
// if this book is currently by reference, we serialize the reference only.
const bookByReferenceState: BookByReferenceSerializedState = {
savedBookId: savedBookId$.value!,
...serializeTitles(),
};

await saveBookAttributes(
savedBookId$.value,
serializeBookAttributes(bookAttributesManager)
);
return { rawState: bookByReferenceState };
},

Expand Down
1 change: 0 additions & 1 deletion examples/embeddable_examples/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
"@kbn/kibana-utils-plugin",
"@kbn/core-mount-utils-browser",
"@kbn/react-kibana-mount",
"@kbn/std",
"@kbn/shared-ux-router"
]
}
Loading