From 118922f365384bcd58567bc915c26c8075c2ed75 Mon Sep 17 00:00:00 2001 From: Ibrahim Date: Fri, 13 Sep 2024 11:01:41 -0400 Subject: [PATCH] progress --- platform/app/src/App.tsx | 4 +- platform/app/src/appInit.js | 2 + platform/core/src/index.ts | 3 + .../OnboardingService/OnboardingService.ts | 121 ++++++++++++++++++ .../src/services/OnboardingService/index.ts | 3 + platform/core/src/services/index.ts | 2 + platform/core/src/types/AppTypes.ts | 3 + platform/core/src/types/Services.ts | 2 + platform/ui-next/package.json | 5 + .../contextProviders/OnboardingProvider.tsx | 84 ++++++++++++ .../ui-next/src/contextProviders/index.ts | 2 + platform/ui-next/src/index.ts | 9 +- yarn.lock | 55 ++++---- 13 files changed, 271 insertions(+), 24 deletions(-) create mode 100644 platform/core/src/services/OnboardingService/OnboardingService.ts create mode 100644 platform/core/src/services/OnboardingService/index.ts create mode 100644 platform/ui-next/src/contextProviders/OnboardingProvider.tsx diff --git a/platform/app/src/App.tsx b/platform/app/src/App.tsx index 13b6fecc4..e5cf5a1e7 100644 --- a/platform/app/src/App.tsx +++ b/platform/app/src/App.tsx @@ -24,7 +24,7 @@ import { UserAuthenticationProvider, ToolboxProvider, } from '@ohif/ui'; -import { ThemeWrapper as ThemeWrapperNext } from '@ohif/ui-next'; +import { ThemeWrapper as ThemeWrapperNext, OnboardingProvider } from '@ohif/ui-next'; // Viewer Project // TODO: Should this influence study list? import { AppConfigProvider } from '@state'; @@ -99,6 +99,7 @@ function App({ cineService, userAuthenticationService, customizationService, + onboardingService, } = servicesManager.services; const providers = [ @@ -115,6 +116,7 @@ function App({ [SnackbarProvider, { service: uiNotificationService }], [DialogProvider, { service: uiDialogService }], [ModalProvider, { service: uiModalService, modal: Modal }], + [OnboardingProvider, { service: onboardingService }], ]; // Loop through and register each of the service providers registered with the ServiceProvidersManager. diff --git a/platform/app/src/appInit.js b/platform/app/src/appInit.js index 60f5ee4db..8992a17e8 100644 --- a/platform/app/src/appInit.js +++ b/platform/app/src/appInit.js @@ -21,6 +21,7 @@ import { PanelService, WorkflowStepsService, StudyPrefetcherService, + OnboardingService, // utils, } from '@ohif/core'; @@ -75,6 +76,7 @@ async function appInit(appConfigOrFunc, defaultExtensions, defaultModes) { WorkflowStepsService.REGISTRATION, StateSyncService.REGISTRATION, [StudyPrefetcherService.REGISTRATION, appConfig.studyPrefetcher], + OnboardingService.REGISTRATION, ]); errorHandler.getHTTPErrorHandler = () => { diff --git a/platform/core/src/index.ts b/platform/core/src/index.ts index 219b32098..d729207a0 100644 --- a/platform/core/src/index.ts +++ b/platform/core/src/index.ts @@ -34,6 +34,7 @@ import { PanelService, WorkflowStepsService, StudyPrefetcherService, + OnboardingService, } from './services'; import { DisplaySetMessage, DisplaySetMessageList } from './services/DisplaySetService'; @@ -87,6 +88,7 @@ const OHIF = { useToolbar, WorkflowStepsService, StudyPrefetcherService, + OnboardingService, }; export { @@ -133,6 +135,7 @@ export { PanelService, WorkflowStepsService, StudyPrefetcherService, + OnboardingService, useToolbar, }; diff --git a/platform/core/src/services/OnboardingService/OnboardingService.ts b/platform/core/src/services/OnboardingService/OnboardingService.ts new file mode 100644 index 000000000..2f259110f --- /dev/null +++ b/platform/core/src/services/OnboardingService/OnboardingService.ts @@ -0,0 +1,121 @@ +const name = 'onboardingService'; + +const serviceImplementation = { + _showHints: () => console.warn('showHints() NOT IMPLEMENTED'), + _hideHints: () => console.warn('hideHints() NOT IMPLEMENTED'), + _startSteps: () => console.warn('startSteps() NOT IMPLEMENTED'), + _exitSteps: () => console.warn('exitSteps() NOT IMPLEMENTED'), + _nextStep: () => console.warn('nextStep() NOT IMPLEMENTED'), + _previousStep: () => console.warn('previousStep() NOT IMPLEMENTED'), + _setHints: hints => console.warn('setHints() NOT IMPLEMENTED'), + _setSteps: steps => console.warn('setSteps() NOT IMPLEMENTED'), +}; + +class OnboardingService { + static REGISTRATION = { + name, + altName: 'OnboardingService', + create: (): OnboardingService => { + return new OnboardingService(); + }, + }; + + readonly name = name; + + /** + * Show hints dynamically + */ + showHints() { + return serviceImplementation._showHints(); + } + + /** + * Hide hints + */ + hideHints() { + return serviceImplementation._hideHints(); + } + + /** + * Start the step tour dynamically + */ + startSteps() { + return serviceImplementation._startSteps(); + } + + /** + * Exit the step tour + */ + exitSteps() { + return serviceImplementation._exitSteps(); + } + + /** + * Move to the next step in the tour + */ + nextStep() { + return serviceImplementation._nextStep(); + } + + /** + * Move to the previous step in the tour + */ + previousStep() { + return serviceImplementation._previousStep(); + } + + /** + * Dynamically set the hints + */ + setHints(hints) { + return serviceImplementation._setHints(hints); + } + + /** + * Dynamically set the steps + */ + setSteps(steps) { + return serviceImplementation._setSteps(steps); + } + + /** + * Sets the service implementation for hints and steps + */ + setServiceImplementation({ + showHints: showHintsImplementation, + hideHints: hideHintsImplementation, + startSteps: startStepsImplementation, + exitSteps: exitStepsImplementation, + nextStep: nextStepImplementation, + previousStep: previousStepImplementation, + setHints: setHintsImplementation, + setSteps: setStepsImplementation, + }) { + if (showHintsImplementation) { + serviceImplementation._showHints = showHintsImplementation; + } + if (hideHintsImplementation) { + serviceImplementation._hideHints = hideHintsImplementation; + } + if (startStepsImplementation) { + serviceImplementation._startSteps = startStepsImplementation; + } + if (exitStepsImplementation) { + serviceImplementation._exitSteps = exitStepsImplementation; + } + if (nextStepImplementation) { + serviceImplementation._nextStep = nextStepImplementation; + } + if (previousStepImplementation) { + serviceImplementation._previousStep = previousStepImplementation; + } + if (setHintsImplementation) { + serviceImplementation._setHints = setHintsImplementation; + } + if (setStepsImplementation) { + serviceImplementation._setSteps = setStepsImplementation; + } + } +} + +export default OnboardingService; diff --git a/platform/core/src/services/OnboardingService/index.ts b/platform/core/src/services/OnboardingService/index.ts new file mode 100644 index 000000000..db23c8712 --- /dev/null +++ b/platform/core/src/services/OnboardingService/index.ts @@ -0,0 +1,3 @@ +import OnboardingService from './OnboardingService'; + +export default OnboardingService; diff --git a/platform/core/src/services/index.ts b/platform/core/src/services/index.ts index 68666a18a..2c5547a72 100644 --- a/platform/core/src/services/index.ts +++ b/platform/core/src/services/index.ts @@ -18,6 +18,7 @@ import StateSyncService from './StateSyncService'; import PanelService from './PanelService'; import WorkflowStepsService from './WorkflowStepsService'; import StudyPrefetcherService from './StudyPrefetcherService'; +import OnboardingService from './OnboardingService'; import type Services from '../types/Services'; @@ -44,4 +45,5 @@ export { PanelService, WorkflowStepsService, StudyPrefetcherService, + OnboardingService, }; diff --git a/platform/core/src/types/AppTypes.ts b/platform/core/src/types/AppTypes.ts index 7d5d35f5e..b056627cc 100644 --- a/platform/core/src/types/AppTypes.ts +++ b/platform/core/src/types/AppTypes.ts @@ -15,6 +15,7 @@ import PanelServiceType from '../services/PanelService'; import UIDialogServiceType from '../services/UIDialogService'; import UIViewportDialogServiceType from '../services/UIViewportDialogService'; import StudyPrefetcherServiceType from '../services/StudyPrefetcherService'; +import onboardingServiceType from '../services/OnboardingService'; import ServicesManagerType from '../services/ServicesManager'; import CommandsManagerType from '../classes/CommandsManager'; @@ -43,6 +44,7 @@ declare global { export type UIViewportDialogService = UIViewportDialogServiceType; export type PanelService = PanelServiceType; export type StudyPrefetcherService = StudyPrefetcherServiceType; + export type OnboardingService = onboardingServiceType; export interface Managers { servicesManager?: ServicesManager; @@ -67,6 +69,7 @@ declare global { uiViewportDialogService?: UIViewportDialogServiceType; panelService?: PanelServiceType; studyPrefetcherService?: StudyPrefetcherServiceType; + onboardingService?: onboardingServiceType; } export interface Config { routerBasename?: string; diff --git a/platform/core/src/types/Services.ts b/platform/core/src/types/Services.ts index 119a2da87..47f9b82fe 100644 --- a/platform/core/src/types/Services.ts +++ b/platform/core/src/types/Services.ts @@ -14,6 +14,7 @@ import { PanelService, UIDialogService, UIViewportDialogService, + OnboardingService, } from '../services'; /** @@ -36,6 +37,7 @@ interface Services { uiDialogService?: UIDialogService; uiViewportDialogService?: UIViewportDialogService; panelService?: PanelService; + onboardingService?: OnboardingService; } export default Services; diff --git a/platform/ui-next/package.json b/platform/ui-next/package.json index f3d7f3051..610e12cda 100644 --- a/platform/ui-next/package.json +++ b/platform/ui-next/package.json @@ -47,6 +47,8 @@ "cmdk": "^1.0.0", "date-fns": "^3.6.0", "framer-motion": "6.2.4", + "intro.js": "^7.2.0", + "intro.js-react": "^1.0.0", "lucide-react": "^0.379.0", "next-themes": "^0.3.0", "react": "^18.3.1", @@ -56,6 +58,9 @@ "tailwindcss": "3.2.4", "tailwindcss-animate": "^1.0.7" }, + "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.16.7" + }, "keywords": [], "author": "OHIF", "license": "MIT" diff --git a/platform/ui-next/src/contextProviders/OnboardingProvider.tsx b/platform/ui-next/src/contextProviders/OnboardingProvider.tsx new file mode 100644 index 000000000..733e074ab --- /dev/null +++ b/platform/ui-next/src/contextProviders/OnboardingProvider.tsx @@ -0,0 +1,84 @@ +import React, { useState, createContext, useContext, useEffect, useCallback } from 'react'; +import { Hints, Steps } from 'intro.js-react'; +import 'intro.js/introjs.css'; + +const OnboardingProviderContext = createContext(null); +const { Provider } = OnboardingProviderContext; + +export const useOnboardingProvider = () => useContext(OnboardingProviderContext); + +const OnboardingProvider = ({ + children, + service = null, +}: { + children: React.ReactNode; + service: any; +}) => { + const [hintsEnabled, setHintsEnabled] = useState(false); + const [stepsEnabled, setStepsEnabled] = useState(false); + const [currentStep, setCurrentStep] = useState(0); + const [steps, setSteps] = useState([]); + const [hints, setHints] = useState([]); + + const showHints = useCallback(() => setHintsEnabled(true), []); + const hideHints = useCallback(() => setHintsEnabled(false), []); + const startSteps = useCallback(() => setStepsEnabled(true), []); + const exitSteps = useCallback(() => setStepsEnabled(false), []); + const nextStep = useCallback(() => setCurrentStep(prev => prev + 1), []); + const previousStep = useCallback(() => setCurrentStep(prev => prev - 1), []); + + // Set the service implementation + useEffect(() => { + if (service) { + service.setServiceImplementation({ + showHints, + hideHints, + startSteps, + exitSteps, + nextStep, + previousStep, + setHints, + setSteps, + }); + } + }, [ + service, + showHints, + hideHints, + startSteps, + exitSteps, + nextStep, + previousStep, + setHints, + setSteps, + ]); + + return ( + + + + {children} + + ); +}; + +export default OnboardingProvider; diff --git a/platform/ui-next/src/contextProviders/index.ts b/platform/ui-next/src/contextProviders/index.ts index 57d3d90ec..09c42fe40 100644 --- a/platform/ui-next/src/contextProviders/index.ts +++ b/platform/ui-next/src/contextProviders/index.ts @@ -1,3 +1,5 @@ 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 3a3dfff9f..85ac618d7 100644 --- a/platform/ui-next/src/index.ts +++ b/platform/ui-next/src/index.ts @@ -35,7 +35,12 @@ import { ToolboxUI, } from './components'; -import { useNotification, NotificationProvider } from './contextProviders'; +import { + useNotification, + NotificationProvider, + OnboardingProvider, + useOnboardingProvider, +} from './contextProviders'; export { // components @@ -76,4 +81,6 @@ export { DisplaySetMessageListTooltip, Toolbox, ToolboxUI, + OnboardingProvider, + useOnboardingProvider, }; diff --git a/yarn.lock b/yarn.lock index 8bd793ce8..d0c693f02 100644 --- a/yarn.lock +++ b/yarn.lock @@ -561,7 +561,7 @@ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== -"@babel/plugin-proposal-private-property-in-object@^7.21.11": +"@babel/plugin-proposal-private-property-in-object@^7.16.7", "@babel/plugin-proposal-private-property-in-object@^7.21.11": version "7.21.11" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz#69d597086b6760c4126525cfa154f34631ff272c" integrity sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw== @@ -7240,7 +7240,7 @@ axobject-query@^3.2.1: dependencies: dequal "^2.0.3" -b4a@^1.6.4: +b4a@^1.6.4, b4a@^1.6.6: version "1.6.6" resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.6.tgz#a4cc349a3851987c3c4ac2d7785c18744f6da9ba" integrity sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg== @@ -7459,18 +7459,18 @@ bare-events@^2.0.0, bare-events@^2.2.0: integrity sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q== bare-fs@^2.1.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-2.3.1.tgz#cdbd63dac7a552dfb2b87d18c822298d1efd213d" - integrity sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA== + version "2.3.5" + resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-2.3.5.tgz#05daa8e8206aeb46d13c2fe25a2cd3797b0d284a" + integrity sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw== dependencies: bare-events "^2.0.0" bare-path "^2.0.0" bare-stream "^2.0.0" bare-os@^2.1.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-2.4.0.tgz#5de5e3ba7704f459c9656629edca7cc736e06608" - integrity sha512-v8DTT08AS/G0F9xrhyLtepoo9EJBJ85FRSMbu1pQUlAf6A8T0tEEQGMVObWeqpjhSPXsE0VGlluFBJu2fdoTNg== + version "2.4.4" + resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-2.4.4.tgz#01243392eb0a6e947177bb7c8a45123d45c9b1a9" + integrity sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ== bare-path@^2.0.0, bare-path@^2.1.0: version "2.1.3" @@ -7480,11 +7480,12 @@ bare-path@^2.0.0, bare-path@^2.1.0: bare-os "^2.1.0" bare-stream@^2.0.0: - version "2.1.3" - resolved "https://registry.yarnpkg.com/bare-stream/-/bare-stream-2.1.3.tgz#070b69919963a437cc9e20554ede079ce0a129b2" - integrity sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ== + version "2.3.0" + resolved "https://registry.yarnpkg.com/bare-stream/-/bare-stream-2.3.0.tgz#5bef1cab8222517315fca1385bd7f08dff57f435" + integrity sha512-pVRWciewGUeCyKEuRxwv06M079r+fRjAQjBEK2P6OYGrO43O+Z0LrPZZEjlc4mB6C2RpZ9AxJ1s7NLEtOHO6eA== dependencies: - streamx "^2.18.0" + b4a "^1.6.6" + streamx "^2.20.0" base16@^1.0.0: version "1.0.0" @@ -9387,9 +9388,9 @@ dayjs@^1.10.4: integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg== dcmjs@*, dcmjs@>=0.33.0, dcmjs@^0.29.8: - version "0.33.1" - resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.33.1.tgz#4297f40667a1ecbc3c5a84f9a207457ab3ae7856" - integrity sha512-/vwOPAX5fOQSwqf8t7bV7sy1h2h+gTWw5t/be8OIY7IwUpuKuKRChZvxINAjNZ9DDWHMB/uSOS7NJceu1YChfQ== + version "0.34.1" + resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.34.1.tgz#d70c514f6daf8128950cc9efd1aceb99dda79ab4" + integrity sha512-sswNQwILvJ2piRoWElEtJKYvKquk8gn3iU5K3X11Tv5UvGF7x4uNx+P9DTNiO/CgTdW0GUiVRdmXJG2WhrIoYA== dependencies: "@babel/runtime-corejs3" "^7.22.5" adm-zip "^0.5.10" @@ -12853,6 +12854,16 @@ interpret@^2.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== +intro.js-react@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/intro.js-react/-/intro.js-react-1.0.0.tgz#28f17519f387c4e76ad0f75c2bd733440bda0a83" + integrity sha512-zR8pbTyX20RnCZpJMc0nuHBpsjcr1wFkj3ZookV6Ly4eE/LGpFTQwPsaA61Cryzwiy/tTFsusf4hPU9NpI9UOg== + +intro.js@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/intro.js/-/intro.js-7.2.0.tgz#e58e8fee4f6b43d6e0fcb264c9ca8a0f0565026d" + integrity sha512-qbMfaB70rOXVBceIWNYnYTpVTiZsvQh/MIkfdQbpA9di9VBfj1GigUPfcCv3aOfsbrtPcri8vTLTA4FcEDcHSQ== + invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -20208,10 +20219,10 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.3.tgz#85b8fab4d71010fc3ba8772e8046cc49b8a3864b" integrity sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ== -streamx@^2.15.0, streamx@^2.18.0: - version "2.18.0" - resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.18.0.tgz#5bc1a51eb412a667ebfdcd4e6cf6a6fc65721ac7" - integrity sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ== +streamx@^2.15.0, streamx@^2.20.0: + version "2.20.1" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.20.1.tgz#471c4f8b860f7b696feb83d5b125caab2fdbb93c" + integrity sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA== dependencies: fast-fifo "^1.3.2" queue-tick "^1.0.1" @@ -20834,9 +20845,9 @@ test-exclude@^6.0.0: minimatch "^3.0.4" text-decoder@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.1.1.tgz#5df9c224cebac4a7977720b9f083f9efa1aefde8" - integrity sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA== + version "1.2.0" + resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.0.tgz#85f19d4d5088e0b45cd841bdfaeac458dbffeefc" + integrity sha512-n1yg1mOj9DNpk3NeZOx7T6jchTbyJS3i3cucbNN6FcdPriMZx7NsgrGpWWdWZZGxD7ES1XB+3uoqHMgOKaN+fg== dependencies: b4a "^1.6.4"