mirror of
https://github.com/apache/superset.git
synced 2026-05-05 07:54:17 +00:00
Compare commits
12 Commits
fix-webpac
...
msyavuz/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d200b24ed2 | ||
|
|
e1678fd5c7 | ||
|
|
818a089412 | ||
|
|
ef37a06e43 | ||
|
|
80dd5f8e6d | ||
|
|
ca1ad9282b | ||
|
|
3bf2c3a536 | ||
|
|
e7b3ab7f32 | ||
|
|
afeee08b49 | ||
|
|
c5ba4ab530 | ||
|
|
e9400222aa | ||
|
|
076e477fd4 |
@@ -16,9 +16,11 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Popover, type PopoverProps } from '@superset-ui/core/components';
|
||||
import type ReactAce from 'react-ace';
|
||||
import {
|
||||
Popover,
|
||||
type PopoverProps,
|
||||
SQLEditor,
|
||||
} from '@superset-ui/core/components';
|
||||
import { CalculatorOutlined } from '@ant-design/icons';
|
||||
import { css, styled, useTheme, t } from '@superset-ui/core';
|
||||
|
||||
@@ -35,24 +37,10 @@ const StyledCalculatorIcon = styled(CalculatorOutlined)`
|
||||
|
||||
export const SQLPopover = (props: PopoverProps & { sqlExpression: string }) => {
|
||||
const theme = useTheme();
|
||||
const [AceEditor, setAceEditor] = useState<typeof ReactAce | null>(null);
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
import('react-ace'),
|
||||
import('ace-builds/src-min-noconflict/mode-sql'),
|
||||
]).then(([reactAceModule]) => {
|
||||
setAceEditor(() => reactAceModule.default);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (!AceEditor) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Popover
|
||||
content={
|
||||
<AceEditor
|
||||
mode="sql"
|
||||
<SQLEditor
|
||||
value={props.sqlExpression}
|
||||
editorProps={{ $blockScrolling: true }}
|
||||
setOptions={{
|
||||
@@ -65,7 +53,6 @@ export const SQLPopover = (props: PopoverProps & { sqlExpression: string }) => {
|
||||
wrapEnabled
|
||||
style={{
|
||||
border: `1px solid ${theme.colorBorder}`,
|
||||
background: theme.colorPrimaryBg,
|
||||
maxWidth: theme.sizeUnit * 100,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -161,7 +161,6 @@ export {
|
||||
export { Image, type ImageProps } from './Image';
|
||||
export { Popconfirm, type PopconfirmProps } from './Popconfirm';
|
||||
export { Upload, type UploadFile, type UploadChangeParam } from './Upload';
|
||||
// Add these to your index.ts
|
||||
export * from './Menu';
|
||||
export * from './Popover';
|
||||
export * from './Radio';
|
||||
|
||||
@@ -44,6 +44,7 @@ import { configureStore, Store } from '@reduxjs/toolkit';
|
||||
import { api } from 'src/hooks/apiResources/queryApi';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { ExtensionsProvider } from 'src/extensions/ExtensionsContext';
|
||||
import { NotificationProvider } from 'src/components/MessageToasts/NotificationProvider';
|
||||
|
||||
type Options = Omit<RenderOptions, 'queries'> & {
|
||||
useRedux?: boolean;
|
||||
@@ -87,7 +88,9 @@ export function createWrapper(options?: Options) {
|
||||
return ({ children }: { children?: ReactNode }) => {
|
||||
let result = (
|
||||
<ThemeProvider theme={supersetTheme}>
|
||||
<ExtensionsProvider>{children}</ExtensionsProvider>
|
||||
<ExtensionsProvider>
|
||||
<NotificationProvider>{children}</NotificationProvider>
|
||||
</ExtensionsProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 { notification as antdNotification } from 'antd';
|
||||
import { createContext, useContext, useMemo, ReactNode } from 'react';
|
||||
import type { NotificationInstance } from 'antd/es/notification/interface';
|
||||
|
||||
export type NotificationType = 'success' | 'info' | 'warning' | 'error';
|
||||
|
||||
export type NotificationPlacement =
|
||||
| 'top'
|
||||
| 'topLeft'
|
||||
| 'topRight'
|
||||
| 'bottom'
|
||||
| 'bottomLeft'
|
||||
| 'bottomRight';
|
||||
|
||||
export interface NotificationArgsProps {
|
||||
message: ReactNode;
|
||||
description?: ReactNode;
|
||||
btn?: ReactNode;
|
||||
key?: string;
|
||||
onClose?: () => void;
|
||||
duration?: number | null;
|
||||
icon?: ReactNode;
|
||||
placement?: NotificationPlacement;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
closeIcon?: boolean | ReactNode;
|
||||
role?: 'alert' | 'status';
|
||||
}
|
||||
|
||||
export type NotificationApi = NotificationInstance;
|
||||
|
||||
export interface NotificationContextType {
|
||||
api: NotificationApi;
|
||||
success: (args: NotificationArgsProps) => void;
|
||||
error: (args: NotificationArgsProps) => void;
|
||||
warning: (args: NotificationArgsProps) => void;
|
||||
info: (args: NotificationArgsProps) => void;
|
||||
open: (args: NotificationArgsProps & { type?: NotificationType }) => void;
|
||||
destroy: (key?: string) => void;
|
||||
}
|
||||
|
||||
const NotificationContext = createContext<NotificationContextType | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
export const NotificationProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [api, contextHolder] = antdNotification.useNotification();
|
||||
|
||||
const value = useMemo<NotificationContextType>(
|
||||
() => ({
|
||||
api,
|
||||
success: api.success,
|
||||
error: api.error,
|
||||
warning: api.warning,
|
||||
info: api.info,
|
||||
open: api.open,
|
||||
destroy: api.destroy,
|
||||
}),
|
||||
[api],
|
||||
);
|
||||
|
||||
return (
|
||||
<NotificationContext.Provider value={value}>
|
||||
{contextHolder}
|
||||
{children}
|
||||
</NotificationContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useNotification = (): NotificationContextType => {
|
||||
const context = useContext(NotificationContext);
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useNotification must be used within a NotificationProvider',
|
||||
);
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 { fireEvent, render, waitFor } from 'spec/helpers/testing-library';
|
||||
import Toast from 'src/components/MessageToasts/Toast';
|
||||
import mockMessageToasts from './mockMessageToasts';
|
||||
|
||||
const props = {
|
||||
toast: mockMessageToasts[0],
|
||||
onCloseToast() {},
|
||||
};
|
||||
|
||||
const setup = overrideProps => render(<Toast {...props} {...overrideProps} />);
|
||||
|
||||
test('should render', () => {
|
||||
const { getByTestId } = setup();
|
||||
expect(getByTestId('toast-container')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render toastText within the div', () => {
|
||||
const { getByTestId } = setup();
|
||||
expect(getByTestId('toast-container')).toHaveTextContent(props.toast.text);
|
||||
});
|
||||
|
||||
test('should call onCloseToast upon toast dismissal', async () => {
|
||||
const onCloseToast = jest.fn();
|
||||
const { getByTestId } = setup({ onCloseToast });
|
||||
fireEvent.click(getByTestId('close-button'));
|
||||
await waitFor(() => expect(onCloseToast).toHaveBeenCalledTimes(1));
|
||||
expect(onCloseToast).toHaveBeenCalledWith(props.toast.id);
|
||||
});
|
||||
@@ -1,150 +0,0 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 { styled, css, SupersetTheme, useTheme } from '@superset-ui/core';
|
||||
import cx from 'classnames';
|
||||
import { Interweave } from 'interweave';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Icons } from '@superset-ui/core/components/Icons';
|
||||
import { ToastType, ToastMeta } from './types';
|
||||
|
||||
const ToastContainer = styled.div`
|
||||
${({ theme }) => css`
|
||||
display: flex;
|
||||
justify-content: space-between; // Changed from center to space-between
|
||||
align-items: center;
|
||||
|
||||
// Content container for icon and text
|
||||
.toast__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1; // Take available space
|
||||
}
|
||||
|
||||
.anticon {
|
||||
padding: 0 ${theme.sizeUnit}px;
|
||||
}
|
||||
|
||||
.toast__close,
|
||||
.toast__close span {
|
||||
padding-left: ${theme.sizeUnit * 4}px;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const notificationStyledIcon = (theme: SupersetTheme) => css`
|
||||
min-width: ${theme.sizeUnit * 5}px;
|
||||
color: ${theme.colorTextLightSolid};
|
||||
margin-right: 0;
|
||||
`;
|
||||
|
||||
interface ToastPresenterProps {
|
||||
toast: ToastMeta;
|
||||
onCloseToast: (id: string) => void;
|
||||
}
|
||||
|
||||
export default function Toast({ toast, onCloseToast }: ToastPresenterProps) {
|
||||
const hideTimer = useRef<ReturnType<typeof setTimeout>>();
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const showToast = () => {
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
const handleClosePress = useCallback(() => {
|
||||
if (hideTimer.current) {
|
||||
clearTimeout(hideTimer.current);
|
||||
}
|
||||
// Wait for the transition
|
||||
setVisible(() => {
|
||||
setTimeout(() => {
|
||||
onCloseToast(toast.id);
|
||||
}, 150);
|
||||
return false;
|
||||
});
|
||||
}, [onCloseToast, toast.id]);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(showToast);
|
||||
if (toast.duration > 0) {
|
||||
hideTimer.current = setTimeout(handleClosePress, toast.duration);
|
||||
}
|
||||
return () => {
|
||||
if (hideTimer.current) {
|
||||
clearTimeout(hideTimer.current);
|
||||
}
|
||||
};
|
||||
}, [handleClosePress, toast.duration]);
|
||||
|
||||
const theme = useTheme();
|
||||
let className = 'toast--success';
|
||||
let icon = (
|
||||
<Icons.CheckCircleFilled
|
||||
css={theme => notificationStyledIcon(theme)}
|
||||
iconColor={theme.colorSuccess}
|
||||
/>
|
||||
);
|
||||
|
||||
if (toast.toastType === ToastType.Warning) {
|
||||
icon = (
|
||||
<Icons.ExclamationCircleFilled
|
||||
css={notificationStyledIcon}
|
||||
iconColor={theme.colorWarning}
|
||||
/>
|
||||
);
|
||||
className = 'toast--warning';
|
||||
} else if (toast.toastType === ToastType.Danger) {
|
||||
icon = (
|
||||
<Icons.ExclamationCircleFilled
|
||||
css={notificationStyledIcon}
|
||||
iconColor={theme.colorError}
|
||||
/>
|
||||
);
|
||||
className = 'toast--danger';
|
||||
} else if (toast.toastType === ToastType.Info) {
|
||||
icon = (
|
||||
<Icons.InfoCircleFilled
|
||||
css={notificationStyledIcon}
|
||||
iconColor={theme.colorInfo}
|
||||
/>
|
||||
);
|
||||
className = 'toast--info';
|
||||
}
|
||||
|
||||
return (
|
||||
<ToastContainer
|
||||
className={cx('alert', 'toast', visible && 'toast--visible', className)}
|
||||
data-test="toast-container"
|
||||
role="alert"
|
||||
>
|
||||
<div className="toast__content">
|
||||
{icon}
|
||||
<Interweave content={toast.text} noHtml={!toast.allowHtml} />
|
||||
</div>
|
||||
<Icons.CloseOutlined
|
||||
iconSize="m"
|
||||
className="toast__close pointer"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={handleClosePress}
|
||||
aria-label="Close"
|
||||
data-test="close-button"
|
||||
/>
|
||||
</ToastContainer>
|
||||
);
|
||||
}
|
||||
@@ -16,11 +16,85 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import ToastPresenter from './ToastPresenter';
|
||||
|
||||
import { Interweave } from 'interweave';
|
||||
import { useNotification } from './NotificationProvider';
|
||||
import { removeToast } from './actions';
|
||||
import { ToastMeta, ToastType } from './types';
|
||||
|
||||
interface ToastContainerProps {
|
||||
toasts: ToastMeta[];
|
||||
removeToast: (id: string) => void;
|
||||
}
|
||||
|
||||
function ToastPresenter({ toasts, removeToast }: ToastContainerProps) {
|
||||
const notification = useNotification();
|
||||
const toastKeyMap = useRef(new Map<string, string>());
|
||||
const displayedToasts = useRef(new Set<string>());
|
||||
|
||||
useEffect(() => {
|
||||
// Process new toasts
|
||||
toasts.forEach(toast => {
|
||||
if (!displayedToasts.current.has(toast.id)) {
|
||||
displayedToasts.current.add(toast.id);
|
||||
|
||||
const notificationKey = `toast-${toast.id}`;
|
||||
toastKeyMap.current.set(toast.id, notificationKey);
|
||||
|
||||
const message = toast.allowHtml ? (
|
||||
<Interweave content={toast.text} />
|
||||
) : (
|
||||
toast.text
|
||||
);
|
||||
|
||||
const config = {
|
||||
key: notificationKey,
|
||||
message,
|
||||
duration: toast.duration > 0 ? toast.duration / 1000 : 0,
|
||||
placement: 'bottomRight' as const,
|
||||
onClose: () => {
|
||||
toastKeyMap.current.delete(toast.id);
|
||||
displayedToasts.current.delete(toast.id);
|
||||
removeToast(toast.id);
|
||||
},
|
||||
};
|
||||
|
||||
switch (toast.toastType) {
|
||||
case ToastType.Success:
|
||||
notification.success(config);
|
||||
break;
|
||||
case ToastType.Warning:
|
||||
notification.warning(config);
|
||||
break;
|
||||
case ToastType.Danger:
|
||||
notification.error(config);
|
||||
break;
|
||||
case ToastType.Info:
|
||||
default:
|
||||
notification.info(config);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up removed toasts
|
||||
const currentToastIds = new Set(toasts.map(t => t.id));
|
||||
displayedToasts.current.forEach(id => {
|
||||
if (!currentToastIds.has(id)) {
|
||||
const key = toastKeyMap.current.get(id);
|
||||
if (key) {
|
||||
notification.destroy(key);
|
||||
toastKeyMap.current.delete(id);
|
||||
}
|
||||
displayedToasts.current.delete(id);
|
||||
}
|
||||
});
|
||||
}, [toasts, removeToast, notification]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const ToastContainer = connect(
|
||||
({ messageToasts: toasts }: any) => ({ toasts }),
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 { fireEvent, render, waitFor } from 'spec/helpers/testing-library';
|
||||
|
||||
import ToastPresenter from 'src/components/MessageToasts/ToastPresenter';
|
||||
import mockMessageToasts from './mockMessageToasts';
|
||||
|
||||
const props = {
|
||||
toasts: mockMessageToasts,
|
||||
removeToast() {},
|
||||
};
|
||||
|
||||
function setup(overrideProps) {
|
||||
return render(<ToastPresenter {...props} {...overrideProps} />);
|
||||
}
|
||||
|
||||
test('should render a div with id toast-presenter', () => {
|
||||
const { container } = setup();
|
||||
expect(container.querySelector('#toast-presenter')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render a Toast for each toast object', () => {
|
||||
const { getAllByRole } = setup();
|
||||
expect(getAllByRole('alert')).toHaveLength(props.toasts.length);
|
||||
});
|
||||
|
||||
test('should pass removeToast to the Toast component', async () => {
|
||||
const removeToast = jest.fn();
|
||||
const { getAllByTestId } = setup({ removeToast });
|
||||
fireEvent.click(getAllByTestId('close-button')[0]);
|
||||
await waitFor(() => expect(removeToast).toHaveBeenCalledTimes(1));
|
||||
});
|
||||
@@ -1,98 +0,0 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 { styled } from '@superset-ui/core';
|
||||
import { ToastMeta } from 'src/components/MessageToasts/types';
|
||||
import Toast from './Toast';
|
||||
|
||||
export interface VisualProps {
|
||||
position: 'bottom' | 'top';
|
||||
}
|
||||
|
||||
const StyledToastPresenter = styled.div<VisualProps>(
|
||||
({ theme, position }) =>
|
||||
// Single access to theme, using dot notation
|
||||
`
|
||||
max-width: 600px;
|
||||
position: fixed;
|
||||
${position === 'bottom' ? 'bottom' : 'top'}: 0px;
|
||||
right: 0px;
|
||||
margin-right: 50px;
|
||||
margin-bottom: 50px;
|
||||
z-index: ${theme.zIndexPopupBase + 1};
|
||||
word-break: break-word;
|
||||
|
||||
.toast {
|
||||
padding: ${theme.sizeUnit * 4}px;
|
||||
margin: ${theme.sizeUnit * 4}px;
|
||||
background: ${theme.colorBgSpotlight};
|
||||
border-radius: ${theme.borderRadius}px;
|
||||
box-shadow: ${theme.boxShadow};
|
||||
color: ${theme.colorTextLightSolid};
|
||||
opacity: 0;
|
||||
position: relative;
|
||||
transform: translateY(-100%);
|
||||
white-space: pre-line;
|
||||
will-change: transform, opacity;
|
||||
transition:
|
||||
transform ${theme.motionDurationMid},
|
||||
opacity ${theme.motionDurationMid};
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 6px;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.toast > button {
|
||||
color: ${theme.colorTextLightSolid};
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.toast--visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
`,
|
||||
);
|
||||
|
||||
type ToastPresenterProps = Partial<VisualProps> & {
|
||||
toasts: Array<ToastMeta>;
|
||||
removeToast: () => any;
|
||||
};
|
||||
|
||||
export default function ToastPresenter({
|
||||
toasts,
|
||||
removeToast,
|
||||
position = 'bottom',
|
||||
}: ToastPresenterProps) {
|
||||
return (
|
||||
<>
|
||||
{toasts.length > 0 && (
|
||||
<StyledToastPresenter id="toast-presenter" position={position}>
|
||||
{toasts.map(toast => (
|
||||
<Toast key={toast.id} toast={toast} onCloseToast={removeToast} />
|
||||
))}
|
||||
</StyledToastPresenter>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -17,9 +17,7 @@
|
||||
* under the License.
|
||||
*/
|
||||
import { nanoid } from 'nanoid';
|
||||
import { ToastType, ToastMeta } from './types';
|
||||
|
||||
type ToastOptions = Partial<Omit<ToastMeta, 'id' | 'toastType' | 'text'>>;
|
||||
import { ToastType, ToastMeta, ToastOptions } from './types';
|
||||
|
||||
export function getToastUuid(type: ToastType) {
|
||||
return `${type}-${nanoid()}`;
|
||||
|
||||
@@ -33,4 +33,13 @@ export interface ToastMeta {
|
||||
noDuplicate?: boolean;
|
||||
/** For security reasons, HTML rendering is disabled by default. Use this property to enable it. */
|
||||
allowHtml?: boolean;
|
||||
description?: string;
|
||||
}
|
||||
export type ToastOptions = Partial<
|
||||
Omit<ToastMeta, 'id' | 'toastType' | 'text'>
|
||||
>;
|
||||
|
||||
export type ToastTriggerFunction = (
|
||||
message: string,
|
||||
options?: ToastOptions,
|
||||
) => void;
|
||||
|
||||
@@ -23,6 +23,7 @@ import { QueryParamProvider } from 'use-query-params';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { FlashProvider, DynamicPluginProvider } from 'src/components';
|
||||
import { NotificationProvider } from 'src/components/MessageToasts/NotificationProvider';
|
||||
import { EmbeddedUiConfigProvider } from 'src/components/UiConfigContext';
|
||||
import { SupersetThemeProvider } from 'src/theme/ThemeProvider';
|
||||
import { ThemeController } from 'src/theme/ThemeController';
|
||||
@@ -71,18 +72,20 @@ export const EmbeddedContextProviders: React.FC = ({ children }) => {
|
||||
<FlashProvider messages={common.flash_messages}>
|
||||
<EmbeddedUiConfigProvider>
|
||||
<DynamicPluginProvider>
|
||||
<QueryParamProvider
|
||||
ReactRouterRoute={Route}
|
||||
stringifyOptions={{ encode: false }}
|
||||
>
|
||||
{RootContextProviderExtension ? (
|
||||
<RootContextProviderExtension>
|
||||
{children}
|
||||
</RootContextProviderExtension>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</QueryParamProvider>
|
||||
<NotificationProvider>
|
||||
<QueryParamProvider
|
||||
ReactRouterRoute={Route}
|
||||
stringifyOptions={{ encode: false }}
|
||||
>
|
||||
{RootContextProviderExtension ? (
|
||||
<RootContextProviderExtension>
|
||||
{children}
|
||||
</RootContextProviderExtension>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</QueryParamProvider>
|
||||
</NotificationProvider>
|
||||
</DynamicPluginProvider>
|
||||
</EmbeddedUiConfigProvider>
|
||||
</FlashProvider>
|
||||
|
||||
@@ -96,7 +96,7 @@ const EmbeddedRoute = () => (
|
||||
<ErrorBoundary>
|
||||
<EmbededLazyDashboardPage />
|
||||
</ErrorBoundary>
|
||||
<ToastContainer position="top" />
|
||||
<ToastContainer />
|
||||
</Suspense>
|
||||
</EmbeddedContextProviders>
|
||||
);
|
||||
|
||||
@@ -59,6 +59,7 @@ import {
|
||||
unsetSystemDefaultTheme,
|
||||
unsetSystemDarkTheme,
|
||||
} from 'src/features/themes/api';
|
||||
import { ToastTriggerFunction } from 'src/components/MessageToasts/types';
|
||||
|
||||
const PAGE_SIZE = 25;
|
||||
|
||||
@@ -79,8 +80,8 @@ const CONFIRM_OVERWRITE_MESSAGE = t(
|
||||
);
|
||||
|
||||
interface ThemesListProps {
|
||||
addDangerToast: (msg: string) => void;
|
||||
addSuccessToast: (msg: string) => void;
|
||||
addDangerToast: ToastTriggerFunction;
|
||||
addSuccessToast: ToastTriggerFunction;
|
||||
user: {
|
||||
userId: string | number;
|
||||
firstName: string;
|
||||
|
||||
@@ -25,6 +25,7 @@ import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import getBootstrapData from 'src/utils/getBootstrapData';
|
||||
import { FlashProvider, DynamicPluginProvider } from 'src/components';
|
||||
import { NotificationProvider } from 'src/components/MessageToasts/NotificationProvider';
|
||||
import { EmbeddedUiConfigProvider } from 'src/components/UiConfigContext';
|
||||
import { SupersetThemeProvider } from 'src/theme/ThemeProvider';
|
||||
import { ThemeController } from 'src/theme/ThemeController';
|
||||
@@ -48,20 +49,22 @@ export const RootContextProviders: React.FC = ({ children }) => {
|
||||
<FlashProvider messages={common.flash_messages}>
|
||||
<EmbeddedUiConfigProvider>
|
||||
<DynamicPluginProvider>
|
||||
<QueryParamProvider
|
||||
ReactRouterRoute={Route}
|
||||
stringifyOptions={{ encode: false }}
|
||||
>
|
||||
<ExtensionsProvider>
|
||||
{RootContextProviderExtension ? (
|
||||
<RootContextProviderExtension>
|
||||
{children}
|
||||
</RootContextProviderExtension>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</ExtensionsProvider>
|
||||
</QueryParamProvider>
|
||||
<NotificationProvider>
|
||||
<QueryParamProvider
|
||||
ReactRouterRoute={Route}
|
||||
stringifyOptions={{ encode: false }}
|
||||
>
|
||||
<ExtensionsProvider>
|
||||
{RootContextProviderExtension ? (
|
||||
<RootContextProviderExtension>
|
||||
{children}
|
||||
</RootContextProviderExtension>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</ExtensionsProvider>
|
||||
</QueryParamProvider>
|
||||
</NotificationProvider>
|
||||
</DynamicPluginProvider>
|
||||
</EmbeddedUiConfigProvider>
|
||||
</FlashProvider>
|
||||
|
||||
@@ -29,6 +29,7 @@ import createCache from '@emotion/cache';
|
||||
import { ThemeProvider, theme } from '@superset-ui/core';
|
||||
import Menu from 'src/features/home/Menu';
|
||||
import getBootstrapData from 'src/utils/getBootstrapData';
|
||||
import { NotificationProvider } from 'src/components/MessageToasts/NotificationProvider';
|
||||
import { setupStore } from './store';
|
||||
|
||||
// Disable connecting to redux debugger so that the React app injected
|
||||
@@ -45,16 +46,18 @@ const app = (
|
||||
// @ts-ignore: emotion types defs are incompatible between core and cache
|
||||
<CacheProvider value={emotionCache}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<Provider store={store}>
|
||||
<BrowserRouter>
|
||||
<QueryParamProvider
|
||||
ReactRouterRoute={Route}
|
||||
stringifyOptions={{ encode: false }}
|
||||
>
|
||||
<Menu data={menu} />
|
||||
</QueryParamProvider>
|
||||
</BrowserRouter>
|
||||
</Provider>
|
||||
<NotificationProvider>
|
||||
<Provider store={store}>
|
||||
<BrowserRouter>
|
||||
<QueryParamProvider
|
||||
ReactRouterRoute={Route}
|
||||
stringifyOptions={{ encode: false }}
|
||||
>
|
||||
<Menu data={menu} />
|
||||
</QueryParamProvider>
|
||||
</BrowserRouter>
|
||||
</Provider>
|
||||
</NotificationProvider>
|
||||
</ThemeProvider>
|
||||
</CacheProvider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user