Skip to content

Commit

Permalink
[Embeddables rebuild] Decouple add panel trigger (#176110)
Browse files Browse the repository at this point in the history
Decouples the `ADD_PANEL_TRIGGER` from the Embeddables framework  by making it take a `PresentationContainer` as context instead.
  • Loading branch information
ThomThomson authored Feb 7, 2024
1 parent 2735882 commit 23b4b53
Show file tree
Hide file tree
Showing 22 changed files with 391 additions and 310 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export class SimpleEmbeddableFactoryDefinition

public getDisplayName() {
return i18n.translate('embeddableExamples.migrations.displayName', {
defaultMessage: 'hello world',
defaultMessage: 'simple migration embeddable',
});
}
}
8 changes: 5 additions & 3 deletions examples/embeddable_examples/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import {
FilterDebuggerEmbeddableFactory,
FilterDebuggerEmbeddableFactoryDefinition,
} from './filter_debugger';
import { registerMarkdownEditorEmbeddable } from './react_embeddables/eui_markdown_react_embeddable';
import { registerMarkdownEditorEmbeddable } from './react_embeddables/eui_markdown/eui_markdown_react_embeddable';
import { registerCreateEuiMarkdownAction } from './react_embeddables/eui_markdown/create_eui_markdown_action';

export interface EmbeddableExamplesSetupDependencies {
embeddable: EmbeddableSetup;
Expand All @@ -54,8 +55,6 @@ export interface EmbeddableExamplesStart {
factories: ExampleEmbeddableFactories;
}

registerMarkdownEditorEmbeddable();

export class EmbeddableExamplesPlugin
implements
Plugin<
Expand All @@ -71,6 +70,9 @@ export class EmbeddableExamplesPlugin
core: CoreSetup<EmbeddableExamplesStartDependencies>,
deps: EmbeddableExamplesSetupDependencies
) {
registerMarkdownEditorEmbeddable();
registerCreateEuiMarkdownAction(deps.uiActions);

this.exampleEmbeddableFactories.getHelloWorldEmbeddableFactory =
deps.embeddable.registerEmbeddableFactory(
HELLO_WORLD_EMBEDDABLE,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/

export const EUI_MARKDOWN_ID = 'euiMarkdown';
export const ADD_EUI_MARKDOWN_ACTION_ID = 'create_eui_markdown';
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/

import { i18n } from '@kbn/i18n';
import { apiIsPresentationContainer } from '@kbn/presentation-containers';
import { EmbeddableApiContext } from '@kbn/presentation-publishing';
import { IncompatibleActionError, UiActionsStart } from '@kbn/ui-actions-plugin/public';
import { ADD_EUI_MARKDOWN_ACTION_ID, EUI_MARKDOWN_ID } from './constants';

// -----------------------------------------------------------------------------
// Create and register an action which allows this embeddable to be created from
// the dashboard toolbar context menu.
// -----------------------------------------------------------------------------
export const registerCreateEuiMarkdownAction = (uiActions: UiActionsStart) => {
uiActions.registerAction<EmbeddableApiContext>({
id: ADD_EUI_MARKDOWN_ACTION_ID,
getIconType: () => 'editorCodeBlock',
isCompatible: async ({ embeddable }) => {
return apiIsPresentationContainer(embeddable);
},
execute: async ({ embeddable }) => {
if (!apiIsPresentationContainer(embeddable)) throw new IncompatibleActionError();
embeddable.addNewPanel(
{
panelType: EUI_MARKDOWN_ID,
initialState: { content: '# hello world!' },
},
true
);
},
getDisplayName: () =>
i18n.translate('embeddableExamples.euiMarkdownEditor.ariaLabel', {
defaultMessage: 'EUI Markdown',
}),
});
uiActions.attachAction('ADD_PANEL_TRIGGER', ADD_EUI_MARKDOWN_ACTION_ID);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/

import { EuiMarkdownEditor, EuiMarkdownFormat } from '@elastic/eui';
import { css } from '@emotion/react';
import {
initializeReactEmbeddableTitles,
initializeReactEmbeddableUuid,
ReactEmbeddableFactory,
RegisterReactEmbeddable,
registerReactEmbeddableFactory,
useReactEmbeddableApiHandle,
useReactEmbeddableUnsavedChanges,
} from '@kbn/embeddable-plugin/public';
import { i18n } from '@kbn/i18n';
import { useInheritedViewMode, useStateFromPublishingSubject } from '@kbn/presentation-publishing';
import { euiThemeVars } from '@kbn/ui-theme';
import React from 'react';
import { BehaviorSubject } from 'rxjs';
import { EUI_MARKDOWN_ID } from './constants';
import { MarkdownEditorSerializedState, MarkdownEditorApi } from './types';

export const registerMarkdownEditorEmbeddable = () => {
const markdownEmbeddableFactory: ReactEmbeddableFactory<
MarkdownEditorSerializedState,
MarkdownEditorApi
> = {
deserializeState: (state) => {
/**
* Here we can run migrations and inject references.
*/
return state.rawState as MarkdownEditorSerializedState;
},
getComponent: async (state, maybeId) => {
/**
* initialize state (source of truth)
*/
const uuid = initializeReactEmbeddableUuid(maybeId);
const { titlesApi, titleComparators, serializeTitles } =
initializeReactEmbeddableTitles(state);
const contentSubject = new BehaviorSubject(state.content);

/**
* getComponent is async so you can async import the component or load a saved object here.
* the loading will be handed gracefully by the Presentation Container.
*/

return RegisterReactEmbeddable((apiRef) => {
/**
* Unsaved changes logic is handled automatically by this hook. You only need to provide
* a subject, setter, and optional state comparator for each key in your state type.
*/
const { unsavedChanges, resetUnsavedChanges } = useReactEmbeddableUnsavedChanges(
uuid,
markdownEmbeddableFactory,
{
content: [contentSubject, (value) => contentSubject.next(value)],
...titleComparators,
}
);

/**
* Publish the API. This is what gets forwarded to the Actions framework, and to whatever the
* parent of this embeddable is.
*/
const thisApi = useReactEmbeddableApiHandle(
{
...titlesApi,
unsavedChanges,
resetUnsavedChanges,
serializeState: async () => {
return {
rawState: {
...serializeTitles(),
content: contentSubject.getValue(),
},
};
},
},
apiRef,
uuid
);

// get state for rendering
const content = useStateFromPublishingSubject(contentSubject);
const viewMode = useInheritedViewMode(thisApi) ?? 'view';

return viewMode === 'edit' ? (
<EuiMarkdownEditor
css={css`
width: 100%;
`}
value={content ?? ''}
onChange={(value) => contentSubject.next(value)}
aria-label={i18n.translate('embeddableExamples.euiMarkdownEditor.ariaLabel', {
defaultMessage: 'Dashboard markdown editor',
})}
height="full"
/>
) : (
<EuiMarkdownFormat
css={css`
padding: ${euiThemeVars.euiSizeS};
`}
>
{content ?? ''}
</EuiMarkdownFormat>
);
});
},
};

/**
* Register the defined Embeddable Factory - notice that this isn't defined
* on the plugin. Instead, it's a simple imported function. I.E to register an
* embeddable, you only need the embeddable plugin in your requiredBundles
*/
registerReactEmbeddableFactory(EUI_MARKDOWN_ID, markdownEmbeddableFactory);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/

import {
DefaultEmbeddableApi,
SerializedReactEmbeddableTitles,
} from '@kbn/embeddable-plugin/public';

export type MarkdownEditorSerializedState = SerializedReactEmbeddableTitles & {
content: string;
};

export type MarkdownEditorApi = DefaultEmbeddableApi;

This file was deleted.

3 changes: 2 additions & 1 deletion examples/embeddable_examples/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@kbn/presentation-publishing",
"@kbn/ui-theme",
"@kbn/i18n",
"@kbn/es-query"
"@kbn/es-query",
"@kbn/presentation-containers"
]
}
Loading

0 comments on commit 23b4b53

Please sign in to comment.