frontend-plugin-api,app: move toast forwading to be internal
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -1938,7 +1938,6 @@ export const swappableComponentsApiRef: ApiRef_2<SwappableComponentsApi>;
|
||||
// @public
|
||||
export type ToastApi = {
|
||||
post(toast: ToastApiMessage): ToastApiPostResult;
|
||||
toast$(): Observable<ToastApiMessageWithKey>;
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -1946,14 +1945,14 @@ export type ToastApiMessage = {
|
||||
title: ReactNode;
|
||||
description?: ReactNode;
|
||||
status?: 'neutral' | 'info' | 'success' | 'warning' | 'danger';
|
||||
links?: ToastLink[];
|
||||
links?: ToastApiMessageLink[];
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type ToastApiMessageWithKey = ToastApiMessage & {
|
||||
key: string;
|
||||
close(): void;
|
||||
export type ToastApiMessageLink = {
|
||||
label: string;
|
||||
href: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -1964,12 +1963,6 @@ export type ToastApiPostResult = {
|
||||
// @public
|
||||
export const toastApiRef: ApiRef<ToastApi>;
|
||||
|
||||
// @public
|
||||
export type ToastLink = {
|
||||
label: string;
|
||||
href: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type TranslationApi = {
|
||||
getTranslation<
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import { createApiRef, ApiRef } from '../system';
|
||||
import { Observable } from '@backstage/types';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
/**
|
||||
@@ -23,7 +22,7 @@ import { ReactNode } from 'react';
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ToastLink = {
|
||||
export type ToastApiMessageLink = {
|
||||
/** Display text for the link */
|
||||
label: string;
|
||||
/** URL the link points to */
|
||||
@@ -43,7 +42,7 @@ export type ToastApiMessage = {
|
||||
/** Status variant of the toast - defaults to 'success' */
|
||||
status?: 'neutral' | 'info' | 'success' | 'warning' | 'danger';
|
||||
/** Optional array of links to display */
|
||||
links?: ToastLink[];
|
||||
links?: ToastApiMessageLink[];
|
||||
/** Timeout in milliseconds before auto-dismiss. If not set, toast is permanent. */
|
||||
timeout?: number;
|
||||
};
|
||||
@@ -59,25 +58,6 @@ export type ToastApiPostResult = {
|
||||
close(): void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Toast message with key, as emitted by the toast$() observable.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ToastApiMessageWithKey = ToastApiMessage & {
|
||||
/** Unique key for the toast, used internally for tracking */
|
||||
key: string;
|
||||
/** Dismiss this toast programmatically */
|
||||
close(): void;
|
||||
/**
|
||||
* Register a callback that fires when this toast is closed.
|
||||
* Used internally by the toast display to sync with the rendering queue.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
onClose(callback: () => void): void;
|
||||
};
|
||||
|
||||
/**
|
||||
* The toast API is used to display toast notifications to the user.
|
||||
*
|
||||
@@ -117,11 +97,6 @@ export type ToastApi = {
|
||||
* @returns A handle with a `close()` method to programmatically dismiss the toast
|
||||
*/
|
||||
post(toast: ToastApiMessage): ToastApiPostResult;
|
||||
|
||||
/**
|
||||
* Observe toasts posted by other parts of the application.
|
||||
*/
|
||||
toast$(): Observable<ToastApiMessageWithKey>;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -714,6 +714,21 @@ const appPlugin: OverridableFrontendPlugin<
|
||||
params: ApiFactory<TApi, TImpl, TDeps>,
|
||||
) => ExtensionBlueprintParams<AnyApiFactory>;
|
||||
}>;
|
||||
'api:app/toast-forwarder': OverridableExtensionDefinition<{
|
||||
kind: 'api';
|
||||
name: 'toast-forwarder';
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>;
|
||||
inputs: {};
|
||||
params: <
|
||||
TApi,
|
||||
TImpl extends TApi,
|
||||
TDeps extends { [name in string]: unknown },
|
||||
>(
|
||||
params: ApiFactory<TApi, TImpl, TDeps>,
|
||||
) => ExtensionBlueprintParams<AnyApiFactory>;
|
||||
}>;
|
||||
'api:app/translations': OverridableExtensionDefinition<{
|
||||
config: {};
|
||||
configInput: {};
|
||||
|
||||
@@ -15,13 +15,15 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
ToastApi,
|
||||
ToastApiMessage,
|
||||
ToastApiMessageWithKey,
|
||||
ToastApiPostResult,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { Observable } from '@backstage/types';
|
||||
import ObservableImpl from 'zen-observable';
|
||||
import {
|
||||
ToastApiForwarderApi,
|
||||
ToastApiForwarderMessage,
|
||||
} from './toastApiForwarderRef';
|
||||
|
||||
let toastKeyCounter = 0;
|
||||
|
||||
@@ -85,9 +87,9 @@ class PublishSubject<T> {
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export class ToastApiForwarder implements ToastApi {
|
||||
private readonly subject = new PublishSubject<ToastApiMessageWithKey>();
|
||||
private readonly recentToasts: ToastApiMessageWithKey[] = [];
|
||||
export class ToastApiForwarder implements ToastApiForwarderApi {
|
||||
private readonly subject = new PublishSubject<ToastApiForwarderMessage>();
|
||||
private readonly recentToasts: ToastApiForwarderMessage[] = [];
|
||||
private readonly closedKeys = new Set<string>();
|
||||
private readonly maxBufferSize = 10;
|
||||
|
||||
@@ -126,7 +128,7 @@ export class ToastApiForwarder implements ToastApi {
|
||||
}
|
||||
};
|
||||
|
||||
const toastWithKey: ToastApiMessageWithKey = {
|
||||
const toastWithKey: ToastApiForwarderMessage = {
|
||||
...toast,
|
||||
key,
|
||||
close,
|
||||
@@ -142,7 +144,7 @@ export class ToastApiForwarder implements ToastApi {
|
||||
return { close };
|
||||
}
|
||||
|
||||
toast$(): Observable<ToastApiMessageWithKey> {
|
||||
toast$(): Observable<ToastApiForwarderMessage> {
|
||||
// Filter out any toasts that were closed to handle race conditions
|
||||
const activeToasts = this.recentToasts.filter(
|
||||
t => !this.closedKeys.has(t.key),
|
||||
|
||||
@@ -15,3 +15,5 @@
|
||||
*/
|
||||
|
||||
export { ToastApiForwarder } from './ToastApiForwarder';
|
||||
export { toastApiForwarderRef } from './toastApiForwarderRef';
|
||||
export type { ToastApiForwarderApi } from './toastApiForwarderRef';
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
ToastApi,
|
||||
ToastApiMessage,
|
||||
createApiRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { Observable } from '@backstage/types';
|
||||
|
||||
/**
|
||||
* Internal enriched toast message emitted by the forwarder's observable.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export type ToastApiForwarderMessage = ToastApiMessage & {
|
||||
/** Unique key for tracking this toast */
|
||||
key: string;
|
||||
/** Dismiss this toast programmatically */
|
||||
close(): void;
|
||||
/**
|
||||
* Register a callback that fires when this toast is closed.
|
||||
* Used by the toast display to sync with the rendering queue.
|
||||
*/
|
||||
onClose(callback: () => void): void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal extension of the public ToastApi that adds an observable
|
||||
* for the toast display to subscribe to. This interface is only used
|
||||
* within the app plugin and can be freely evolved.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export type ToastApiForwarderApi = ToastApi & {
|
||||
/** Observe toasts posted by other parts of the application. */
|
||||
toast$(): Observable<ToastApiForwarderMessage>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal API ref for the toast forwarder. The public `toastApiRef`
|
||||
* delegates to this, and the toast display subscribes to it directly.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export const toastApiForwarderRef = createApiRef<ToastApiForwarderApi>({
|
||||
id: 'app.toast.internal-forwarder',
|
||||
});
|
||||
@@ -23,11 +23,10 @@ import {
|
||||
appThemeApiRef,
|
||||
AppThemeApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { toastApiRef } from '@backstage/frontend-plugin-api';
|
||||
import { Observable } from '@backstage/types';
|
||||
import ObservableImpl from 'zen-observable';
|
||||
import { ToastDisplay } from './ToastDisplay';
|
||||
import { ToastApiForwarder } from '../../apis';
|
||||
import { ToastApiForwarder, toastApiForwarderRef } from '../../apis';
|
||||
|
||||
// Mock AlertApi with proper Observable implementation
|
||||
class MockAlertApi implements AlertApi {
|
||||
@@ -88,7 +87,7 @@ describe('ToastDisplay', () => {
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[alertApiRef, alertApi],
|
||||
[toastApiRef, toastApi],
|
||||
[toastApiForwarderRef, toastApi],
|
||||
[appThemeApiRef, mockAppThemeApi],
|
||||
]}
|
||||
>
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { alertApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { toastApiRef } from '@backstage/frontend-plugin-api';
|
||||
import { ToastQueue } from '@react-stately/toast';
|
||||
import { ToastContainer } from './ToastContainer';
|
||||
import { toastApiForwarderRef } from '../../apis';
|
||||
import type {
|
||||
ToastApiMessageDisplayProps,
|
||||
ToastApiMessageContent,
|
||||
@@ -85,7 +85,7 @@ function mapSeverity(
|
||||
*/
|
||||
export function ToastDisplay(props: ToastApiMessageDisplayProps) {
|
||||
const alertApi = useApi(alertApiRef);
|
||||
const toastApi = useApi(toastApiRef);
|
||||
const toastApi = useApi(toastApiForwarderRef);
|
||||
const { transientTimeoutMs = 5000 } = props;
|
||||
|
||||
// Create toast queue once per component instance
|
||||
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
VMwareCloudAuth,
|
||||
OpenShiftAuth,
|
||||
} from '../../../packages/core-app-api/src/apis/implementations';
|
||||
import { ToastApiForwarder } from './apis';
|
||||
import { ToastApiForwarder, toastApiForwarderRef } from './apis';
|
||||
|
||||
import {
|
||||
alertApiRef,
|
||||
@@ -108,13 +108,22 @@ export const apis = [
|
||||
factory: () => new AlertApiForwarder(),
|
||||
}),
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'toast-forwarder',
|
||||
params: defineParams =>
|
||||
defineParams({
|
||||
api: toastApiForwarderRef,
|
||||
deps: {},
|
||||
factory: () => new ToastApiForwarder(),
|
||||
}),
|
||||
}),
|
||||
ApiBlueprint.make({
|
||||
name: 'toast',
|
||||
params: defineParams =>
|
||||
defineParams({
|
||||
api: toastApiRef,
|
||||
deps: {},
|
||||
factory: () => new ToastApiForwarder(),
|
||||
deps: { forwarder: toastApiForwarderRef },
|
||||
factory: ({ forwarder }) => forwarder,
|
||||
}),
|
||||
}),
|
||||
analyticsApi,
|
||||
|
||||
Reference in New Issue
Block a user