This commit is contained in:
Ibrahim 2024-09-27 12:58:39 -04:00
parent 9ff4ece541
commit e91811ff23
11 changed files with 377 additions and 61 deletions

View File

@ -6,6 +6,7 @@ import { HangingProtocolService, CommandsManager } from '@ohif/core';
import { useAppConfig } from '@state'; import { useAppConfig } from '@state';
import ViewerHeader from './ViewerHeader'; import ViewerHeader from './ViewerHeader';
import SidePanelWithServices from '../Components/SidePanelWithServices'; import SidePanelWithServices from '../Components/SidePanelWithServices';
import { Onboarding } from '@ohif/ui-next';
function ViewerLayout({ function ViewerLayout({
// From Extension Module Params // From Extension Module Params
@ -155,7 +156,7 @@ function ViewerLayout({
) : null} ) : null}
</React.Fragment> </React.Fragment>
</div> </div>
<Onboarding />
<InvestigationalUseDialog dialogConfiguration={appConfig?.investigationalUseDialog} /> <InvestigationalUseDialog dialogConfiguration={appConfig?.investigationalUseDialog} />
</div> </div>
); );

View File

@ -298,27 +298,97 @@ window.config = {
steps: [ steps: [
{ {
id: 'scroll', id: 'scroll',
text: 'You can scroll through the images using the mouse wheel.', text: 'You can scroll through the images using the mouse wheel or scrollbar.',
attachTo: { attachTo: {
element: '.viewport-element', element: '.scroll',
on: 'left-start', on: 'left-start',
}, },
advanceOn: {
selector: '.cornerstone-viewport-element',
event: 'CORNERSTONE_STACK_VIEWPORT_SCROLL',
},
/**
buttons: [
{
text: 'Next',
action() {
this.next();
},
},
{
text: 'Skip',
action() {
this.cancel();
},
},
],
*/
}, },
{ {
id: 'zoom', id: 'zoom',
text: 'You can zoom the images using the right click.', text: 'You can zoom the images using the right click.',
attachTo: { attachTo: {
element: '.viewport-element', element: '.viewport-element',
on: 'left-start',
}, },
advanceOn: {
selector: '.cornerstone-viewport-element',
event: 'CORNERSTONE_CAMERA_MODIFIED',
},
/**
buttons: [
{
text: 'Next',
action() {
this.next();
},
},
{
text: 'Skip',
action() {
this.cancel();
},
},
{
text: 'Back',
action() {
this.back();
},
},
],
*/
}, },
{ {
id: 'pan', id: 'pan',
text: 'You can pan the images using the middle click.', text: 'You can pan the images using the middle click.',
attachTo: { attachTo: {
element: '.viewport-element', element: '.viewport-element',
on: 'left-start',
}, },
advanceOn: {
selector: '.cornerstone-viewport-element',
event: 'CORNERSTONE_TOOLS_MOUSE_DRAG',
},
/**
buttons: [
{
text: 'Next',
action() {
this.next();
},
},
{
text: 'Skip',
action() {
this.cancel();
},
},
{
text: 'Back',
action() {
this.back();
},
},
],
*/
}, },
{ {
id: 'windowing', id: 'windowing',
@ -327,14 +397,58 @@ window.config = {
element: '.viewport-element', element: '.viewport-element',
on: 'left-start', on: 'left-start',
}, },
/**
buttons: [
{
text: 'Next',
action() {
this.next();
},
},
{
text: 'Skip',
action() {
this.cancel();
},
},
{
text: 'Back',
action() {
this.back();
},
},
],
*/
}, },
{ {
id: 'length', id: 'length',
text: 'You can measure the length of a region using the Length tool.', text: 'You can measure the length of a region using the Length tool.',
attachTo: { attachTo: {
element: '.MeasurementTools-split-button-primary', element: '[data-cy="MeasurementTools-split-button-primary"]',
on: 'bottom', on: 'bottom',
}, },
/**
buttons: [
{
text: 'Next',
action() {
this.next();
},
},
{
text: 'Skip',
action() {
this.cancel();
},
},
{
text: 'Back',
action() {
this.back();
},
},
],
*/
}, },
{ {
id: 'drawAnnotation', id: 'drawAnnotation',
@ -343,6 +457,28 @@ window.config = {
element: '.viewport-element', element: '.viewport-element',
on: 'left-start', on: 'left-start',
}, },
/**
buttons: [
{
text: 'Next',
action() {
this.next();
},
},
{
text: 'Skip',
action() {
this.cancel();
},
},
{
text: 'Back',
action() {
this.back();
},
},
],
*/
}, },
{ {
id: 'trackMeasurement', id: 'trackMeasurement',
@ -351,6 +487,28 @@ window.config = {
element: '[data-cy="prompt-begin-tracking-yes-btn"]', element: '[data-cy="prompt-begin-tracking-yes-btn"]',
on: 'bottom', on: 'bottom',
}, },
/**
buttons: [
{
text: 'Next',
action() {
this.next();
},
},
{
text: 'Skip',
action() {
this.cancel();
},
},
{
text: 'Back',
action() {
this.back();
},
},
],
*/
}, },
{ {
id: 'openMeasurementPanel', id: 'openMeasurementPanel',
@ -359,6 +517,28 @@ window.config = {
element: '#trackedMeasurements-btn', element: '#trackedMeasurements-btn',
on: 'left-start', on: 'left-start',
}, },
/**
buttons: [
{
text: 'Next',
action() {
this.next();
},
},
{
text: 'Skip',
action() {
this.cancel();
},
},
{
text: 'Back',
action() {
this.back();
},
},
],
*/
}, },
{ {
id: 'scrollAwayFromMeasurement', id: 'scrollAwayFromMeasurement',
@ -367,6 +547,28 @@ window.config = {
element: '.viewport-element', element: '.viewport-element',
on: 'left-start', on: 'left-start',
}, },
/**
buttons: [
{
text: 'Next',
action() {
this.next();
},
},
{
text: 'Skip',
action() {
this.cancel();
},
},
{
text: 'Back',
action() {
this.back();
},
},
],
*/
}, },
{ {
id: 'jumpToMeasurement', id: 'jumpToMeasurement',
@ -375,6 +577,28 @@ window.config = {
element: '[data-cy="measurement-item"]', element: '[data-cy="measurement-item"]',
on: 'left-start', on: 'left-start',
}, },
/**
buttons: [
{
text: 'Next',
action() {
this.next();
},
},
{
text: 'Skip',
action() {
this.cancel();
},
},
{
text: 'Back',
action() {
this.back();
},
},
],
*/
}, },
{ {
id: 'changeLayout', id: 'changeLayout',
@ -383,6 +607,28 @@ window.config = {
element: '[data-cy="Layout"]', element: '[data-cy="Layout"]',
on: 'bottom', on: 'bottom',
}, },
/**
buttons: [
{
text: 'Next',
action() {
this.next();
},
},
{
text: 'Skip',
action() {
this.cancel();
},
},
{
text: 'Back',
action() {
this.back();
},
},
],
*/
}, },
{ {
id: 'selectLayout', id: 'selectLayout',
@ -391,6 +637,28 @@ window.config = {
element: '[data-cy="MPR"]', element: '[data-cy="MPR"]',
on: 'left-start', on: 'left-start',
}, },
/**
buttons: [
{
text: 'Complete',
action() {
this.complete();
},
},
{
text: 'Skip',
action() {
this.cancel();
},
},
{
text: 'Back',
action() {
this.back();
},
},
],
*/
}, },
], ],
tourOptions: {}, tourOptions: {},

View File

@ -24,13 +24,14 @@ import {
UserAuthenticationProvider, UserAuthenticationProvider,
ToolboxProvider, ToolboxProvider,
} from '@ohif/ui'; } from '@ohif/ui';
import { ThemeWrapper as ThemeWrapperNext, OnboardingProvider } from '@ohif/ui-next'; import { ThemeWrapper as ThemeWrapperNext } from '@ohif/ui-next';
// Viewer Project // Viewer Project
// TODO: Should this influence study list? // TODO: Should this influence study list?
import { AppConfigProvider } from '@state'; import { AppConfigProvider } from '@state';
import createRoutes from './routes'; import createRoutes from './routes';
import appInit from './appInit.js'; import appInit from './appInit.js';
import OpenIdConnectRoutes from './utils/OpenIdConnectRoutes'; import OpenIdConnectRoutes from './utils/OpenIdConnectRoutes';
import { ShepherdJourneyProvider } from 'react-shepherd';
let commandsManager: CommandsManager, let commandsManager: CommandsManager,
extensionManager: ExtensionManager, extensionManager: ExtensionManager,
@ -99,7 +100,6 @@ function App({
cineService, cineService,
userAuthenticationService, userAuthenticationService,
customizationService, customizationService,
onboardingService,
} = servicesManager.services; } = servicesManager.services;
const providers = [ const providers = [
@ -116,7 +116,7 @@ function App({
[SnackbarProvider, { service: uiNotificationService }], [SnackbarProvider, { service: uiNotificationService }],
[DialogProvider, { service: uiDialogService }], [DialogProvider, { service: uiDialogService }],
[ModalProvider, { service: uiModalService, modal: Modal }], [ModalProvider, { service: uiModalService, modal: Modal }],
[OnboardingProvider], [ShepherdJourneyProvider],
]; ];
// Loop through and register each of the service providers registered with the ServiceProvidersManager. // Loop through and register each of the service providers registered with the ServiceProvidersManager.

View File

@ -34,6 +34,7 @@ import {
import { Types } from '@ohif/ui'; import { Types } from '@ohif/ui';
import i18n from '@ohif/i18n'; import i18n from '@ohif/i18n';
import { Onboarding } from '@ohif/ui-next';
const PatientInfoVisibility = Types.PatientInfoVisibility; const PatientInfoVisibility = Types.PatientInfoVisibility;
@ -411,7 +412,7 @@ function WorkList({
disabled={!isValidMode} disabled={!isValidMode}
startIconTooltip={ startIconTooltip={
!isValidMode ? ( !isValidMode ? (
<div className="font-inter flex w-[206px] whitespace-normal text-left text-xs font-normal text-white "> <div className="font-inter flex w-[206px] whitespace-normal text-left text-xs font-normal text-white">
{invalidModeDescription} {invalidModeDescription}
</div> </div>
) : null ) : null
@ -534,6 +535,7 @@ function WorkList({
WhiteLabeling={appConfig.whiteLabeling} WhiteLabeling={appConfig.whiteLabeling}
showPatientInfo={PatientInfoVisibility.DISABLED} showPatientInfo={PatientInfoVisibility.DISABLED}
/> />
<Onboarding />
<InvestigationalUseDialog dialogConfiguration={appConfig?.investigationalUseDialog} /> <InvestigationalUseDialog dialogConfiguration={appConfig?.investigationalUseDialog} />
<div className="ohif-scrollbar ohif-scrollbar-stable-gutter flex grow flex-col overflow-y-auto sm:px-5"> <div className="ohif-scrollbar ohif-scrollbar-stable-gutter flex grow flex-col overflow-y-auto sm:px-5">
<StudyListFilter <StudyListFilter

View File

@ -0,0 +1,23 @@
.shepherd-element {
@apply !bg-primary-dark !rounded-lg !p-2;
}
.shepherd-text {
@apply text-[11px] !text-white;
}
.shepherd-footer {
@apply !bg-primary-dark !rounded-b-lg;
}
.shepherd-button {
@apply !bg-primary-main hover:!bg-customblue-80 active:!bg-customblue-40 !box-content !inline-flex !h-[15px] !min-w-[60px] !flex-row !items-center !justify-center !gap-[5px] !whitespace-nowrap !rounded !px-[10px] !text-center !font-sans !text-[11px] !font-semibold !leading-[1.2] !text-white !outline-none !transition !duration-300 !ease-in-out focus:!outline-none;
}
.shepherd-arrow::before {
@apply !bg-primary-dark;
}
.shepherd-element[data-popper-placement^='left'] > .shepherd-arrow {
right: -5px !important;
}

View File

@ -0,0 +1,65 @@
import { useEffect } from 'react';
import { useShepherd } from 'react-shepherd';
import { StepOptions, TourOptions } from 'shepherd.js';
import { useLocation } from 'react-router';
import 'shepherd.js/dist/css/shepherd.css';
import './Onboarding.css';
const hasTourBeenShown = (tourId: string) => {
const shownTours = JSON.parse(localStorage.getItem('shownTours')) || [];
return shownTours.includes(tourId);
};
const markTourAsShown = (tourId: string) => {
const shownTours = JSON.parse(localStorage.getItem('shownTours')) || [];
if (!shownTours.includes(tourId)) {
shownTours.push(tourId);
localStorage.setItem('shownTours', JSON.stringify(shownTours));
}
};
const Onboarding = () => {
const Shepherd = useShepherd();
const location = useLocation();
const tours = window.config.tours as Array<{
id: string;
route: string;
tourOptions: TourOptions;
steps: StepOptions[];
}>;
useEffect(() => {
if (!tours) {
return null;
}
const pathname = location.pathname;
const matchingTour = tours.find(tour => tour.route === pathname);
if (matchingTour) {
const startTour = () => {
const tourInstance = new Shepherd.Tour(matchingTour.tourOptions);
matchingTour.steps.forEach(step => tourInstance.addStep(step));
const observer = new MutationObserver(() => {
const firstStepElement = document.querySelector(
matchingTour.steps[0].attachTo.element as string
);
if (firstStepElement) {
observer.disconnect();
tourInstance.start();
markTourAsShown(matchingTour.id);
}
});
if (!hasTourBeenShown(matchingTour.id)) {
observer.observe(document.body, { childList: true, subtree: true });
setTimeout(() => observer.disconnect(), 5000);
}
};
startTour();
}
}, [Shepherd, tours, location.pathname]);
return null;
};
export { Onboarding };

View File

@ -0,0 +1,3 @@
import { Onboarding } from './Onboarding';
export { Onboarding };

View File

@ -28,6 +28,7 @@ import ThumbnailList from './ThumbnailList';
import PanelSection from './PanelSection'; import PanelSection from './PanelSection';
import DisplaySetMessageListTooltip from './DisplaySetMessageListTooltip'; import DisplaySetMessageListTooltip from './DisplaySetMessageListTooltip';
import { Toolbox, ToolboxUI } from './Toolbox'; import { Toolbox, ToolboxUI } from './Toolbox';
import { Onboarding } from './Onboarding';
export { export {
Button, Button,
@ -71,4 +72,5 @@ export {
DisplaySetMessageListTooltip, DisplaySetMessageListTooltip,
Toolbox, Toolbox,
ToolboxUI, ToolboxUI,
Onboarding,
}; };

View File

@ -1,41 +0,0 @@
import React, { createContext, useContext } from 'react';
import { ShepherdJourneyProvider, useShepherd } from 'react-shepherd';
import { StepOptions, TourOptions } from 'shepherd.js';
import 'shepherd.js/dist/css/shepherd.css';
const OnboardingProviderContext = createContext(null);
const { Provider } = OnboardingProviderContext;
export const useOnboardingProvider = () => useContext(OnboardingProviderContext);
const ShepherdController = ({
tours,
}: {
tours: Array<{
tourOptions: TourOptions;
steps: StepOptions[];
route: string;
id: string;
}>;
}) => {
const Shepherd = useShepherd();
return null;
};
const OnboardingProvider = ({ children }: { children: React.ReactNode }) => {
const tours = window?.config?.tours;
if (!tours) {
return <>{children}</>;
}
return (
<ShepherdJourneyProvider>
<Provider value={{}}>
<ShepherdController tours={tours} />
{children}
</Provider>
</ShepherdJourneyProvider>
);
};
export default OnboardingProvider;

View File

@ -1,5 +1,3 @@
import NotificationProvider, { useNotification } from './NotificationProvider'; import NotificationProvider, { useNotification } from './NotificationProvider';
import OnboardingProvider, { useOnboardingProvider } from './OnboardingProvider';
export { useNotification, NotificationProvider }; export { useNotification, NotificationProvider };
export { useOnboardingProvider, OnboardingProvider };

View File

@ -33,14 +33,10 @@ import {
DisplaySetMessageListTooltip, DisplaySetMessageListTooltip,
Toolbox, Toolbox,
ToolboxUI, ToolboxUI,
Onboarding,
} from './components'; } from './components';
import { import { useNotification, NotificationProvider } from './contextProviders';
useNotification,
NotificationProvider,
OnboardingProvider,
useOnboardingProvider,
} from './contextProviders';
export { export {
// components // components
@ -81,6 +77,5 @@ export {
DisplaySetMessageListTooltip, DisplaySetMessageListTooltip,
Toolbox, Toolbox,
ToolboxUI, ToolboxUI,
OnboardingProvider, Onboarding,
useOnboardingProvider,
}; };