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

View File

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

View File

@ -24,13 +24,14 @@ import {
UserAuthenticationProvider,
ToolboxProvider,
} from '@ohif/ui';
import { ThemeWrapper as ThemeWrapperNext, OnboardingProvider } from '@ohif/ui-next';
import { ThemeWrapper as ThemeWrapperNext } from '@ohif/ui-next';
// Viewer Project
// TODO: Should this influence study list?
import { AppConfigProvider } from '@state';
import createRoutes from './routes';
import appInit from './appInit.js';
import OpenIdConnectRoutes from './utils/OpenIdConnectRoutes';
import { ShepherdJourneyProvider } from 'react-shepherd';
let commandsManager: CommandsManager,
extensionManager: ExtensionManager,
@ -99,7 +100,6 @@ function App({
cineService,
userAuthenticationService,
customizationService,
onboardingService,
} = servicesManager.services;
const providers = [
@ -116,7 +116,7 @@ function App({
[SnackbarProvider, { service: uiNotificationService }],
[DialogProvider, { service: uiDialogService }],
[ModalProvider, { service: uiModalService, modal: Modal }],
[OnboardingProvider],
[ShepherdJourneyProvider],
];
// 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 i18n from '@ohif/i18n';
import { Onboarding } from '@ohif/ui-next';
const PatientInfoVisibility = Types.PatientInfoVisibility;
@ -411,7 +412,7 @@ function WorkList({
disabled={!isValidMode}
startIconTooltip={
!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}
</div>
) : null
@ -534,6 +535,7 @@ function WorkList({
WhiteLabeling={appConfig.whiteLabeling}
showPatientInfo={PatientInfoVisibility.DISABLED}
/>
<Onboarding />
<InvestigationalUseDialog dialogConfiguration={appConfig?.investigationalUseDialog} />
<div className="ohif-scrollbar ohif-scrollbar-stable-gutter flex grow flex-col overflow-y-auto sm:px-5">
<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 DisplaySetMessageListTooltip from './DisplaySetMessageListTooltip';
import { Toolbox, ToolboxUI } from './Toolbox';
import { Onboarding } from './Onboarding';
export {
Button,
@ -71,4 +72,5 @@ export {
DisplaySetMessageListTooltip,
Toolbox,
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 OnboardingProvider, { useOnboardingProvider } from './OnboardingProvider';
export { useNotification, NotificationProvider };
export { useOnboardingProvider, OnboardingProvider };

View File

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