From 02572b49280309770859cc5f55bb62708852eb88 Mon Sep 17 00:00:00 2001 From: Danny Brown Date: Fri, 6 Dec 2019 16:31:46 -0500 Subject: [PATCH] docs: extensions (#1258) * docs: process docs to include UX Stories requirement * docs: include note regarding different environments * Services init * Remove unused canny logos * docs: add ModalService diagram * docs: GIF of notification * docs: add ui-services page * docs: simplify ui services call out in the general services docs * docs: tips and tricks for UI services * docs: moar pages * docs: dialog gif * docs: services in summary/sidebar * docs: gif examples at top of dialog and notification pages * docs: add UIModal gif * docs: details for Dialog Service * docs: include usage information for ui modal service * docs: detailed information about our UI Notification Service * docs: services diagram * docs: remove unused links * docs: services example and image * docs: stubbing extension pages * docs: UX Stories --> User Cases * chore: eslint fix * docs: extensions overview + diagram * docs: update extensions in sidebar * docs: extension skeleton and registration info * docs: copy of maintained extensions table partial in the extensions folder * docs: swap out old extensions structure for new --- .../pull_request_template.md | 2 +- docs/latest/SUMMARY.md | 17 +- docs/latest/advanced/architecture.md | 10 +- docs/latest/advanced/custom-tools.md | 3 +- docs/latest/advanced/extensions.md | 263 ------------------ docs/latest/assets/img/extensions-diagram.png | Bin 0 -> 19240 bytes docs/latest/deployment/index.md | 3 +- .../deployment/recipes/embedded-viewer.md | 2 +- .../_maintained-extensions-table.md | 62 +++++ docs/latest/extensions/index.md | 151 ++++++++++ .../extensions/lifecycle/pre-registration.md | 21 ++ docs/latest/extensions/modules/commands.md | 34 +++ docs/latest/extensions/modules/panel.md | 3 + .../extensions/modules/sop-class-handler.md | 3 + docs/latest/extensions/modules/toolbar.md | 48 ++++ docs/latest/extensions/modules/viewport.md | 29 ++ docs/latest/hotkeys/index.md | 3 + docs/latest/our-process.md | 4 +- docs/latest/services/ui/index.md | 2 +- .../src/services/UIDialogService/index.js | 2 +- 20 files changed, 380 insertions(+), 282 deletions(-) delete mode 100644 docs/latest/advanced/extensions.md create mode 100644 docs/latest/assets/img/extensions-diagram.png create mode 100644 docs/latest/extensions/_maintained-extensions-table.md create mode 100644 docs/latest/extensions/index.md create mode 100644 docs/latest/extensions/lifecycle/pre-registration.md create mode 100644 docs/latest/extensions/modules/commands.md create mode 100644 docs/latest/extensions/modules/panel.md create mode 100644 docs/latest/extensions/modules/sop-class-handler.md create mode 100644 docs/latest/extensions/modules/toolbar.md create mode 100644 docs/latest/extensions/modules/viewport.md create mode 100644 docs/latest/hotkeys/index.md diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md index 7ddb1e172..4a5623153 100644 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -3,7 +3,7 @@ - [ ] Brief description of changes - [ ] Links to any relevant issues - [ ] Required status checks are passing -- [ ] UX Stories if changes impact the user's experience +- [ ] User cases if changes impact the user's experience - [ ] `@mention` a maintainer to request a review diff --git a/docs/latest/advanced/custom-tools.md b/docs/latest/advanced/custom-tools.md index bed6c344f..5afbfb2d5 100644 --- a/docs/latest/advanced/custom-tools.md +++ b/docs/latest/advanced/custom-tools.md @@ -2,7 +2,8 @@ This is not yet exposed in an easy/convenient way. Most tools are currently added by creating new Viewport, Toolbar, and SOPInstanceHandler extension -modules. You can read more about that approach in [extensions](./extensions.md). +modules. You can read more about that approach in +[extensions](../extensions/index.md). In the near future, we intend to improve the extensibility of tools for existing Viewports (like our Cornerstone.js and VTK.js viewports). diff --git a/docs/latest/advanced/extensions.md b/docs/latest/advanced/extensions.md deleted file mode 100644 index e83ffad76..000000000 --- a/docs/latest/advanced/extensions.md +++ /dev/null @@ -1,263 +0,0 @@ -# Extensions - -Extensions add new functionality to the viewer by registering one or more -modules. They go one step further than configuration in that they allow us to -inject custom React components, so long as they adhere to the module's -interface. This can be something as simple as adding a new button to the -toolbar, or as complex as a new viewport capable of rendering volumes in 3D. - -- [Overview](#overview) -- [Modules](#modules) - - [Commands](#commands) - - [Hotkeys](#hotkeys) - - [Toolbar](#toolbar) - - [Panel](#panel) - - [Viewport](#viewport) - - [SOP Class Handler](#sopclasshandler) - -## Overview - -At a glance, an extension is a javascript object that has an `id` property, and -one or more "module" methods. You can find an abbreviated extension below, or -[view the source][example-ext-src] of our example extension. - -```js -export default { - /** - * Only required property. Should be a unique value across all extensions. - */ - id: 'example-extension', - - /** - * Registers one or more named commands scoped to a context. Commands are - * the primary means for... - */ - getCommandsModule() { - return { - defaultContext: 'VIEWER' - actions: { ... }, - definitions: { ... } - } - }, - - /** - * Allows you to provide toolbar definitions that will be merged with any - * existing application toolbar configuration. Used to determine which - * buttons should be visible when, their order, what happens when they're - * clicked, etc. - */ - getToolbarModule() { - return { - definitions: [ ... ], - defaultContext: 'ACTIVE_VIEWPORT::CORNERSTONE' - } - } - - /** - * Not yet implemented - */ - getPanelModule: () => null, - - /** - * Registers a ReactComponent that should be used to render data in a - * Viewport. The first registered viewport is our "default viewport". If - * more than one viewport is registered, we use `SopClassHandlers` to - * determine which viewport should be used. - */ - getViewportModule: () => reactViewportComponent, - - /** Provides a whitelist of SOPClassUIDs the viewport is capable of rendering. - * Can modify default behavior for methods like `getDisplaySetFromSeries` */ - getSopClassHandler: () => { - id: 'some-other-unique-id', - sopClassUids: [ ... ], - getDisplaySetFromSeries: (series, study, dicomWebClient, authorizationHeaders) => { ... } - }, -} -``` - -### Modules - -There are a few different module types. Each module type allows us to extend the -viewer in a different way, and provides a consistent API for us to do so. You -can find a full list of the different types of modules -[`in ohif-core`][module-types]. Information on each type of module, it's API, -and how we determine when/where it should be used is included below. - -> NOTE: Modifying the extensions/modules registered to the OHIF Viewer currently -> requires us to import and pass extensions to the ExtensionManager in -> `src/App.js`, then rebuild the application. Long-term, we intend to make it -> possible to accomplish this without a build step. - -#### Commands - -The Commands Module allows us to register one or more commands scoped to -specific contexts. Commands can be run by [hotkeys][#], [toolbar buttons][#], -and any registered custom react component (like a [viewport][#] or [panel][#]). -Here is a simple example commands module: - -```js -{ - getCommandsModule() { - return { - actions: { - speak: ({ viewports, words }) => { - console.log(viewports, words); - }, - }, - definitions: { - rotateViewportCW: { - commandFn: actions.rotateViewport, - storeContexts: ['viewports'], - options: { rotation: 90 } - }, - rotateViewportCCW: { - commandFn: actions.rotateViewport, - storeContexts: ['viewports'], - options: { rotation: -90 }, - context: 'ACTIVE_VIEWER::CORNERSTONE' - }, - }, - defaultContext: 'VIEWER' - } - } -} -``` - -#### Viewport - -An extension can register a Viewport Module by providing a `getViewportModule()` -method that returns a React Component. The React component will receive the -following props: - -```js -children: PropTypes.arrayOf(PropTypes.element) -studies: PropTypes.object, -displaySet: PropTypes.object, -viewportData: PropTypes.object, // { studies, displaySet } -viewportIndex: PropTypes.number, -children: PropTypes.node, -customProps: PropTypes.object -``` - -Viewport components are managed by the `ViewportGrid` Component. Which Viewport -component is used depends on: - -- The Layout Configuration -- Registered SopClassHandlers -- The SopClassUID for visible/selected datasets - -![Cornerstone Viewport](../assets/img/extensions-viewport.png) - -
An example of three Viewports
- -For a complete example implementation, -[check out the OHIFCornerstoneViewport](https://github.com/OHIF/Viewers/blob/master/extensions/cornerstone/src/OHIFCornerstoneViewport.js). - -#### Toolbar - -An extension can register a Toolbar Module by providing a `getToolbarModule()` -method that returns a React Component. The component does not receive any props. -If you want to modify or react to state, you will need to connect to the redux -store. The given toolbar must determine its set of elements and the context of -them. The set of elements will be listed on toolbar `definitions`. - -![Toolbar Extension](../assets/img/extensions-toolbar.gif) - -
A toolbar extension example
- -Toolbar components are rendered in the `ToolbarRow` component. - -For a complete example implementation, -[check out the OHIFCornerstoneViewport's Toolbar Module](https://github.com/OHIF/Viewers/blob/master/extensions/cornerstone/src/toolbarModule.js). - -##### Toolbar Custom Component - -Toolbar elements can define its own custom react component to be consumed when -rendering it. So far, it accepts `Functional` and `Class` Components. For that, -you just need to expose your `CustomToolbarComponent` as the value of key -`CustomComponent`. In case the property `CustomComponent` is not present, a -default toolbar component will be used to render it. See bellow - -```js -definitions: [ -... - { - id: 'Custom', - label: 'Custom', - icon: 'custom-icon', - CustomComponent: CustomToolbarComponent, - } -... -] - -``` - -`CustomComponent` components will receive the following props: - -- parentContext: parent context. (In most of the cases it will be a ToolbarRow - instance) -- toolbarClickCallback: callback method when clicking on toolbar -- button: its own definition object -- key: react key prop -- activeButtons: list of active elements -- isActive: if current - -#### SopClassHandler - -... - -#### Panel - -> The panel module is not yet in use. - -#### Hotkeys - -... - -### Registering Extensions - -Extensions are registered for the application at startup. The -`ExtensionManager`, exposed by `ohif-core`, registers a list of extensions with -our application's store. Each module provided by the extension becomes available -via `state.plugins.availablePlugins`, and consists of three parts: id, type -([PLUGIN_TYPE](https://github.com/OHIF/ohif-core/blob/43c08a29eff3fb646a0e83a03a236ddd84f4a6e8/src/plugins.js#L1-L6)), -and the return value of the module method. - -In a future version, we will likely expose a way to provide the extensions you -would like included at startup. - -_app.js_ - -```js -import { createStore, combineReducers } from 'redux'; -import OHIF from '@ohif/core'; -import OHIFCornerstoneExtension from 'ohif-cornerstone-extension'; - -const combined = combineReducers(OHIF.redux.reducers); -const store = createStore(combined); -const extensions = [new OHIFCornerstoneExtension()]; - -// Dispatches the `addPlugin` action to the store -// Adding extension modules to `state.plugins.availablePlugins` -ExtensionManager.registerExtensions(store, extensions); -``` - -## OHIF Maintained Extensions - -A small number of powerful extensions for popular use cases are maintained by -OHIF. They're co-located in the -[`OHIF/Viewers`](https://github.com/OHIF/Viewers) repository, in the top level -[`extensions/`](https://github.com/OHIF/Viewers/tree/master/extensions) -directory. - -{% include "./_maintained-extensions-table.md" %} - - - - -[example-ext-src]: https://github.com/OHIF/Viewers/tree/master/extensions/_example/src -[module-types]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/extensions/MODULE_TYPES.js - diff --git a/docs/latest/assets/img/extensions-diagram.png b/docs/latest/assets/img/extensions-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..c71be869263ca5c7ee9077bc8e2b22b9f487f5f3 GIT binary patch literal 19240 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

T$sGf`xDWwtUsEc3H+Kuq#SI z2UPj4qsYs5t~`BxXOYhhEetE~hxC4ng*ej!C*Kh}PDZ#J`$2X5;Cq%Q%8?-@GCpFl z*K)Cqv2EiCcPiTamue39=Y=kNqvLY7k<;11f4sf(PG%zTUB4f{T<)4x@9(!lVqR-gkTPep`(?;&F5?%|0<` zl1p(!ms^hubCOfD*DmYcaj4?$3MWZ51Sj6pN5-iMJHLdNWgf%X`WN{1?CU}vuq1fg zcpB?f?wKbU5p3mAn5M-fu7AiDpf4dVxXw5toc&3gUt&t?>KqqmQo`xO8yss2M^EY@ zuTgVmEq_MmtfV~DKv%Gd5(LzToxP8J*QAa(wxl3ZVEf)omRMZPS^l}fUlzH!-CJN~ z|4v2fVX2bLlckgg4lh}Ky(z$c`X<>aTN2)1L>L`o`Qp`nlR3-e{Xkft-*zo!9Xl8y za%#+%jMdECd7*I+I$yY}z3)8b9E|^)gYr>wtzMh|X9lPhX5gD1u_9~B9QyW9cPa-{ z{}!?79Odz;Iatb>ob}rK=)Q2D?l3qGI`fFzEpGbeo<8>ahRx!v0KYXG>$=!GoXH3e zqHS(zeh-v-4LR}d#0Q4&Y@w_Bv}LXnXQpi`svNi3o8o67I+*2)oZ*3(wXMcRc0jVB zCTt~uw81*|;m;|-c35pw81EU&ASjt@kKTcKfY?_3sm<@Ys_^g0G$V_rc1Vvej;{|g z91k^b>StIuUL9Vw)=V=bQwu>rImMS~^@iktQa3gv`;w2Gen#_p%pG8Zal1D$%2xLg zpRkwZu~z&lXnGg$;xv{kJrwJ(A^7m-QkCZLE9BH4imE7pN@IdE zT4<~ZG?fFmoPPSh75Dc4kyv+m;_DpsOpJ}%=r`WG@63OZs1?e#hg|&0wvC$}S4%jL zsvi||c7sI4bJrTc4AEe|GH7BJ5Vj`C*>u)kQp^FbVD(N7c0!+hb^% zfr3#+FNvP4TiaTEkEvN2i=5KwJaRNFJBnKzf8rK+`3G*bO@8;S=x~MDs(%?iWHc$) zyyHOX)GNV^xsKLR_eEIfl*WjtZjC|5$<`+{f32yvKMSYgzW>KarD52|oW%|NshUJ> z_z8`KVP18^qmqA({X(GB%iMZ;iFo^M?7^4&jsJx`l!?j`Y}C&?ZRtNnFr+PZ2h2pj zL$W<711`TiX&tlqA28%U$?*S^HUFDw|IbJ~&#!wtGeG?iKlSi{p{whoO;vS(#;ZgG z%vpE7tG$~z&5hdyhd*ONmDbb4wx&$F)s5C))y}=J&6ojhR0eYV1&_ud?ee=_QnB;I zD32;m9&G*1+87deUcV*S*xttk3$@Z+{2D}{Fq@l~7Dk37Z+xYSILAek9M0MtPXqu! zdCeOwYyH&edB#w?+og)J(1*J0UphK?!4rQ>T}`dr3le;m#0iJvM|74l;F|1Q(SBE$ z8lN;mYyMImzG1pYAXs662ezYVbF07eCdc$5mO+#8ho7t6=7-)^cRXh|V(a(Y+@2rb zMXGPE^QYt4VytLyz7h?w$;;j(ioN?Y;x6+l3A|FeHs26+FKV?-I2;Ks*Q0e%zk%n8 z%(9bKNvinJKdIAag?{&iz3Ce*MBI2hE@^$w088N7Ur zwj6jj@DRX%NM{mvst$VnqXc4(Nqh$AqO7RysW2=NSc)f=v{I`whw zpkAQ0F1lf9O*ZK`{TIpm@YA@JrJ4L9L_g*lm;44ckL!{cLqvsfb+TMAwf;-AV2E36 zc)0?_88f_f>`IG$b0J$bimJ-^65XDq}hKG0(E z(lDIvqs$uz1A(+#0lsa-Ewmvqx3UXhrtI3b);1~9%TC`v@qcE==OuXPHI87_C~4ld z^|$H%QDDxLc`uRsCs7Z%p%a)dlEhOjwor_tSvkY$(6Ds9$Df{f|R z^O*6ds>xJD{JW+0#Z~?uySon%=C-zT9IcZD8yeTEQGyD_t0o7c{5Ys;l=yOScCy$k zW6Vd1>7mc%uB_@^b@ShxCv!?!-h5E|uZPsLjn)`{Y*h(Sa=*v-{hcC;_5MvI4k;aY z1H8X!gLN7|ke;0u27FZSr2t3670A*x>f1{^#fpr;I!BCjDC4+Mwpu?X>qOH*9iH9D z^5{4i(_DYsLtiw1j;RBMv1K4SZI1~XBgl4ZDu;`pj9O3P&bmwcAySv|X(6Uh)xHGM zBG8q1ZtHYEh*L84s+A%llW(qN9b9#dP?)%&FSj=gD7P0`9@#3wnU!|5s606LF?d$Q`GGnwOgMbHdMg{HckYC(dy=>Y8Dc5%7k z+Gg!mHN@?L>4!HHdowjsw|vm(wdiDLL!(aiAQ%?X4&Jcu4xO*9vIG$mN512xyv>^n zCV2uO^fA_lwrPng+n(*2&MjtH9VF?&RT^79m6#M=EL!#-tzo@^=U>sB(>IkIC{ttik#M1oA`T^14-c^MF$#$ZC!~vO=Gmq=uz>*6en1kg96h z?3RRJ36#lb=BpmO!|9;6*5*2OpR*3GvEzc!Wz=1;jZLTR;5+&vl?(Eb&4MOLPEAy( zBzt$U=^ZBN3d%e}O1DME%-mn)BjP7-Ft(Q>AA`6A9JhUqk}a?jv1*0Gc3v`GxCoY9 z_l8bXC07V3xm|5K->salo_}tdIsfWgNMn|{lWuPP*}jLw0&A^}dE*M|13Yk>Tf=zr z__G78$`{!@x?+o`7y2>Pm1Itm4I^dNWfo;#2L)Xb;}kS(w}nbCd`&Fs4Y(=fn%Xi@ z`+Pl(Y#s3F#D8)yc!zed`=Fl~QC4i(>(#`V;S_B9$1z1oLQ~)7OmKi7*(l2O$2H3@ zBU%nVVju3Sfden0*47hyDeUW%w~{3@ZG&#ARi#m5mI+G7aj%c_es>;?nZv+YYg2K8 zWyRLHBq-xXbI`V*FkjXU7yj#0nG;I-#jBLJ&z#g%mKI^=rehOllGLk*Peu%1aI^KRn~7GQRv*^)v!ZE zJHi|$i)uw9tz=C17UmQRX2}Gk8sN>NC+zVXQlM1Ox9I(&xMHbTE%+`SEj`8fda^Z zr=LBWju7pE0j+uCSADjO84skS`WQRcvY2nnpv@g}%4l5$o!F6s%K?2;T*bHmJ3O=hf>zsEi=^iPY8 zob{$+n!h&f0-2Z?t5`nGxy|BU&Ulj*cmUe)HPo-%UB%xBafukrgLS_9VGTwcL1KgOM`?hQyvwKJ$N%tJ$l4Y*9cuU8Fw+cL5y|jtp2+g(($TbqQS^t6 zaVt-GmAbMNE1D9(Xxw|ZZ*26w`BHr+U2eJEv~*>!>yND$uDOSsD3maxMK0))py$WJ z@QK~JLQ5TDNiJ8>H*I<7*byuIag(`sxBGo1*O%}C*jBxi)Ua&s?|SFWNJnNfhxwwfJgYU`NfU%8J8LU#>%&XOeM>oRw$q{znV#TG{r-)C z>n0qv*g=@+i%!AvH;O>yn&_>DzRaxvfbii+JanX1bS$tu&~&v_7G6$ws-_+Yb-xRK zT?6_&-%_N&cr^h$?6pu{;^3F@V%YYe6FadGTt+m`Ev@XSPLAn!uq~YO4X;^WBsyXB zG@U#}Gbi^>w>kEMP3P@n;})l^Q|?qQhf>bTLIQoq&2okD2csUj=#Ec&oQin-DQB3m z(2^6_nZ%--6R7dw(HhZ4)N?&Wc@_Xa&UOjnLX*8c!lcEc`KA{3)#a!VqG59CNLAQ( za5News0CUI=38pl+?1~99NdG5Q_!BtJeXH_B9QmaKv&YWf#C!9-ktl zGEkf<`Er*3;hoch@3_@v@^bEc+VB!*GYl9Qx$kn2lE|&|_AD@T-yV>yORM}UIvlVe zHGc{_+wKoGDv<_ZUxz>5e%Vq=nchROwrs#An0RI6Viq{t@kA*kYh}5@`2r>{s+?<- z|HE6zKe#uZzH_8~0U#cv-xJB%afhjwqd;wSy^(4oGX$Tm3(9nl@&H31x1r1R{pFV-qX=Ie&+rCm z(I+wh;~(<=+p{_8xLG{sFdM-(dl!Exw!BZ#vzf7t{W(<@UV%zJd{oi10~_#0g1pdE zSXoZ!DQW=~>xH4HJt0jci)-Vft96F|*UV=g(f({{b0NDimSgkXcQyU_hOwV637@I~ zpUJ@v8tM4fedl2O8TPZ>TezQ};?392ul=$ARhQ3lUMcmsx_zs4N;j|67GGo+i)QrA zbF)4kKW%#{%W&TMzoj)kXP*Qz98gi$u`lxU-^Xlh_*1euC9hq)r#$=|OVFVw1%klA zki9xzXQn0sk1q+f{Lyjv)T`pS7`cCcO1ZDG&RpsYJa>dmDDS1z!f7v4F0BK#ztYrR z9hH$f+^7iLfE6qylK0(a@9qOZ%2y6$0K0k(nP1y)v&;XCY+RZKUeA=+0$NBkBanBy zwcPIgGW$VoowJSmt9x;z~^o2Bkuja9TA!!0UVFXKO6*HG;rX=t=bQcPiOJI z=K8P)xX^3S0nma1*q}?HDhcD;XE8yzyD0!ZjV&=a+3f9B|4-F`7R%ZktS5#?H#MCrcz^5hEG%s}?dJ$Ow4v{9Q7c zg2RIjT(J1I!<3bw;cvEPOf%;;Aphf)uOR;GCBgmL?7)=?JiW$o{}wR;kHYy1I{8E< z0~ov}W_uq#O}+Zc8EExy_g#wn_m%UOEdXhcsQq#3)B1mBqk$(l{XPypiDoHqE~M?0 z_WytPHS?|hhXb{AYcDL*+;XiAX!zXke@@3o8 + + + Extension + Description + Modules + + + + + + + + Cornerstone + + + + A viewport powered by cornerstone.js. Adds support for 2D DICOM rendering and manipulation, as well as support for the tools features in cornerstone-tools. Also adds "CINE Dialog" to the Toolbar. + + Viewport, Toolbar + + + + + + VTK.js + + + + A viewport powered by vtk.js. Adds support for volume renderings and advanced features like MPR. Also adds "3D Rotate" to the Toolbar. + + Viewport, Toolbar + + + + DICOM HTML + + + Renders text and HTML content for specific SopClassUIDs. + + Viewport, SopClassHandler + + + + DICOM PDF + + + Renders PDFs for a specific SopClassUID. + + Viewport, SopClassHandler + + + + DICOM Microscopy + + + Renders Microscopy images for a specific SopClassUID. + + Viewport, SopClassHandler + + + diff --git a/docs/latest/extensions/index.md b/docs/latest/extensions/index.md new file mode 100644 index 000000000..394f436c2 --- /dev/null +++ b/docs/latest/extensions/index.md @@ -0,0 +1,151 @@ +# Extensions + +- [Overview](#overview) +- [Concepts](#concepts) + - [Extension Skeleton](#extension-skeleton) + - [Registering an Extension](#registering-an-extension) + - [Lifecylce Hooks](#lifecycle-hooks) + - [Modules](#modules) + - [Contexts](#contexts) +- [Consuming Extensions](#consuming-extensions) +- [Maintained Extensions](#maintained-extensions) + +## Overview + +We use extensions to help us isolate and package groups of related features. +Extensions provide functionality, ui components, and new behaviors. + +

+ + Extensions Diagram + +
Diagram showing how extensions are configured and accessed.
+
+ +The `@ohif/viewer`'s application level configuration gives us the ability to add +and configure extensions. When the application starts, extensions are registered +with the `ExtensionManager`. Different portions of the `@ohif/viewer` project +will use registered extensions to influence application behavior. + +Extensions allow us to: + +- Wrap and integrate functionality of 3rd party dependencies in a reusable way +- Change how application data is mapped and transformed +- Display a consistent/cohesive UI +- Inject custom components to override built-in components + +Practical examples of extensions include: + +- A set of segmentation tools that build on top of the `cornerstone` viewport +- Showing ML/AI report summaries for the selected study/series/image +- Support for parsing DICOM structured reports and displaying them in a user + friendly way +- [See our maintained extensions for more examples of what's possible](#maintained-extensions) + +## Concepts + +### Extension Skeleton + +An extension is a plain JavaScript object has an `id` property, and one or more +"getModuleFunctions" and/or lifecycle hooks. You can read more about +[lifecycle hooks](#lifecycle-hooks) and [modules](#modules) further down. + +```js +// prettier-ignore +export default { + /** + * Only required property. Should be a unique value across all extensions. + */ + id: 'example-extension', + + // Lifecyle + preRegistration() { /* */ }, + // Modules + getCommandsModule() { /* */ }, + getToolbarModule() { /* */ }, + getPanelModule() { /* */ }, + getSopClassHandler() { /* */ }, + getViewportModule() { /* */ }, +} +``` + +### Registering an Extension + +There are two different ways to register and configure extensions. You can +leverage one or both strategies. Which one(s) you choose depend on your +application's requirements. Each [module](#modules) defined by the extension +becomes available to the core application via the `ExtensionManager`. + +```js +// prettier-ignore +const config = { + extensions: [ + MyFirstExtension, + [ + MySecondExtension, + { /* MySecondExtensions Configuration */ }, + ], + ]; +} +``` + +#### Runtime Extensions + +The `@ohif/viewer` uses a [configuration file](#) at startup. The schema for +that file includes an `Extensions` key that supports an array of extensions to +register. + +#### Bundled Extensions + +The `@ohif/viewer` works best when built as a "Progressive Web Application" +(PWA). If you know the extensions your application will need, you can specify +them at "build time" to leverage some advantaged afforded to us by modern +tooling: + +- Code Splitting +- Tree Shaking +- Dependency deduplication + +You can update the list of bundled extensions by: + +1. Having your `@ohif/viewer` project depend on the extension +2. Importing and adding it to the list of extensions in the + `/platform/src/index.js` entrypoint. + +### Lifecycle Hooks + +... + +### Modules + +There are a few different module types. Each module type allows us to extend the +viewer in a different way, and provides a consistent API for us to do so. You +can find a full list of the different types of modules +[`in ohif-core`][module-types]. Information on each type of module, it's API, +and how we determine when/where it should be used is included below. + +### Contexts + +... + +## Consuming Extensions + +... + +## Maintained Extensions + +A small number of powerful extensions for popular use cases are maintained by +OHIF. They're co-located in the [`OHIF/Viewers`][viewers-repo] repository, in +the top level [`extensions/`][ext-source] directory. + +{% include "./_maintained-extensions-table.md" %} + + + + +[viewers-repo]: https://github.com/OHIF/Viewers +[ext-source]: https://github.com/OHIF/Viewers/tree/master/extensions +[module-types]: https://github.com/OHIF/Viewers/blob/master/platform/core/src/extensions/MODULE_TYPES.js + diff --git a/docs/latest/extensions/lifecycle/pre-registration.md b/docs/latest/extensions/lifecycle/pre-registration.md new file mode 100644 index 000000000..a4cef9957 --- /dev/null +++ b/docs/latest/extensions/lifecycle/pre-registration.md @@ -0,0 +1,21 @@ +# Lifecylce Hook: preRegistration + +If an extension defines the `preRegistration` lifecycle hook, it is called +before any modules are registered to the `ExtensionManager`. + +```js +export default { + id: 'MyExampleExtension', + + preRegistration({ servicesManager, commandsManager, configuration }) { + console.log('Wiring up important stuff.'); + + window.importantStuff = () => { + console.log(configuration); + }; + + console.log('Important stuff has been wired.'); + window.importantStuff(); + }, +}; +``` diff --git a/docs/latest/extensions/modules/commands.md b/docs/latest/extensions/modules/commands.md new file mode 100644 index 000000000..3bf68c2ba --- /dev/null +++ b/docs/latest/extensions/modules/commands.md @@ -0,0 +1,34 @@ +# Module: Commands + +The Commands Module allows us to register one or more commands scoped to +specific contexts. Commands can be run by [hotkeys][#], [toolbar buttons][#], +and any registered custom react component (like a [viewport][#] or [panel][#]). +Here is a simple example commands module: + +```js +{ + getCommandsModule() { + return { + actions: { + speak: ({ viewports, words }) => { + console.log(viewports, words); + }, + }, + definitions: { + rotateViewportCW: { + commandFn: actions.rotateViewport, + storeContexts: ['viewports'], + options: { rotation: 90 } + }, + rotateViewportCCW: { + commandFn: actions.rotateViewport, + storeContexts: ['viewports'], + options: { rotation: -90 }, + context: 'ACTIVE_VIEWER::CORNERSTONE' + }, + }, + defaultContext: 'VIEWER' + } + } +} +``` diff --git a/docs/latest/extensions/modules/panel.md b/docs/latest/extensions/modules/panel.md new file mode 100644 index 000000000..4604d4154 --- /dev/null +++ b/docs/latest/extensions/modules/panel.md @@ -0,0 +1,3 @@ +# Module: Panel + +... diff --git a/docs/latest/extensions/modules/sop-class-handler.md b/docs/latest/extensions/modules/sop-class-handler.md new file mode 100644 index 000000000..4a7e81699 --- /dev/null +++ b/docs/latest/extensions/modules/sop-class-handler.md @@ -0,0 +1,3 @@ +# Module: SOP Class Handler + +... diff --git a/docs/latest/extensions/modules/toolbar.md b/docs/latest/extensions/modules/toolbar.md new file mode 100644 index 000000000..92508cc76 --- /dev/null +++ b/docs/latest/extensions/modules/toolbar.md @@ -0,0 +1,48 @@ +# Module: Toolbar + +An extension can register a Toolbar Module by providing a `getToolbarModule()` +method that returns a React Component. The component does not receive any props. +If you want to modify or react to state, you will need to connect to the redux +store. The given toolbar must determine its set of elements and the context of +them. The set of elements will be listed on toolbar `definitions`. + +![Toolbar Extension](../assets/img/extensions-toolbar.gif) + +
A toolbar extension example
+ +Toolbar components are rendered in the `ToolbarRow` component. + +For a complete example implementation, +[check out the OHIFCornerstoneViewport's Toolbar Module](https://github.com/OHIF/Viewers/blob/master/extensions/cornerstone/src/toolbarModule.js). + +## Toolbar Custom Component + +Toolbar elements can define its own custom react component to be consumed when +rendering it. So far, it accepts `Functional` and `Class` Components. For that, +you just need to expose your `CustomToolbarComponent` as the value of key +`CustomComponent`. In case the property `CustomComponent` is not present, a +default toolbar component will be used to render it. See bellow + +```js +definitions: [ +... + { + id: 'Custom', + label: 'Custom', + icon: 'custom-icon', + CustomComponent: CustomToolbarComponent, + } +... +] + +``` + +`CustomComponent` components will receive the following props: + +- parentContext: parent context. (In most of the cases it will be a ToolbarRow + instance) +- toolbarClickCallback: callback method when clicking on toolbar +- button: its own definition object +- key: react key prop +- activeButtons: list of active elements +- isActive: if current diff --git a/docs/latest/extensions/modules/viewport.md b/docs/latest/extensions/modules/viewport.md new file mode 100644 index 000000000..19b27b16c --- /dev/null +++ b/docs/latest/extensions/modules/viewport.md @@ -0,0 +1,29 @@ +# Module: Viewport + +An extension can register a Viewport Module by providing a `getViewportModule()` +method that returns a React Component. The React component will receive the +following props: + +```js +children: PropTypes.arrayOf(PropTypes.element) +studies: PropTypes.object, +displaySet: PropTypes.object, +viewportData: PropTypes.object, // { studies, displaySet } +viewportIndex: PropTypes.number, +children: PropTypes.node, +customProps: PropTypes.object +``` + +Viewport components are managed by the `ViewportGrid` Component. Which Viewport +component is used depends on: + +- The Layout Configuration +- Registered SopClassHandlers +- The SopClassUID for visible/selected datasets + +![Cornerstone Viewport](../../assets/img/extensions-viewport.png) + +
An example of three Viewports
+ +For a complete example implementation, +[check out the OHIFCornerstoneViewport](https://github.com/OHIF/Viewers/blob/master/extensions/cornerstone/src/OHIFCornerstoneViewport.js). diff --git a/docs/latest/hotkeys/index.md b/docs/latest/hotkeys/index.md new file mode 100644 index 000000000..95a90d7d6 --- /dev/null +++ b/docs/latest/hotkeys/index.md @@ -0,0 +1,3 @@ +# Hotkeys + +... diff --git a/docs/latest/our-process.md b/docs/latest/our-process.md index 41dd4bbbd..2f16d1f63 100644 --- a/docs/latest/our-process.md +++ b/docs/latest/our-process.md @@ -95,11 +95,11 @@ appropriate: | [PR: Awaiting Review 👀][awaiting-review] | The core team has not yet performed a code review. | | [PR: Awaiting Revisions 🖊][awaiting-revisions] | Following code review, this label is applied until the author has made sufficient changes. | | **QA** | | -| [PR: Awaiting UX Stories 💃][awaiting-stories] | The PR code changes need common language descriptions of impact to end users before the review can start | +| [PR: Awaiting User Cases 💃][awaiting-stories] | The PR code changes need common language descriptions of impact to end users before the review can start | | [PR: No UX Impact 🙃][no-ux-impact] | The PR code changes do not impact the user's experience | We rely on GitHub Checks and integrations with third party services to evaluate -changes in code quality and test coverage. Tests must pass and UX stories must +changes in code quality and test coverage. Tests must pass and User cases must be present (when applicable) before a PR can be merged to master, and code quality and test coverage must not changed by a significant margin. For some repositories, visual screenshot-based tests are also included, and video diff --git a/docs/latest/services/ui/index.md b/docs/latest/services/ui/index.md index 2f24c5258..708c26ab0 100644 --- a/docs/latest/services/ui/index.md +++ b/docs/latest/services/ui/index.md @@ -33,7 +33,7 @@ The `ServicesManager` is: - Passed to the `ExtensionManager` - The `ExtensionManager` makes the `ServicesManager` available to: - - All of it's lifecycle hooks (`preInit`) + - All of it's lifecycle hooks (`preRegistration`) - Each "getModuleFunction" (`getToolbarModule`, `getPanelModule`, etc.) ## Example diff --git a/platform/core/src/services/UIDialogService/index.js b/platform/core/src/services/UIDialogService/index.js index ae09d9ca1..44e8c0297 100644 --- a/platform/core/src/services/UIDialogService/index.js +++ b/platform/core/src/services/UIDialogService/index.js @@ -16,7 +16,7 @@ * @property {ReactElement|HTMLElement} content The dialog content. * @property {Object} contentProps The dialog content props. * @property {boolean} [isDraggable=true] Controls if dialog content is draggable or not. - * @property {boolean} [showOverlay=false] Controls dialog overlay. + * @property {boolean} [showOverlay=false] Controls dialog overlay. * @property {ElementPosition} defaultPosition Specifies the `x` and `y` that the dragged item should start at. * @property {ElementPosition} position If this property is present, the item becomes 'controlled' and is not responsive to user input. * @property {Function} onStart Called when dragging starts. If `false` is returned any handler, the action will cancel.