diff --git a/extensions/default/src/ViewerLayout/index.tsx b/extensions/default/src/ViewerLayout/index.tsx
index d39d6e181..3125dd136 100644
--- a/extensions/default/src/ViewerLayout/index.tsx
+++ b/extensions/default/src/ViewerLayout/index.tsx
@@ -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}
-
+
);
diff --git a/platform/app/public/config/default.js b/platform/app/public/config/default.js
index 42dc11282..d953e5b63 100644
--- a/platform/app/public/config/default.js
+++ b/platform/app/public/config/default.js
@@ -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: {},
diff --git a/platform/app/src/App.tsx b/platform/app/src/App.tsx
index cb5ff53dc..23690d612 100644
--- a/platform/app/src/App.tsx
+++ b/platform/app/src/App.tsx
@@ -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.
diff --git a/platform/app/src/routes/WorkList/WorkList.tsx b/platform/app/src/routes/WorkList/WorkList.tsx
index 01c39509e..785810ca3 100644
--- a/platform/app/src/routes/WorkList/WorkList.tsx
+++ b/platform/app/src/routes/WorkList/WorkList.tsx
@@ -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 ? (
-
+
{invalidModeDescription}
) : null
@@ -534,6 +535,7 @@ function WorkList({
WhiteLabeling={appConfig.whiteLabeling}
showPatientInfo={PatientInfoVisibility.DISABLED}
/>
+
.shepherd-arrow {
+ right: -5px !important;
+}
diff --git a/platform/ui-next/src/components/Onboarding/Onboarding.tsx b/platform/ui-next/src/components/Onboarding/Onboarding.tsx
new file mode 100644
index 000000000..415ffff90
--- /dev/null
+++ b/platform/ui-next/src/components/Onboarding/Onboarding.tsx
@@ -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 };
diff --git a/platform/ui-next/src/components/Onboarding/index.ts b/platform/ui-next/src/components/Onboarding/index.ts
new file mode 100644
index 000000000..459553f0b
--- /dev/null
+++ b/platform/ui-next/src/components/Onboarding/index.ts
@@ -0,0 +1,3 @@
+import { Onboarding } from './Onboarding';
+
+export { Onboarding };
diff --git a/platform/ui-next/src/components/index.ts b/platform/ui-next/src/components/index.ts
index c8951c2d7..b7dedaafd 100644
--- a/platform/ui-next/src/components/index.ts
+++ b/platform/ui-next/src/components/index.ts
@@ -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,
};
diff --git a/platform/ui-next/src/contextProviders/OnboardingProvider.tsx b/platform/ui-next/src/contextProviders/OnboardingProvider.tsx
deleted file mode 100644
index b756151a6..000000000
--- a/platform/ui-next/src/contextProviders/OnboardingProvider.tsx
+++ /dev/null
@@ -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 (
-
-
-
- {children}
-
-
- );
-};
-
-export default OnboardingProvider;
diff --git a/platform/ui-next/src/contextProviders/index.ts b/platform/ui-next/src/contextProviders/index.ts
index 09c42fe40..57d3d90ec 100644
--- a/platform/ui-next/src/contextProviders/index.ts
+++ b/platform/ui-next/src/contextProviders/index.ts
@@ -1,5 +1,3 @@
import NotificationProvider, { useNotification } from './NotificationProvider';
-import OnboardingProvider, { useOnboardingProvider } from './OnboardingProvider';
export { useNotification, NotificationProvider };
-export { useOnboardingProvider, OnboardingProvider };
diff --git a/platform/ui-next/src/index.ts b/platform/ui-next/src/index.ts
index 85ac618d7..880b3c11f 100644
--- a/platform/ui-next/src/index.ts
+++ b/platform/ui-next/src/index.ts
@@ -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,
};