feat: Notification provider and service (#1703)
* Create basic structure for Viewport dialog provier and Dialog component * Implementation of a UIViewportDialogService * Update viewportDialogProvider docz * Create example of use of UIViewportDialogService
This commit is contained in:
parent
cee09fcf8d
commit
e633eb80eb
@ -22,10 +22,11 @@ import errorHandler from './errorHandler.js';
|
|||||||
import utils, { hotkeys } from './utils/';
|
import utils, { hotkeys } from './utils/';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
UINotificationService,
|
|
||||||
UIModalService,
|
|
||||||
UIDialogService,
|
|
||||||
MeasurementService,
|
MeasurementService,
|
||||||
|
UIDialogService,
|
||||||
|
UIModalService,
|
||||||
|
UINotificationService,
|
||||||
|
UIViewportDialogService,
|
||||||
} from './services';
|
} from './services';
|
||||||
|
|
||||||
const OHIF = {
|
const OHIF = {
|
||||||
@ -56,9 +57,10 @@ const OHIF = {
|
|||||||
measurements,
|
measurements,
|
||||||
hangingProtocols,
|
hangingProtocols,
|
||||||
//
|
//
|
||||||
UINotificationService,
|
|
||||||
UIModalService,
|
|
||||||
UIDialogService,
|
UIDialogService,
|
||||||
|
UIModalService,
|
||||||
|
UINotificationService,
|
||||||
|
UIViewportDialogService,
|
||||||
MeasurementService,
|
MeasurementService,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -89,9 +91,10 @@ export {
|
|||||||
measurements,
|
measurements,
|
||||||
hangingProtocols,
|
hangingProtocols,
|
||||||
//
|
//
|
||||||
UINotificationService,
|
|
||||||
UIModalService,
|
|
||||||
UIDialogService,
|
UIDialogService,
|
||||||
|
UIModalService,
|
||||||
|
UINotificationService,
|
||||||
|
UIViewportDialogService,
|
||||||
MeasurementService,
|
MeasurementService,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
99
platform/core/src/services/UIViewportDialogService/index.js
Normal file
99
platform/core/src/services/UIViewportDialogService/index.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/**
|
||||||
|
* Viewport Dialog
|
||||||
|
*
|
||||||
|
* @typedef {Object} ViewportDialogProps
|
||||||
|
* @property {ReactElement|HTMLElement} [content=null] Modal content.
|
||||||
|
* @property {Object} [contentProps=null] Modal content props.
|
||||||
|
* @property {boolean} [viewportIndex=false] Modal is dismissible via the esc key.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const name = 'UIViewportDialogService';
|
||||||
|
|
||||||
|
const publicAPI = {
|
||||||
|
name,
|
||||||
|
hide: _hide,
|
||||||
|
show: _show,
|
||||||
|
setServiceImplementation,
|
||||||
|
};
|
||||||
|
|
||||||
|
const serviceImplementation = {
|
||||||
|
_viewports: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a new UI viewport dialog on the specified viewportIndex;
|
||||||
|
*
|
||||||
|
* @param {ViewportDialogProps} props { content, contentProps, viewportIndex }
|
||||||
|
*/
|
||||||
|
function _show({ content = null, contentProps = null, viewportIndex }) {
|
||||||
|
const viewportIndexImplementation =
|
||||||
|
(viewportIndex !== undefined &&
|
||||||
|
serviceImplementation._viewports[viewportIndex]) ||
|
||||||
|
{};
|
||||||
|
|
||||||
|
if (!viewportIndexImplementation._show) {
|
||||||
|
console.warn('show() NOT IMPLEMENTED');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return viewportIndexImplementation._show({
|
||||||
|
content,
|
||||||
|
contentProps,
|
||||||
|
viewportIndex,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides/dismisses the viewport dialog, if currently shown
|
||||||
|
*
|
||||||
|
* @param {*} { viewportIndex }
|
||||||
|
*/
|
||||||
|
function _hide({ viewportIndex }) {
|
||||||
|
const viewportIndexImplementation =
|
||||||
|
(viewportIndex && serviceImplementation._viewports[viewportIndex]) || {};
|
||||||
|
|
||||||
|
if (!viewportIndexImplementation._hide) {
|
||||||
|
console.warn('hide() NOT IMPLEMENTED');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return viewportIndexImplementation._hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param {*} {
|
||||||
|
* hide: hideImplementation,
|
||||||
|
* show: showImplementation,
|
||||||
|
* viewportIndex,
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
function setServiceImplementation({
|
||||||
|
hide: hideImplementation,
|
||||||
|
show: showImplementation,
|
||||||
|
viewportIndex,
|
||||||
|
}) {
|
||||||
|
if (viewportIndex !== undefined) {
|
||||||
|
const newImplementations = {};
|
||||||
|
if (hideImplementation) {
|
||||||
|
newImplementations._hide = hideImplementation;
|
||||||
|
}
|
||||||
|
if (showImplementation) {
|
||||||
|
newImplementations._show = showImplementation;
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceImplementation._viewports[viewportIndex] = Object.assign(
|
||||||
|
{},
|
||||||
|
serviceImplementation._viewports[viewportIndex],
|
||||||
|
newImplementations
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name,
|
||||||
|
create: ({ configuration = {} }) => {
|
||||||
|
return publicAPI;
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -1,13 +1,15 @@
|
|||||||
import ServicesManager from './ServicesManager.js';
|
|
||||||
import UINotificationService from './UINotificationService';
|
|
||||||
import UIModalService from './UIModalService';
|
|
||||||
import UIDialogService from './UIDialogService';
|
|
||||||
import MeasurementService from './MeasurementService';
|
import MeasurementService from './MeasurementService';
|
||||||
|
import ServicesManager from './ServicesManager.js';
|
||||||
|
import UIDialogService from './UIDialogService';
|
||||||
|
import UIModalService from './UIModalService';
|
||||||
|
import UINotificationService from './UINotificationService';
|
||||||
|
import UIViewportDialogService from './UIViewportDialogService';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
UINotificationService,
|
|
||||||
UIModalService,
|
|
||||||
UIDialogService,
|
|
||||||
ServicesManager,
|
|
||||||
MeasurementService,
|
MeasurementService,
|
||||||
|
ServicesManager,
|
||||||
|
UIDialogService,
|
||||||
|
UIModalService,
|
||||||
|
UINotificationService,
|
||||||
|
UIViewportDialogService,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,6 +8,8 @@ export {
|
|||||||
ModalConsumer,
|
ModalConsumer,
|
||||||
useModal,
|
useModal,
|
||||||
withModal,
|
withModal,
|
||||||
|
ViewportDialogProvider,
|
||||||
|
useViewportDialog,
|
||||||
} from './src/contextProviders';
|
} from './src/contextProviders';
|
||||||
|
|
||||||
/** COMPONENTS */
|
/** COMPONENTS */
|
||||||
@ -15,6 +17,7 @@ export {
|
|||||||
Button,
|
Button,
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
DateRange,
|
DateRange,
|
||||||
|
Dialog,
|
||||||
EmptyStudies,
|
EmptyStudies,
|
||||||
Icon,
|
Icon,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
|||||||
12
platform/ui/src/components/Dialog/Dialog.jsx
Normal file
12
platform/ui/src/components/Dialog/Dialog.jsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
const Dialog = ({ children }) => {
|
||||||
|
return <div className="absolute top-0 left-0">{children}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
Dialog.propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Dialog;
|
||||||
2
platform/ui/src/components/Dialog/index.js
Normal file
2
platform/ui/src/components/Dialog/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import Dialog from './Dialog';
|
||||||
|
export default Dialog;
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
import ButtonGroup from './ButtonGroup';
|
import ButtonGroup from './ButtonGroup';
|
||||||
import DateRange from './DateRange';
|
import DateRange from './DateRange';
|
||||||
|
import Dialog from './Dialog';
|
||||||
import EmptyStudies from './EmptyStudies';
|
import EmptyStudies from './EmptyStudies';
|
||||||
import Icon from './Icon';
|
import Icon from './Icon';
|
||||||
import IconButton from './IconButton';
|
import IconButton from './IconButton';
|
||||||
@ -47,6 +48,7 @@ export {
|
|||||||
Button,
|
Button,
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
DateRange,
|
DateRange,
|
||||||
|
Dialog,
|
||||||
EmptyStudies,
|
EmptyStudies,
|
||||||
Icon,
|
Icon,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
|||||||
79
platform/ui/src/contextProviders/ViewportDialogProvider.jsx
Normal file
79
platform/ui/src/contextProviders/ViewportDialogProvider.jsx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import React, {
|
||||||
|
useState,
|
||||||
|
createContext,
|
||||||
|
useContext,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
} from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
const DEFAULT_OPTIONS = {
|
||||||
|
content: null,
|
||||||
|
contentProps: null,
|
||||||
|
customClassName: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ViewportDialogContext = createContext(null);
|
||||||
|
const { Provider } = ViewportDialogContext;
|
||||||
|
|
||||||
|
export const useViewportDialog = () => useContext(ViewportDialogContext);
|
||||||
|
|
||||||
|
const ViewportDialogProvider = ({
|
||||||
|
children,
|
||||||
|
dialog: Dialog,
|
||||||
|
service,
|
||||||
|
viewportIndex,
|
||||||
|
}) => {
|
||||||
|
const [options, setOptions] = useState(DEFAULT_OPTIONS);
|
||||||
|
|
||||||
|
const show = useCallback((props) => setOptions({ ...options, ...props }), [
|
||||||
|
options,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const hide = useCallback(() => setOptions(DEFAULT_OPTIONS), []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (service) {
|
||||||
|
service.setServiceImplementation({ hide, show, viewportIndex });
|
||||||
|
}
|
||||||
|
}, [hide, service, show, viewportIndex]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
content: ViewportDialogContent,
|
||||||
|
contentProps,
|
||||||
|
customClassName,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Provider value={{ show, hide }}>
|
||||||
|
<div className="relative w-full h-full">
|
||||||
|
{ViewportDialogContent && (
|
||||||
|
<Dialog className={customClassName}>
|
||||||
|
<ViewportDialogContent {...contentProps} show={show} hide={hide} />
|
||||||
|
</Dialog>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ViewportDialogProvider.propTypes = {
|
||||||
|
/** Children that will be wrapped with Modal Context */
|
||||||
|
children: PropTypes.oneOfType([
|
||||||
|
PropTypes.arrayOf(PropTypes.node),
|
||||||
|
PropTypes.node,
|
||||||
|
]).isRequired,
|
||||||
|
/** dialog component */
|
||||||
|
dialog: PropTypes.oneOfType([
|
||||||
|
PropTypes.arrayOf(PropTypes.node),
|
||||||
|
PropTypes.node,
|
||||||
|
PropTypes.func,
|
||||||
|
]).isRequired,
|
||||||
|
service: PropTypes.shape({
|
||||||
|
setServiceImplementation: PropTypes.func,
|
||||||
|
}),
|
||||||
|
viewportIndex: PropTypes.number,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ViewportDialogProvider;
|
||||||
165
platform/ui/src/contextProviders/ViewportDialogProvider.mdx
Normal file
165
platform/ui/src/contextProviders/ViewportDialogProvider.mdx
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
---
|
||||||
|
name: ViewportDialogProvider
|
||||||
|
route: customHooks/viewportDialogProvider
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Playground, Props } from 'docz';
|
||||||
|
|
||||||
|
<!-- This is a workaround to import things from ohif/core as docz does not allow us to access window element and @ohif/core does use it once we import to instanciate cornerstone -->
|
||||||
|
import { UIViewportDialogService, ServicesManager } from './../../../core/src/services';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ViewportDialogProvider,
|
||||||
|
Dialog,
|
||||||
|
Button,
|
||||||
|
useViewportDialog,
|
||||||
|
Notification,
|
||||||
|
} from '@ohif/ui';
|
||||||
|
|
||||||
|
# Viewport Dialog Provider
|
||||||
|
|
||||||
|
This is a context provider that allow the application to share the modal
|
||||||
|
component across all application.
|
||||||
|
|
||||||
|
## Sample
|
||||||
|
|
||||||
|
<Playground>
|
||||||
|
{() => {
|
||||||
|
const ViewportNotification = ({ hide }) => {
|
||||||
|
return (
|
||||||
|
<Notification
|
||||||
|
text="Track all measurement for this series?"
|
||||||
|
type="info"
|
||||||
|
actionButtons={
|
||||||
|
<div>
|
||||||
|
<Button onClick={hide}>No</Button>
|
||||||
|
<Button onClick={hide} className="ml-2">
|
||||||
|
No, do not ask again
|
||||||
|
</Button>
|
||||||
|
<Button onClick={hide} className="ml-2" color="primary">
|
||||||
|
Yes
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const ViewportActionButtons = () => {
|
||||||
|
const dialog = useViewportDialog();
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
dialog.show({
|
||||||
|
content: ViewportNotification,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Open Dialog
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="w-full flex flex-row p-4" style={{height: '400px'}}>
|
||||||
|
<div className="flex flex-1 items-center justify-center h-full w-full bg-black text-white border border-primary-main">
|
||||||
|
<ViewportDialogProvider dialog={Dialog}>
|
||||||
|
<div className="flex flex-1 flex-col items-center justify-center h-full">
|
||||||
|
<ViewportActionButtons />
|
||||||
|
<span>CONTENT 1</span>
|
||||||
|
</div>
|
||||||
|
</ViewportDialogProvider>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-1 items-center justify-center h-full w-full bg-black text-white border border-primary-main">
|
||||||
|
<ViewportDialogProvider dialog={Dialog}>
|
||||||
|
<div className="flex flex-1 flex-col items-center justify-center h-full">
|
||||||
|
<ViewportActionButtons />
|
||||||
|
<span>CONTENT 1</span>
|
||||||
|
</div>
|
||||||
|
</ViewportDialogProvider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Playground>
|
||||||
|
|
||||||
|
## Example using UIViewportDialogService
|
||||||
|
|
||||||
|
<Playground>
|
||||||
|
{() => {
|
||||||
|
const ViewportNotification = ({ hide }) => {
|
||||||
|
return (
|
||||||
|
<Notification
|
||||||
|
text="Track all measurement for this series?"
|
||||||
|
type="info"
|
||||||
|
actionButtons={
|
||||||
|
<div>
|
||||||
|
<Button onClick={hide}>No</Button>
|
||||||
|
<Button className="ml-2">No, do not ask again</Button>
|
||||||
|
<Button className="ml-2" color="primary">
|
||||||
|
Yes
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
// Creating servicesManager and register services should be in the root of your app
|
||||||
|
const servicesManager = new ServicesManager();
|
||||||
|
servicesManager.registerServices([UIViewportDialogService]);
|
||||||
|
// Get service instance
|
||||||
|
const _UIViewportDialogService =
|
||||||
|
servicesManager.services.UIViewportDialogService;
|
||||||
|
return (
|
||||||
|
<div className="w-full flex flex-col p-4" style={{height: '400px'}}>
|
||||||
|
<div className="flex flex-2">
|
||||||
|
<Button
|
||||||
|
className="mr-4 mb-4"
|
||||||
|
onClick={() =>
|
||||||
|
_UIViewportDialogService.show({
|
||||||
|
content: ViewportNotification,
|
||||||
|
viewportIndex: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Open dialog on 0
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="mr-4 mb-4"
|
||||||
|
onClick={() =>
|
||||||
|
_UIViewportDialogService.show({
|
||||||
|
content: ViewportNotification,
|
||||||
|
viewportIndex: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Open dialog on 1
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-1 w-full flex-row">
|
||||||
|
<ViewportDialogProvider
|
||||||
|
dialog={Dialog}
|
||||||
|
service={_UIViewportDialogService}
|
||||||
|
viewportIndex={0}
|
||||||
|
>
|
||||||
|
<div className="flex flex-1 items-center justify-center h-full bg-black text-white border border-primary-main">
|
||||||
|
CONTENT 0
|
||||||
|
</div>
|
||||||
|
</ViewportDialogProvider>
|
||||||
|
<ViewportDialogProvider
|
||||||
|
dialog={Dialog}
|
||||||
|
service={_UIViewportDialogService}
|
||||||
|
viewportIndex={1}
|
||||||
|
>
|
||||||
|
<div className="flex flex-1 items-center justify-center h-full bg-black text-white border border-primary-main">
|
||||||
|
CONTENT 1
|
||||||
|
</div>
|
||||||
|
</ViewportDialogProvider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Playground>
|
||||||
|
|
||||||
|
|
||||||
|
## Properties:
|
||||||
|
|
||||||
|
<Props of={ViewportDialogProvider} />
|
||||||
@ -3,4 +3,9 @@ export {
|
|||||||
useModal,
|
useModal,
|
||||||
withModal,
|
withModal,
|
||||||
ModalConsumer,
|
ModalConsumer,
|
||||||
} from './ModalProvider.js';
|
} from './ModalProvider';
|
||||||
|
|
||||||
|
export {
|
||||||
|
default as ViewportDialogProvider,
|
||||||
|
useViewportDialog,
|
||||||
|
} from './ViewportDialogProvider';
|
||||||
|
|||||||
@ -15,7 +15,7 @@ export const Sidebar = React.forwardRef((props, ref) => {
|
|||||||
const menus = useMenus({ query });
|
const menus = useMenus({ query });
|
||||||
const currentDoc = useCurrentDoc();
|
const currentDoc = useCurrentDoc();
|
||||||
const currentDocRef = useRef();
|
const currentDocRef = useRef();
|
||||||
const handleChange = ev => {
|
const handleChange = (ev) => {
|
||||||
setQuery(ev.target.value);
|
setQuery(ev.target.value);
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -40,26 +40,26 @@ export const Sidebar = React.forwardRef((props, ref) => {
|
|||||||
'Data Display',
|
'Data Display',
|
||||||
'Other',
|
'Other',
|
||||||
],
|
],
|
||||||
'Custom Hooks': ['ModalProvider'],
|
'Custom Hooks': ['ModalProvider', 'ViewportDialogProvider'],
|
||||||
Examples: ['Views'],
|
Examples: ['Views'],
|
||||||
System: ['Colors'],
|
System: ['Colors'],
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderMenuCategories = () => {
|
const renderMenuCategories = () => {
|
||||||
return Object.keys(customMenus).map(menuName => {
|
return Object.keys(customMenus).map((menuName) => {
|
||||||
return (
|
return (
|
||||||
<div key={menuName}>
|
<div key={menuName}>
|
||||||
<h2 className="pl-2 border-l-8 -ml-4 border-secondary-active">
|
<h2 className="pl-2 border-l-8 -ml-4 border-secondary-active">
|
||||||
{menuName}
|
{menuName}
|
||||||
</h2>
|
</h2>
|
||||||
{customMenus[menuName].map(item => item)}
|
{customMenus[menuName].map((item) => item)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMenuCategory = menuName => {
|
const getMenuCategory = (menuName) => {
|
||||||
return Object.keys(MENU_CATEGORIES).find(category => {
|
return Object.keys(MENU_CATEGORIES).find((category) => {
|
||||||
if (MENU_CATEGORIES[category].includes(menuName)) {
|
if (MENU_CATEGORIES[category].includes(menuName)) {
|
||||||
return category;
|
return category;
|
||||||
}
|
}
|
||||||
@ -99,7 +99,7 @@ export const Sidebar = React.forwardRef((props, ref) => {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
{menus &&
|
{menus &&
|
||||||
menus.map(menu => {
|
menus.map((menu) => {
|
||||||
const isGroup = !!menu.menu;
|
const isGroup = !!menu.menu;
|
||||||
const Component = isGroup ? NavGroup : NavLink;
|
const Component = isGroup ? NavGroup : NavLink;
|
||||||
const menuCategory = getMenuCategory(menu.name) || null;
|
const menuCategory = getMenuCategory(menu.name) || null;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user