feat: Add DICOM SEG support and MPR, referenceLines and StackSync and more (#3015)
* feat: add initial sop class handler for SEG * feat: move segmentation service * update segmentation service methods * fix: viewport data structure * feat: Add initial render for the DICOM SEG for each displaySet * fix: various wrong architectural dependencies between services * feat: initial separate SEG display in each viewport * feat: refactore viewport action bar * initial work for SEG hydration * fix: various bugs regarding drag and dropping different viewports * fix: rendering issues for multiple seg displaysets * fix: bugs for thumbnail and hydration * feat: fix the initial segment color * update after rebase * feat: initial design for the segmentation group table * feat: initial new design for the segmentation panel * feat: segmentation panel * feat: make segmentation appear on all related viewports * fix: segmentation load bug based on functional groups * initial work for segmentation crosshairs * fix: various stylings and functionality * fix: various stylings for the seg panel * fix: overflow styles * feat: add more ui components * feat: added segmentation config * feat: add jump to segment * feat: add jump to segment for DICOM SEG viewport * fix: bugs after rebase * feat: jump in segmentation viewport * fix: mpr support for seg * feat: add more icons * feat: new icons * feat: add new side panel * feat: add segmentation config * feat: add loading indicator to ohif * feat: make hanging protocols follow matching rules for viewports * feat: enhance drag and drop to be hanging protocol aware * fix: mpr restore previous layout * fix: crosshairs toggle * fix: add auth headers to the dicom loader via dicomwebclient * fix: bug for crosshairs toggle in mpr * fix: seg viewport reusing old toolGroup * feat: add loading animation with lottie * fix: various bugs for Segmentation hydration in mpr * feat: change outline alpha to outline opacity * feat: loading indicator for seg viewport * fix: various segmentation group styles * fix: local mode for seg * feat: add animation for the highlight * fix: loading indicatro to show segment indices * feat: enhance modality drop down ui * fix: panels * fix: download form * fix: layout shift in loading indicator * fix: loading indicator to have correct values * fix: update software number * fix: image jump between MPR and default * fix: segmentation cleanup and cine service cleanup * fix: segmentation toolgroup clena up * fix: highlight interval should not trigger again * fix: issue with multiframe sorting * rename: change onSeriesChange to onArrowsClick * fix: buttons for tmtv and layout shift * fix: various bugs wrt crosshairs * fix: crosshairs re init on reset camera * fix: middle slice calculation different from cs middle reset camera * fix: reset camera should reset viewport camera * fix: loading segments in MPR mode * fix: tmtv hp back to before * fix: layout shift * feat: orientation markers for volume viewport * fix: capture for volume viewports * fix: memory leak for back to worklist * fix: loading bar bg color * fix: side panel for only one panel * fix: react select style in production * fix: various styling for segmentation groups * fix: various ui styles * feat: add new segmentation config styles * fix: hover state for segmentation item * fix: side panel layout shift * temp add panels * try to fix scrollbar for thimbnail * fix: scroll not appearing for side panels * feat: changed styles for scrollbar * feat: add cpu fallback warning * fix: webworker destroy * fix: select styles * fix: orientation marker color and position * fix: capture screenshot for volume viewports * fix: hide segment visibility on mpr * fix: reloading an already loaded seg displayset * feat: add is equal * fix: mpr jump to measurements * apply review comments * fix: optimization for the segmentation load * fix: remove unnecessary context menu and hp service reset * fix: segmentation service wrt brush settings * feat: initial work for the stack synchronization * fix: add validateDisplaySetSelectorsForNewDisplaySets to make sure drag and drop can follow requirements * fix: various react proptypes * fix: thumbnails for the tmtv and change default props to default params * feat: make showing loading indicator configurable and add docs * bump versions and add more docs * fix demo * update cornerstone versions * apply review comments * feat: add reference lines * fix build * fix unit tests * add reference lines icon * fix: e2e tests * fix static wado config * docs: add segmentation service docs * fix: docker build * fix: keep camera and bump versions * Try re-enabling minification to fix deploy previews Co-authored-by: Erik Ziegler <erik.sweed@gmail.com>
No files matched your search
@@ -43,7 +43,9 @@
|
||||
"react-modal": "3.11.2",
|
||||
"react-outside-click-handler": "^1.3.0",
|
||||
"react-select": "3.0.8",
|
||||
"react-with-direction": "^1.3.1"
|
||||
"react-with-direction": "^1.3.1",
|
||||
"lottie-react": "^2.3.1",
|
||||
"swiper": "^8.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.8",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<path id="bf9k9cj5la" d="m3 7.463.994-.982 2.131 2.113L10.756 4l.994.987-5.625 5.575z"/>
|
||||
</defs>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<rect stroke="#5ACCE6" fill="#5ACCE6" x=".5" y=".5" width="14" height="14" rx="3"/>
|
||||
<use fill="#000" xlink:href="#bf9k9cj5la"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 447 B |
@@ -0,0 +1,9 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<path id="3nvolf8jsa" d="M4.9 8.45 2.4 5.97l.795-.785L4.9 6.875 8.605 3.2l.795.79z"/>
|
||||
</defs>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<rect stroke="#348CFD" fill="#348CFD" x=".5" y=".5" width="11" height="11" rx="3"/>
|
||||
<use fill="#000" xlink:href="#3nvolf8jsa"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 444 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="807.5" y="496.5" width="14" height="14" rx="3" transform="translate(-807 -496)" fill="#000" stroke="#5ACCE6" fill-rule="evenodd"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 235 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x=".5" y=".5" width="11" height="11" rx="3" stroke="#348CFD" fill="none" fill-rule="evenodd"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 196 B |
@@ -0,0 +1,9 @@
|
||||
<svg width="19" height="19" viewBox="0 0 19 19" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" fill-rule="evenodd">
|
||||
<g stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="m9.75.5-8.5 3.719 8.5 3.718 8.5-3.718z"/>
|
||||
<path d="M1.25 4.219v10.093l8.5 3.72 8.5-3.72V4.22M9.75 7.938v10.093"/>
|
||||
</g>
|
||||
<path fill="currentColor" d="M1.25 4.576v9.736l8.05 3.72.45-10.095z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 482 B |
@@ -0,0 +1,6 @@
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(1 1)" fill="none" fill-rule="evenodd">
|
||||
<circle stroke="#1C498E" stroke-width=".5" cx="7.5" cy="7.5" r="7.5"/>
|
||||
<path fill="#1C498E" fill-rule="nonzero" d="M6.504 3 5.5 4.004l3.259 3.259L5.5 10.52l1.004 1.004 4.263-4.262z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 367 B |
@@ -0,0 +1,6 @@
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(1 1)" fill="none" fill-rule="evenodd">
|
||||
<circle stroke="#348CFD" stroke-width=".5" cx="7.5" cy="7.5" r="7.5"/>
|
||||
<path fill="#348CFD" fill-rule="nonzero" d="M6.504 3 5.5 4.004l3.259 3.259L5.5 10.52l1.004 1.004 4.263-4.262z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 367 B |
@@ -0,0 +1,6 @@
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="matrix(-1 0 0 1 16 1)" fill="none" fill-rule="evenodd">
|
||||
<circle stroke="#1C498E" stroke-width=".5" cx="7.5" cy="7.5" r="7.5"/>
|
||||
<path fill="#1C498E" fill-rule="nonzero" d="M6.504 3 5.5 4.004l3.259 3.259L5.5 10.52l1.004 1.004 4.263-4.262z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 374 B |
@@ -0,0 +1,6 @@
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="matrix(-1 0 0 1 16 1)" fill="none" fill-rule="evenodd">
|
||||
<circle stroke="#348CFD" stroke-width=".5" cx="7.5" cy="7.5" r="7.5"/>
|
||||
<path fill="#348CFD" fill-rule="nonzero" d="M6.504 3 5.5 4.004l3.259 3.259L5.5 10.52l1.004 1.004 4.263-4.262z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 374 B |
@@ -0,0 +1,7 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#348CFD" fill-rule="evenodd">
|
||||
<path d="M7 .102A6.899 6.899 0 1 0 7 13.9 6.899 6.899 0 0 0 7 .102zm0 .875a6.024 6.024 0 1 1 0 12.048A6.024 6.024 0 0 1 7 .977z"/>
|
||||
<path d="M6.462 5.486c.503 0 .917.38.97.87l.006.106v3.769a.438.438 0 0 1-.868.078l-.007-.078V6.46a.101.101 0 0 0-.07-.095l-.031-.005H5.385a.437.437 0 0 1-.079-.868l.079-.007h1.077zM6.192 2.793l.089.006a.707.707 0 1 1-.177 0l.088-.006z"/>
|
||||
<path d="M8.615 9.794c.242 0 .438.224.438.5 0 .246-.155.45-.359.492l-.079.008h-3.23c-.242 0-.438-.224-.438-.5 0-.245.155-.45.359-.492l.079-.008h3.23z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 689 B |
@@ -0,0 +1,5 @@
|
||||
<svg width="47" height="47" viewBox="0 0 47 47" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#FFF" fill-rule="nonzero">
|
||||
<path d="M43.585 0H29.763a3.415 3.415 0 0 0-3.415 3.415v13.822a3.415 3.415 0 0 0 3.415 3.415h13.822A3.415 3.415 0 0 0 47 17.237V3.415A3.415 3.415 0 0 0 43.585 0zM29.763 1.366h13.822c1.132 0 2.05.917 2.05 2.049v13.822a2.049 2.049 0 0 1-2.05 2.049H29.763a2.049 2.049 0 0 1-2.049-2.05V3.416c0-1.132.918-2.05 2.05-2.05zM17.237 0H3.415A3.415 3.415 0 0 0 0 3.415v13.822a3.415 3.415 0 0 0 3.415 3.415h13.822a3.415 3.415 0 0 0 3.415-3.415V3.415A3.415 3.415 0 0 0 17.237 0zM3.415 1.366h13.822c1.131 0 2.049.917 2.049 2.049v13.822a2.049 2.049 0 0 1-2.05 2.049H3.416a2.049 2.049 0 0 1-2.05-2.05V3.416c0-1.132.918-2.05 2.05-2.05zM43.585 26.348H29.763a3.415 3.415 0 0 0-3.415 3.415v13.822A3.415 3.415 0 0 0 29.763 47h13.822A3.415 3.415 0 0 0 47 43.585V29.763a3.415 3.415 0 0 0-3.415-3.415zm-13.822 1.366h13.822c1.132 0 2.05.918 2.05 2.05v13.821a2.049 2.049 0 0 1-2.05 2.05H29.763a2.049 2.049 0 0 1-2.049-2.05V29.763c0-1.131.918-2.049 2.05-2.049zM17.237 26.348H3.415A3.415 3.415 0 0 0 0 29.763v13.822A3.415 3.415 0 0 0 3.415 47h13.822a3.415 3.415 0 0 0 3.415-3.415V29.763a3.415 3.415 0 0 0-3.415-3.415zM3.415 27.714h13.822c1.131 0 2.049.918 2.049 2.05v13.821a2.049 2.049 0 0 1-2.05 2.05H3.416a2.049 2.049 0 0 1-2.05-2.05V29.763c0-1.131.918-2.049 2.05-2.049z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="11" viewBox="0 0 16 11" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke="#348CFD" stroke-width="1.5" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M1 5.406h9.834M6.765 1.337l4.069 4.07-4.07 4.068M15.242 9.475V1.337"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 311 B |
@@ -0,0 +1,5 @@
|
||||
<svg width="17" height="10" viewBox="0 0 17 10" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke="#348CFD" stroke-width="1.5" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M10.856 5.078H1M5.078 1 1 5.078l4.078 4.078M15.582 9.156V1"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 302 B |
@@ -0,0 +1,6 @@
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M0 0h21v21H0z"/>
|
||||
<path d="M5.25 9a1.75 1.75 0 1 0-.001 3.499A1.75 1.75 0 0 0 5.25 9zm10.5 0a1.75 1.75 0 1 0-.001 3.499A1.75 1.75 0 0 0 15.75 9zM10.5 9a1.75 1.75 0 1 0-.001 3.499A1.75 1.75 0 0 0 10.5 9z" fill="#348CFD" fill-rule="nonzero"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 405 B |
@@ -0,0 +1,6 @@
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M0 0h21v21H0z"/>
|
||||
<path fill="#FFF" d="m13.837 11-6 4.837V6.163z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 232 B |
@@ -0,0 +1,11 @@
|
||||
<svg width="25px" height="25px" viewBox="0 0 25 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="reference-lines" stroke="currentColor" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Group" transform="translate(3.250000, 4.926395)">
|
||||
<path d="M0.75,8.32360458 L2.75,8.32360458 C3.16421356,8.32360458 3.5,7.98781815 3.5,7.57360458 C3.5,7.15939102 3.16421356,6.82360458 2.75,6.82360458 L0.75,6.82360458 C0.335786438,6.82360458 0,7.15939102 0,7.57360458 C0,7.98781815 0.335786438,8.32360458 0.75,8.32360458 Z M5.75,8.32360458 L7.75,8.32360458 C8.16421356,8.32360458 8.5,7.98781815 8.5,7.57360458 C8.5,7.15939102 8.16421356,6.82360458 7.75,6.82360458 L5.75,6.82360458 C5.33578644,6.82360458 5,7.15939102 5,7.57360458 C5,7.98781815 5.33578644,8.32360458 5.75,8.32360458 Z M10.75,8.32360458 L12.75,8.32360458 C13.1642136,8.32360458 13.5,7.98781815 13.5,7.57360458 C13.5,7.15939102 13.1642136,6.82360458 12.75,6.82360458 L10.75,6.82360458 C10.3357864,6.82360458 10,7.15939102 10,7.57360458 C10,7.98781815 10.3357864,8.32360458 10.75,8.32360458 Z M15.75,8.32360458 L17.75,8.32360458 C18.1642136,8.32360458 18.5,7.98781815 18.5,7.57360458 C18.5,7.15939102 18.1642136,6.82360458 17.75,6.82360458 L15.75,6.82360458 C15.3357864,6.82360458 15,7.15939102 15,7.57360458 C15,7.98781815 15.3357864,8.32360458 15.75,8.32360458 Z" id="Line-7" fill="#000000" fill-rule="nonzero"></path>
|
||||
<g id="Group-2" transform="translate(1.250000, 0.000000)" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M0,5 L0,1 C-6.76353751e-17,0.44771525 0.44771525,1.01453063e-16 1,0 L15,0 C15.5522847,-1.01453063e-16 16,0.44771525 16,1 L16,5 L16,5" id="Path-7"></path>
|
||||
<path d="M0,15 L0,11 C-6.76353751e-17,10.4477153 0.44771525,10 1,10 L15,10 C15.5522847,10 16,10.4477153 16,11 L16,15 L16,15" id="Path-7" transform="translate(8.000000, 12.500000) scale(1, -1) translate(-8.000000, -12.500000) "></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,8 @@
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M0 0h21v21H0z"/>
|
||||
<g stroke="#348CFD" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.25">
|
||||
<path d="M10.5 5.5v10M15.5 10.5h-10"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 334 B |
@@ -0,0 +1,9 @@
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M0 0h21v21H0z"/>
|
||||
<g stroke="#FFF" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="m12.625 5.872 2.232 2.231-7.034 7.035-3.72 1.488 1.487-3.72 7.035-7.034z"/>
|
||||
<path d="M14.753 4.5c.404 0 .808.154 1.116.462.308.309.462.713.462 1.117s-.154.808-.462 1.116L14.753 8.31 12.52 6.08l1.115-1.117a1.574 1.574 0 0 1 1.117-.462z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 531 B |
@@ -0,0 +1,11 @@
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M0 0h21v21H0z"/>
|
||||
<path d="M0 0h21v21H0z"/>
|
||||
<path d="M17.29 10.6c-.676-.77-3.378-3.6-6.61-3.6-3.23 0-5.933 2.83-6.609 3.6a.293.293 0 0 0 0 .383c.676.77 3.378 3.6 6.61 3.6 3.23 0 5.933-2.83 6.609-3.6a.293.293 0 0 0 0-.383z" stroke="#3D5871" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.68 8.75c.69 0 1.298.344 1.666.87l-2.84 2.84c-.52-.37-.859-.98-.859-1.668 0-1.128.91-2.042 2.033-2.042zM12.689 10.47a2.038 2.038 0 0 1-2.337 2.337z" fill="#3D5871"/>
|
||||
<g stroke="#3D5871" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M16.396 5 5.02 16.375"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 782 B |
@@ -0,0 +1,11 @@
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M0 0h21v21H0z"/>
|
||||
<path d="M0 0h21v21H0z"/>
|
||||
<path d="M17.29 10.6c-.676-.77-3.378-3.6-6.61-3.6-3.23 0-5.933 2.83-6.609 3.6a.293.293 0 0 0 0 .383c.676.77 3.378 3.6 6.61 3.6 3.23 0 5.933-2.83 6.609-3.6a.293.293 0 0 0 0-.383z" stroke="#348CFD" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.68 8.75c.69 0 1.298.344 1.666.87l-2.84 2.84c-.52-.37-.859-.98-.859-1.668 0-1.128.91-2.042 2.033-2.042zM12.689 10.47a2.038 2.038 0 0 1-2.337 2.337z" fill="#348CFD"/>
|
||||
<g stroke="#348CFD" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M16.396 5 5.02 16.375"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 782 B |
@@ -0,0 +1,9 @@
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M0 0h21v21H0z"/>
|
||||
<g transform="translate(4 7)">
|
||||
<path d="M13.29 3.6C12.614 2.83 9.912 0 6.68 0 3.45 0 .747 2.83.071 3.6a.293.293 0 0 0 0 .383c.676.77 3.378 3.6 6.61 3.6 3.23 0 5.933-2.83 6.609-3.6a.293.293 0 0 0 0-.383z" stroke="#FFF" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<ellipse fill="#FFF" cx="6.68" cy="3.792" rx="2.033" ry="2.042"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 552 B |
@@ -0,0 +1,7 @@
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M0 0h21v21H0z"/>
|
||||
<path d="M8 10.5V8.559A2.554 2.554 0 0 1 10.549 6a2.554 2.554 0 0 1 2.548 2.559v2.483" stroke="#FFF" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14.468 11c0-.552-.42-1-.938-1H7.437c-.517 0-.937.448-.937 1v5c0 .552.42 1 .937 1h6.093c.518 0 .938-.448.938-1v-5z" fill="#FFF"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 479 B |
@@ -0,0 +1,9 @@
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M0 0h21v21H0z"/>
|
||||
<g transform="translate(4 7)">
|
||||
<path d="M13.29 3.6C12.614 2.83 9.912 0 6.68 0 3.45 0 .747 2.83.071 3.6a.293.293 0 0 0 0 .383c.676.77 3.378 3.6 6.61 3.6 3.23 0 5.933-2.83 6.609-3.6a.293.293 0 0 0 0-.383z" stroke="#348CFD" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<ellipse fill="#348CFD" cx="6.68" cy="3.792" rx="2.033" ry="2.042"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 558 B |
@@ -0,0 +1,11 @@
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M0 0h21v21H0z"/>
|
||||
<path d="M0 0h21v21H0z"/>
|
||||
<path d="M17.29 10.6c-.676-.77-3.378-3.6-6.61-3.6-3.23 0-5.933 2.83-6.609 3.6a.293.293 0 0 0 0 .383c.676.77 3.378 3.6 6.61 3.6 3.23 0 5.933-2.83 6.609-3.6a.293.293 0 0 0 0-.383z" stroke="#FFF" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.68 8.75c.69 0 1.298.344 1.666.87l-2.84 2.84c-.52-.37-.859-.98-.859-1.668 0-1.128.91-2.042 2.033-2.042zM12.689 10.47a2.038 2.038 0 0 1-2.337 2.337z" fill="#FFF"/>
|
||||
<g stroke="#FFF" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M16.396 5 5.02 16.375"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 773 B |
@@ -0,0 +1,7 @@
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M0 0h21v21H0z"/>
|
||||
<path d="M16.014 7.559A2.554 2.554 0 0 0 13.466 5a2.554 2.554 0 0 0-2.549 2.559v2.483" stroke="#FFF" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.968 10.824c0-.553-.42-1-.938-1H5.937c-.517 0-.937.447-.937 1v5c0 .552.42 1 .937 1h6.093c.518 0 .938-.448.938-1v-5z" fill="#FFF"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 483 B |
@@ -0,0 +1,8 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<rect stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" x="1.5" y="16.37" width="4.13" height="4.13" rx="1"/>
|
||||
<rect stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" x="16.37" y="1.5" width="4.13" height="4.13" rx="1"/>
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M5.388 16.612 16.612 5.388"/>
|
||||
<path d="M0 0h22v22H0z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 565 B |
@@ -0,0 +1,7 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M3.638 13.16a17.265 17.265 0 0 1 3.652-1.586c.565-.21.626-1.504.221-1.95-.584-.642-1.08-1.396-1.08-3.215a2.572 2.572 0 0 1 2.632-2.811 2.572 2.572 0 0 1 2.631 2.811c0 1.822-.495 2.573-1.08 3.215-.404.446-.343 1.74.222 1.95 1.275.386 2.5.919 3.652 1.587" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M17.15 11.064V2.369a.87.87 0 0 0-.87-.869H2.37a.87.87 0 0 0-.87.87v13.91c0 .48.39.87.87.87h8.694M15.846 20.628l-3.043.87.87-3.044 5.2-5.2a1.537 1.537 0 0 1 2.174 2.173l-5.201 5.2zM18.221 13.905l2.174 2.174M13.672 18.454l2.174 2.174" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M0 0h22v22H0z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 837 B |
@@ -0,0 +1,10 @@
|
||||
<svg width="23" height="22" viewBox="0 0 23 22" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="m13.34 11.74-2.971.425.425-2.971 7.64-7.64A1.8 1.8 0 1 1 20.98 4.1l-7.64 7.64zM8.644 19.034h7.383c.865 0 1.566-.701 1.566-1.566V10.03M12.504 4.94H5.066C4.2 4.94 3.5 5.643 3.5 6.508v1.972" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path stroke="currentColor" d="m11.5 8.5 2.333 2.333"/>
|
||||
<g stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="m1 14 2.5-2.5L6 14M1 17.5 3.5 20 6 17.5"/>
|
||||
</g>
|
||||
<path d="M0 0h22v22H0z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 672 B |
@@ -0,0 +1,11 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<circle stroke="currentColor" cx="11.037" cy="10.912" r="8"/>
|
||||
<g stroke="currentColor">
|
||||
<path stroke-linecap="square" d="m11.354 3.575-7.779 7.779M17.364 6.757 6.757 17.364"/>
|
||||
<path d="m18.955 9.763-9.192 9.192"/>
|
||||
<path stroke-linecap="square" d="M15.066 4.46 4.459 15.065"/>
|
||||
</g>
|
||||
<path d="M0 0h22v22H0z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 516 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="6" height="5" viewBox="0 0 6 5" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 5 6 .5H0z" fill="#726F7E" fill-rule="evenodd"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 151 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="6" height="5" viewBox="0 0 6 5" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m3 0 3 4.5H0z" fill="#726F7E" fill-rule="evenodd"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 152 B |
@@ -1,6 +1,6 @@
|
||||
/* CUSTOM OHIF SCROLLBAR */
|
||||
.ohif-scrollbar {
|
||||
scrollbar-color: #3E5967 transparent;
|
||||
scrollbar-color: #173239 transparent;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
}
|
||||
|
||||
.ohif-scrollbar::-webkit-scrollbar {
|
||||
@apply w-2;
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.ohif-scrollbar::-webkit-scrollbar-track {
|
||||
@@ -19,12 +19,12 @@
|
||||
.ohif-scrollbar::-webkit-scrollbar-thumb {
|
||||
@apply rounded;
|
||||
@apply bg-primary-main;
|
||||
background-color: #3e5967;
|
||||
background-color: #173239;
|
||||
}
|
||||
|
||||
.ohif-scrollbar::-webkit-scrollbar-thumb:window-inactive {
|
||||
@apply bg-primary-main;
|
||||
background-color: #3e5967;
|
||||
background-color: #173239;
|
||||
}
|
||||
|
||||
/* INVISIBLE SCROLLBAR */
|
||||
|
||||
@@ -46,7 +46,7 @@ const variantClasses = {
|
||||
outlined: {
|
||||
default: '',
|
||||
primary: '',
|
||||
secondary: '',
|
||||
secondary: 'border border-secondary-light',
|
||||
white: '',
|
||||
black:
|
||||
'border-primary-main group-hover:bg-primary-main group-hover:border-black',
|
||||
@@ -68,6 +68,12 @@ const orientationClasses = {
|
||||
const baseDisplayClass = 'inline-flex';
|
||||
const fullWidthDisplayClass = 'flex';
|
||||
|
||||
// css class that are applied for buttons that not the first or last
|
||||
const nonFirstLastClasses = {
|
||||
vertical: 'border-t-0 border-b-0',
|
||||
horizontal: 'border-l-0 last:border-r-0',
|
||||
};
|
||||
|
||||
const ButtonGroup = ({
|
||||
children,
|
||||
className,
|
||||
@@ -78,6 +84,7 @@ const ButtonGroup = ({
|
||||
rounded = 'medium',
|
||||
size = 'medium',
|
||||
variant = 'outlined',
|
||||
splitBorder = true,
|
||||
...other
|
||||
}) => {
|
||||
const ref = useRef(null);
|
||||
@@ -101,13 +108,24 @@ const ButtonGroup = ({
|
||||
ref={ref}
|
||||
{...other}
|
||||
>
|
||||
{React.Children.map(children, child => {
|
||||
{React.Children.map(children, (child, index) => {
|
||||
const isFirst = index === 0;
|
||||
const isLast = index === React.Children.count(children) - 1;
|
||||
const isNotFirstOrLast = !isFirst && !isLast;
|
||||
|
||||
if (!React.isValidElement(child)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return React.cloneElement(child, {
|
||||
className: classnames(buttonClasses, child.props.className),
|
||||
className: classnames(
|
||||
buttonClasses,
|
||||
child.props.className,
|
||||
!splitBorder &&
|
||||
isNotFirstOrLast &&
|
||||
nonFirstLastClasses[orientation],
|
||||
!splitBorder && isLast && 'last:border-l-0'
|
||||
),
|
||||
disabled: child.props.disabled || disabled,
|
||||
color: child.props.color || color,
|
||||
fullWidth,
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import PropTypes, { string } from 'prop-types';
|
||||
import Icon from '../Icon';
|
||||
import Typography from '../Typography';
|
||||
|
||||
/**
|
||||
* REACT CheckBox component
|
||||
* it has two props, checked and onChange
|
||||
* checked is a boolean value
|
||||
* onChange is a function that will be called when the checkbox is clicked
|
||||
*
|
||||
* CheckBox is a component that allows you to use as a boolean
|
||||
*/
|
||||
|
||||
const CheckBox: React.FC<{
|
||||
checked: boolean;
|
||||
onChange: (state) => void;
|
||||
className?: string;
|
||||
label: string;
|
||||
labelClassName?: string;
|
||||
labelVariant?: string;
|
||||
}> = ({
|
||||
checked,
|
||||
onChange,
|
||||
label,
|
||||
labelClassName,
|
||||
labelVariant = 'body',
|
||||
className,
|
||||
}) => {
|
||||
const [isChecked, setIsChecked] = useState(checked);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
setIsChecked(!isChecked);
|
||||
onChange(!isChecked);
|
||||
}, [isChecked, onChange]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center cursor-pointer space-x-1 ${
|
||||
className ? className : ''
|
||||
}`}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{isChecked ? (
|
||||
<Icon name="checkbox-checked" />
|
||||
) : (
|
||||
<Icon name="checkbox-unchecked" />
|
||||
)}
|
||||
|
||||
<Typography
|
||||
variant={labelVariant ?? 'subtitle'}
|
||||
component="p"
|
||||
className={labelClassName ?? 'text-white '}
|
||||
>
|
||||
{label}
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
CheckBox.propTypes = {
|
||||
checked: PropTypes.bool,
|
||||
onChange: PropTypes.func,
|
||||
label: PropTypes.string,
|
||||
labelClassName: PropTypes.string,
|
||||
labelVariant: PropTypes.string,
|
||||
};
|
||||
|
||||
export default CheckBox;
|
||||
@@ -0,0 +1,43 @@
|
||||
import CheckBox from '../CheckBox';
|
||||
import { ArgsTable, Story, Canvas, Meta } from '@storybook/addon-docs';
|
||||
|
||||
export const argTypes = {
|
||||
component: CheckBox,
|
||||
title: 'Components/CheckBox',
|
||||
};
|
||||
|
||||
<Meta title="Components/CheckBox" component={CheckBox} />
|
||||
|
||||
export const CheckBoxTemplate = args => <CheckBox {...args} />
|
||||
|
||||
<Heading title="CheckBox" componentRelativePath="CheckBox/CheckBox.tsx" />
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Props](#props)
|
||||
- [Contribute](#contribute)
|
||||
|
||||
## Overview
|
||||
|
||||
CheckBox is a component that allows you to use as a boolean value
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="Overview"
|
||||
args={{
|
||||
checked: false,
|
||||
onChange: () => console.log('on change callback'),
|
||||
label: "checkBoxLabel",
|
||||
labelClassName: "text-black text-sm",
|
||||
}}
|
||||
>
|
||||
{CheckBoxTemplate.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
## Props
|
||||
|
||||
<ArgsTable of={CheckBox} />
|
||||
|
||||
## Contribute
|
||||
|
||||
<Footer componentRelativePath="CheckBox/__stories__/CheckBox.stories.mdx" />
|
||||
@@ -0,0 +1,3 @@
|
||||
import CheckBox from './CheckBox';
|
||||
|
||||
export default CheckBox;
|
||||
@@ -4,12 +4,12 @@ import classnames from 'classnames';
|
||||
import { Icon, Typography } from '../';
|
||||
|
||||
// TODO: Add loading spinner to OHIF + use it here.
|
||||
const EmptyStudies = ({ className, isLoading }) => {
|
||||
const EmptyStudies = ({ className }) => {
|
||||
return (
|
||||
<div className={classnames('flex-col inline-flex items-center', className)}>
|
||||
<Icon name="magnifier" className="mb-4" />
|
||||
<Typography className="text-primary-light" variant="h5">
|
||||
{!isLoading ? 'No studies available' : 'Loading...'}
|
||||
{'No studies available'}
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -38,6 +38,21 @@ import sortingActiveDown from './../../assets/icons/sorting-active-down.svg';
|
||||
import sortingActiveUp from './../../assets/icons/sorting-active-up.svg';
|
||||
import tracked from './../../assets/icons/tracked.svg';
|
||||
import unlink from './../../assets/icons/unlink.svg';
|
||||
import checkboxChecked from './../../assets/icons/checkbox-checked.svg';
|
||||
import checkboxUnchecked from './../../assets/icons/checkbox-unchecked.svg';
|
||||
import iconNextInactive from './../../assets/icons/icon-next-inactive.svg';
|
||||
import iconNext from './../../assets/icons/icon-next.svg';
|
||||
import iconPrevInactive from './../../assets/icons/icon-prev-inactive.svg';
|
||||
import iconPrev from './../../assets/icons/icon-prev.svg';
|
||||
import navigationPanelRightHide from './../../assets/icons/navigation-panel-right-hide.svg';
|
||||
import navigationPanelRightReveal from './../../assets/icons/navigation-panel-right-reveal.svg';
|
||||
import tabLinear from './../../assets/icons/tab-linear.svg';
|
||||
import tabPatientInfo from './../../assets/icons/tab-patient-info.svg';
|
||||
import tabROIThreshold from './../../assets/icons/tab-roi-threshold.svg';
|
||||
import tabSegmentation from './../../assets/icons/tab-segmentation.svg';
|
||||
import uiArrowDown from './../../assets/icons/ui-arrow-down.svg';
|
||||
import uiArrowUp from './../../assets/icons/ui-arrow-up.svg';
|
||||
import loadingOHIFMark from './../../assets/icons/loading-ohif-mark.svg';
|
||||
|
||||
/** Tools */
|
||||
import toolZoom from './../../assets/icons/tool-zoom.svg';
|
||||
@@ -64,6 +79,21 @@ import toolRectangle from './../../assets/icons/tool-rectangle.svg';
|
||||
import toolFusionColor from './../../assets/icons/tool-fusion-color.svg';
|
||||
import toolCreateThreshold from './../../assets/icons/tool-create-threshold.svg';
|
||||
import editPatient from './../../assets/icons/edit-patient.svg';
|
||||
import panelGroupMore from './../../assets/icons/panel-group-more.svg';
|
||||
import panelGroupOpenClose from './../../assets/icons/panel-group-open-close.svg';
|
||||
import rowAdd from './../../assets/icons/row-add.svg';
|
||||
import rowEdit from './../../assets/icons/row-edit.svg';
|
||||
import rowHidden from './../../assets/icons/row-hidden.svg';
|
||||
import rowHideAll from './../../assets/icons/row-hide-all.svg';
|
||||
import rowHide from './../../assets/icons/row-hide.svg';
|
||||
import rowLocked from './../../assets/icons/row-locked.svg';
|
||||
import rowShowAll from './../../assets/icons/row-show-all.svg';
|
||||
import rowUnhide from './../../assets/icons/row-unhide.svg';
|
||||
import rowUnlocked from './../../assets/icons/row-unlocked.svg';
|
||||
import iconMPR from './../../assets/icons/icon-mpr-alt.svg';
|
||||
import checkboxDefault from './../../assets/icons/checkbox-default.svg';
|
||||
import checkboxActive from './../../assets/icons/checkbox-active.svg';
|
||||
import referenceLines from './../../assets/icons/reference-lines.svg';
|
||||
|
||||
/** Old OHIF */
|
||||
import oldTrash from './../../assets/icons/old-trash.svg';
|
||||
@@ -108,6 +138,20 @@ const ICONS = {
|
||||
sorting: sorting,
|
||||
tracked: tracked,
|
||||
unlink: unlink,
|
||||
'panel-group-more': panelGroupMore,
|
||||
'panel-group-open-close': panelGroupOpenClose,
|
||||
'row-add': rowAdd,
|
||||
'row-edit': rowEdit,
|
||||
'row-hidden': rowHidden,
|
||||
'row-hide-all': rowHideAll,
|
||||
'row-hide': rowHide,
|
||||
'row-locked': rowLocked,
|
||||
'row-show-all': rowShowAll,
|
||||
'row-unhide': rowUnhide,
|
||||
'row-unlocked': rowUnlocked,
|
||||
'checkbox-checked': checkboxChecked,
|
||||
'checkbox-unchecked': checkboxUnchecked,
|
||||
'loading-ohif-mark': loadingOHIFMark,
|
||||
|
||||
/** Tools */
|
||||
'tool-zoom': toolZoom,
|
||||
@@ -134,6 +178,22 @@ const ICONS = {
|
||||
'tool-fusion-color': toolFusionColor,
|
||||
'tool-create-threshold': toolCreateThreshold,
|
||||
'edit-patient': editPatient,
|
||||
'icon-mpr': iconMPR,
|
||||
'icon-next-inactive': iconNextInactive,
|
||||
'icon-next': iconNext,
|
||||
'icon-prev-inactive': iconPrevInactive,
|
||||
'icon-prev': iconPrev,
|
||||
'navigation-panel-right-hide': navigationPanelRightHide,
|
||||
'navigation-panel-right-reveal': navigationPanelRightReveal,
|
||||
'tab-linear': tabLinear,
|
||||
'tab-patient-info': tabPatientInfo,
|
||||
'tab-roi-threshold': tabROIThreshold,
|
||||
'tab-segmentation': tabSegmentation,
|
||||
'ui-arrow-down': uiArrowDown,
|
||||
'ui-arrow-up': uiArrowUp,
|
||||
'checkbox-default': checkboxDefault,
|
||||
'checkbox-active': checkboxActive,
|
||||
'tool-referenceLines': referenceLines,
|
||||
|
||||
/** Old OHIF */
|
||||
'old-trash': oldTrash,
|
||||
|
||||
@@ -8,6 +8,7 @@ class ImageScrollbar extends PureComponent {
|
||||
max: PropTypes.number.isRequired,
|
||||
height: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onContextMenu: PropTypes.func,
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -20,7 +21,7 @@ class ImageScrollbar extends PureComponent {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="scroll">
|
||||
<div className="scroll" onContextMenu={this.props.onContextMenu}>
|
||||
<div className="scroll-holder">
|
||||
<input
|
||||
// adding mousetrap let the mousetrap know about the scrollbar otherwise,
|
||||
@@ -66,4 +67,8 @@ class ImageScrollbar extends PureComponent {
|
||||
};
|
||||
}
|
||||
|
||||
ImageScrollbar.defaultProps = {
|
||||
onContextMenu: e => e.preventDefault(),
|
||||
};
|
||||
|
||||
export default ImageScrollbar;
|
||||
@@ -0,0 +1,17 @@
|
||||
input[type='text'] {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
input[type='text']:focus {
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.up-arrowsize svg {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
}
|
||||
|
||||
.up-arrowsize svg path {
|
||||
fill: #726f7e;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import IconButton from '../IconButton';
|
||||
import Icon from '../Icon';
|
||||
import './InputNumber.css';
|
||||
|
||||
/**
|
||||
* React Number Input component'
|
||||
* it has two props, value and onChange
|
||||
* value is a number value
|
||||
* onChange is a function that will be called when the number input is changed
|
||||
* it can get changed by clicking on the up and down buttons
|
||||
* or by typing a number in the input
|
||||
*/
|
||||
|
||||
const sizesClasses = {
|
||||
sm: 'w-[45px] h-[28px]',
|
||||
};
|
||||
|
||||
const InputNumber: React.FC<{
|
||||
value: number;
|
||||
onChange: (value) => void;
|
||||
step: number;
|
||||
size?: string;
|
||||
className?: string;
|
||||
}> = ({ value, onChange, step = 1, className, size = 'sm' }) => {
|
||||
const [numberValue, setNumberValue] = useState(value);
|
||||
|
||||
const handleChange = useCallback(
|
||||
e => {
|
||||
const numberValue = e.target.value;
|
||||
setNumberValue(numberValue);
|
||||
onChange(numberValue);
|
||||
},
|
||||
[onChange, setNumberValue]
|
||||
);
|
||||
|
||||
const handleIncrement = useCallback(
|
||||
e => {
|
||||
const newNum = Number(numberValue) + step;
|
||||
setNumberValue(newNum);
|
||||
onChange(newNum);
|
||||
},
|
||||
[onChange, setNumberValue, step, numberValue]
|
||||
);
|
||||
|
||||
const handleDecrement = useCallback(
|
||||
e => {
|
||||
const newNum = Number(numberValue) - step;
|
||||
setNumberValue(newNum);
|
||||
onChange(newNum);
|
||||
},
|
||||
[onChange, setNumberValue, step, numberValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center bg-black border-2 px-1 overflow-hidden justify-center border-secondary-light rounded-md ${
|
||||
sizesClasses[size]
|
||||
} ${className ? className : ''}`}
|
||||
>
|
||||
<div className="flex">
|
||||
<input
|
||||
type="text"
|
||||
value={numberValue}
|
||||
onChange={handleChange}
|
||||
className={`bg-black text-white text-[12px] w-full text-center`}
|
||||
/>
|
||||
|
||||
<div className="up-arrowsize flex flex-col items-center justify-around">
|
||||
<IconButton
|
||||
id={'down-arrow-icon'}
|
||||
variant="text"
|
||||
color="inherit"
|
||||
size="initial"
|
||||
className="text-[#726f7e] transform rotate-180"
|
||||
onClick={handleIncrement}
|
||||
>
|
||||
<Icon name="ui-arrow-down" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
id={'down-arrow-icon'}
|
||||
variant="text"
|
||||
color="inherit"
|
||||
size="initial"
|
||||
className="text-[#726f7e]"
|
||||
onClick={handleDecrement}
|
||||
>
|
||||
<Icon name="ui-arrow-down" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputNumber;
|
||||
@@ -0,0 +1,47 @@
|
||||
import InputNumber from '../InputNumber';
|
||||
import { ArgsTable, Story, Canvas, Meta } from '@storybook/addon-docs';
|
||||
|
||||
export const argTypes = {
|
||||
component: InputNumber,
|
||||
title: 'Components/InputNumber',
|
||||
};
|
||||
|
||||
<Meta title="Components/InputNumber" component={InputNumber} />
|
||||
|
||||
export const InputNumberTemplate = args => (
|
||||
<div className="w-16">
|
||||
<InputNumber {...args} />
|
||||
</div>
|
||||
)
|
||||
|
||||
<Heading title="InputNumber" componentRelativePath="InputNumber/InputNumber.tsx" />
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Props](#props)
|
||||
- [Contribute](#contribute)
|
||||
|
||||
## Overview
|
||||
|
||||
InputNumber is a component that allows you to use as a number value
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="Overview"
|
||||
args={{
|
||||
step: 1,
|
||||
value: 3,
|
||||
size: 'sm',
|
||||
onChange: () => console.log('input range change'),
|
||||
}}
|
||||
>
|
||||
{InputNumberTemplate.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
## Props
|
||||
|
||||
<ArgsTable of={InputNumber} />
|
||||
|
||||
## Contribute
|
||||
|
||||
<Footer componentRelativePath="InputNumber/__stories__/InputNumber.stories.mdx" />
|
||||
@@ -0,0 +1,3 @@
|
||||
import InputNumber from './InputNumber';
|
||||
|
||||
export default InputNumber;
|
||||
@@ -0,0 +1,26 @@
|
||||
input[type='range'] {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
input[type='range']::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
border: none;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
border-radius: 50%;
|
||||
background: #5acce6;
|
||||
}
|
||||
|
||||
input[type='range']::-moz-range-thumb {
|
||||
-webkit-appearance: none;
|
||||
border: none;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
border-radius: 50%;
|
||||
background: #5acce6;
|
||||
}
|
||||
|
||||
.input-range-thumb-design[type='range']::-webkit-slider-thumb {
|
||||
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.521);
|
||||
border: 1.5px solid #090c29;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Typography from '../Typography';
|
||||
import './InputRange.css';
|
||||
|
||||
/**
|
||||
* React Range Input component
|
||||
* it has two props, value and onChange
|
||||
* value is a number value
|
||||
* onChange is a function that will be called when the range input is changed
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const InputRange: React.FC<{
|
||||
value: number;
|
||||
onChange: (value) => void;
|
||||
minValue: number;
|
||||
maxValue: number;
|
||||
step: number;
|
||||
unit?: string;
|
||||
containerClassName?: string;
|
||||
inputClassName?: string;
|
||||
labelClassName?: string;
|
||||
labelVariant?: string;
|
||||
}> = ({
|
||||
value,
|
||||
onChange,
|
||||
minValue,
|
||||
maxValue,
|
||||
step = 1,
|
||||
unit = '',
|
||||
containerClassName,
|
||||
inputClassName,
|
||||
labelClassName,
|
||||
labelVariant,
|
||||
}) => {
|
||||
const [rangeValue, setRangeValue] = useState(value);
|
||||
|
||||
const handleChange = useCallback(
|
||||
e => {
|
||||
const rangeValue = e.target.value;
|
||||
setRangeValue(rangeValue);
|
||||
onChange(rangeValue);
|
||||
},
|
||||
[onChange, setRangeValue]
|
||||
);
|
||||
|
||||
const rangeValuePercentage =
|
||||
((rangeValue - minValue) / (maxValue - minValue)) * 100;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center cursor-pointer space-x-1 ${
|
||||
containerClassName ? containerClassName : ''
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
type="range"
|
||||
min={minValue}
|
||||
max={maxValue}
|
||||
value={rangeValue}
|
||||
className={`appearance-none h-[3px] rounded-lg input-range-thumb-design ${
|
||||
inputClassName ? inputClassName : ''
|
||||
}`}
|
||||
style={{
|
||||
background: `linear-gradient(to right, #5acce6 0%, #5acce6 ${rangeValuePercentage -
|
||||
10}%, #3a3f99 ${rangeValuePercentage + 10}%, #3a3f99 100%)`,
|
||||
}}
|
||||
onChange={handleChange}
|
||||
id="myRange"
|
||||
step={step}
|
||||
/>
|
||||
<Typography
|
||||
variant={labelVariant ?? 'subtitle'}
|
||||
component="p"
|
||||
className={classNames('w-8', labelClassName ?? 'text-white')}
|
||||
>
|
||||
{rangeValue}
|
||||
{unit}
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputRange;
|
||||
@@ -0,0 +1,50 @@
|
||||
import InputRange from '../InputRange';
|
||||
import { ArgsTable, Story, Canvas, Meta } from '@storybook/addon-docs';
|
||||
|
||||
export const argTypes = {
|
||||
component: InputRange,
|
||||
title: 'Components/InputRange',
|
||||
};
|
||||
|
||||
<Meta title="Components/InputRange" component={InputRange} />
|
||||
|
||||
export const RangeInputTemplate = args => (
|
||||
<div className="w-3">
|
||||
<InputRange {...args} />
|
||||
</div>
|
||||
)
|
||||
|
||||
<Heading title="InputRange" componentRelativePath="InputRange/InputRange.tsx" />
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Props](#props)
|
||||
- [Contribute](#contribute)
|
||||
|
||||
## Overview
|
||||
|
||||
InputRange is a component that allows you to use as a boolean value
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="Overview"
|
||||
args={{
|
||||
value: 10,
|
||||
minValue: 0,
|
||||
maxValue: 300,
|
||||
step: 10,
|
||||
unit: '%',
|
||||
onChange: () => console.log('input range change'),
|
||||
labelClassName: 'text-black'
|
||||
}}
|
||||
>
|
||||
{RangeInputTemplate.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
## Props
|
||||
|
||||
<ArgsTable of={InputRange} />
|
||||
|
||||
## Contribute
|
||||
|
||||
<Footer componentRelativePath="InputRange/__stories__/InputRange.stories.mdx" />
|
||||
@@ -0,0 +1,3 @@
|
||||
import InputRange from './InputRange';
|
||||
|
||||
export default InputRange;
|
||||
@@ -0,0 +1,793 @@
|
||||
import React from 'react';
|
||||
import Lottie from 'lottie-react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const LoadingAnimation = {
|
||||
v: '5.9.6',
|
||||
fr: 60,
|
||||
ip: 0,
|
||||
op: 106,
|
||||
w: 215,
|
||||
h: 215,
|
||||
nm: 'ohif-progress',
|
||||
ddd: 0,
|
||||
assets: [],
|
||||
layers: [
|
||||
{
|
||||
ddd: 0,
|
||||
ind: 1,
|
||||
ty: 4,
|
||||
nm: 'Layer 1/ohif-shape-new Outlines',
|
||||
sr: 1,
|
||||
ks: {
|
||||
o: { a: 0, k: 100, ix: 11 },
|
||||
r: {
|
||||
a: 1,
|
||||
k: [
|
||||
{
|
||||
i: { x: [0.833], y: [0.797] },
|
||||
o: { x: [0.274], y: [0.135] },
|
||||
t: 25,
|
||||
s: [0],
|
||||
},
|
||||
{
|
||||
i: { x: [0.828], y: [0.97] },
|
||||
o: { x: [0.446], y: [0.039] },
|
||||
t: 52,
|
||||
s: [90],
|
||||
},
|
||||
{
|
||||
i: { x: [0.83], y: [1.207] },
|
||||
o: { x: [0.167], y: [-0.246] },
|
||||
t: 65,
|
||||
s: [90],
|
||||
},
|
||||
{ t: 85, s: [180] },
|
||||
],
|
||||
ix: 10,
|
||||
},
|
||||
p: {
|
||||
a: 1,
|
||||
k: [
|
||||
{
|
||||
i: { x: 0.667, y: 0.667 },
|
||||
o: { x: 0.167, y: 0.167 },
|
||||
t: 0,
|
||||
s: [130.5, 131.75, 0],
|
||||
to: [0, 0, 0],
|
||||
ti: [0, 0, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.833, y: 0.806 },
|
||||
o: { x: 0.167, y: 0.006 },
|
||||
t: 9,
|
||||
s: [130.5, 131.75, 0],
|
||||
to: [2.917, 2.875, 0],
|
||||
ti: [-2.917, -2.875, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.872, y: 0.872 },
|
||||
o: { x: 0.167, y: 0.167 },
|
||||
t: 40,
|
||||
s: [148, 149, 0],
|
||||
to: [0, 0, 0],
|
||||
ti: [0, 0, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.856, y: 0.811 },
|
||||
o: { x: 0.167, y: 0.17 },
|
||||
t: 52,
|
||||
s: [148, 149, 0],
|
||||
to: [-0.881, -0.866, 0],
|
||||
ti: [1.995, 1.961, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.833, y: 0.833 },
|
||||
o: { x: 0.167, y: 0 },
|
||||
t: 76,
|
||||
s: [108.4, 113.023, 0],
|
||||
to: [-1.196, -1.176, 0],
|
||||
ti: [-2.169, -1.843, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.833, y: 0.832 },
|
||||
o: { x: 0.167, y: 0.167 },
|
||||
t: 85,
|
||||
s: [108.4, 113, 0],
|
||||
to: [1.272, 1.081, 0],
|
||||
ti: [-1.819, -1.575, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.833, y: 0.941 },
|
||||
o: { x: 0.167, y: 0.008 },
|
||||
t: 95,
|
||||
s: [115.214, 118.607, 0],
|
||||
to: [7.038, 6.091, 0],
|
||||
ti: [1.198, 1.177, 0],
|
||||
},
|
||||
{ t: 106, s: [130.5, 131.8, 0] },
|
||||
],
|
||||
ix: 2,
|
||||
l: 2,
|
||||
},
|
||||
a: { a: 0, k: [21, 21, 0], ix: 1, l: 2 },
|
||||
s: { a: 0, k: [100, 100, 100], ix: 6, l: 2 },
|
||||
},
|
||||
ao: 0,
|
||||
shapes: [
|
||||
{
|
||||
ty: 'gr',
|
||||
it: [
|
||||
{
|
||||
ind: 0,
|
||||
ty: 'sh',
|
||||
ix: 1,
|
||||
ks: {
|
||||
a: 0,
|
||||
k: {
|
||||
i: [
|
||||
[-1.657, 0],
|
||||
[0, 0],
|
||||
[0, -1.657],
|
||||
[0, 0],
|
||||
[1.657, 0],
|
||||
[0, 0],
|
||||
[0, 1.657],
|
||||
[0, 0],
|
||||
],
|
||||
o: [
|
||||
[0, 0],
|
||||
[1.657, 0],
|
||||
[0, 0],
|
||||
[0, 1.657],
|
||||
[0, 0],
|
||||
[-1.657, 0],
|
||||
[0, 0],
|
||||
[0, -1.657],
|
||||
],
|
||||
v: [
|
||||
[-13, -16],
|
||||
[13, -16],
|
||||
[16, -13],
|
||||
[16, 13],
|
||||
[13, 16],
|
||||
[-13, 16],
|
||||
[-16, 13],
|
||||
[-16, -13],
|
||||
],
|
||||
c: true,
|
||||
},
|
||||
ix: 2,
|
||||
},
|
||||
nm: 'Path 1',
|
||||
mn: 'ADBE Vector Shape - Group',
|
||||
hd: false,
|
||||
},
|
||||
{
|
||||
ty: 'st',
|
||||
c: { a: 0, k: [1, 1, 1, 1], ix: 3 },
|
||||
o: { a: 0, k: 100, ix: 4 },
|
||||
w: { a: 0, k: 2, ix: 5 },
|
||||
lc: 1,
|
||||
lj: 1,
|
||||
ml: 10,
|
||||
bm: 0,
|
||||
nm: 'Stroke 1',
|
||||
mn: 'ADBE Vector Graphic - Stroke',
|
||||
hd: false,
|
||||
},
|
||||
{
|
||||
ty: 'tr',
|
||||
p: { a: 0, k: [21, 21], ix: 2 },
|
||||
a: { a: 0, k: [0, 0], ix: 1 },
|
||||
s: { a: 0, k: [100, 100], ix: 3 },
|
||||
r: { a: 0, k: 0, ix: 6 },
|
||||
o: { a: 0, k: 100, ix: 7 },
|
||||
sk: { a: 0, k: 0, ix: 4 },
|
||||
sa: { a: 0, k: 0, ix: 5 },
|
||||
nm: 'Transform',
|
||||
},
|
||||
],
|
||||
nm: 'Group 1',
|
||||
np: 2,
|
||||
cix: 2,
|
||||
bm: 0,
|
||||
ix: 1,
|
||||
mn: 'ADBE Vector Group',
|
||||
hd: false,
|
||||
},
|
||||
],
|
||||
ip: 0,
|
||||
op: 106,
|
||||
st: 0,
|
||||
ct: 1,
|
||||
bm: 0,
|
||||
},
|
||||
{
|
||||
ddd: 0,
|
||||
ind: 3,
|
||||
ty: 4,
|
||||
nm: 'Layer 1/ohif-shape-new Outlines',
|
||||
sr: 1,
|
||||
ks: {
|
||||
o: { a: 0, k: 100, ix: 11 },
|
||||
r: {
|
||||
a: 1,
|
||||
k: [
|
||||
{
|
||||
i: { x: [0.833], y: [0.79] },
|
||||
o: { x: [0.278], y: [0.149] },
|
||||
t: 25,
|
||||
s: [0],
|
||||
},
|
||||
{
|
||||
i: { x: [0.828], y: [1.005] },
|
||||
o: { x: [0.446], y: [0.022] },
|
||||
t: 52,
|
||||
s: [90],
|
||||
},
|
||||
{
|
||||
i: { x: [0.83], y: [1.207] },
|
||||
o: { x: [0.167], y: [-0.246] },
|
||||
t: 65,
|
||||
s: [90],
|
||||
},
|
||||
{ t: 85, s: [180] },
|
||||
],
|
||||
ix: 10,
|
||||
},
|
||||
p: {
|
||||
a: 1,
|
||||
k: [
|
||||
{
|
||||
i: { x: 0.833, y: 0.833 },
|
||||
o: { x: 0.167, y: 0.167 },
|
||||
t: 0,
|
||||
s: [81, 132, 0],
|
||||
to: [0, 0, 0],
|
||||
ti: [0, 0, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.833, y: 0.808 },
|
||||
o: { x: 0.167, y: 0.011 },
|
||||
t: 9,
|
||||
s: [81, 132, 0],
|
||||
to: [-3, 3, 0],
|
||||
ti: [3, -3, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.872, y: 0.872 },
|
||||
o: { x: 0.167, y: 0.167 },
|
||||
t: 40,
|
||||
s: [63, 150, 0],
|
||||
to: [0, 0, 0],
|
||||
ti: [0, 0, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.856, y: 0.812 },
|
||||
o: { x: 0.167, y: 0.174 },
|
||||
t: 52,
|
||||
s: [63, 150, 0],
|
||||
to: [0.906, -0.906, 0],
|
||||
ti: [-2.052, 2.052, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.833, y: 0.833 },
|
||||
o: { x: 0.167, y: 0.002 },
|
||||
t: 76,
|
||||
s: [108.685, 113.315, 0],
|
||||
to: [1.217, -1.217, 0],
|
||||
ti: [2.674, -1.792, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.833, y: 0.834 },
|
||||
o: { x: 0.167, y: 0.167 },
|
||||
t: 85,
|
||||
s: [108.7, 113.3, 0],
|
||||
to: [-1.593, 1.067, 0],
|
||||
ti: [2.246, -1.581, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.833, y: 0.942 },
|
||||
o: { x: 0.167, y: 0.014 },
|
||||
t: 95,
|
||||
s: [99.965, 118.742, 0],
|
||||
to: [-8.674, 6.104, 0],
|
||||
ti: [-1.236, 1.236, 0],
|
||||
},
|
||||
{ t: 106, s: [81, 132, 0] },
|
||||
],
|
||||
ix: 2,
|
||||
l: 2,
|
||||
},
|
||||
a: { a: 0, k: [21, 21, 0], ix: 1, l: 2 },
|
||||
s: { a: 0, k: [100, 100, 100], ix: 6, l: 2 },
|
||||
},
|
||||
ao: 0,
|
||||
shapes: [
|
||||
{
|
||||
ty: 'gr',
|
||||
it: [
|
||||
{
|
||||
ind: 0,
|
||||
ty: 'sh',
|
||||
ix: 1,
|
||||
ks: {
|
||||
a: 0,
|
||||
k: {
|
||||
i: [
|
||||
[-1.657, 0],
|
||||
[0, 0],
|
||||
[0, -1.657],
|
||||
[0, 0],
|
||||
[1.657, 0],
|
||||
[0, 0],
|
||||
[0, 1.657],
|
||||
[0, 0],
|
||||
],
|
||||
o: [
|
||||
[0, 0],
|
||||
[1.657, 0],
|
||||
[0, 0],
|
||||
[0, 1.657],
|
||||
[0, 0],
|
||||
[-1.657, 0],
|
||||
[0, 0],
|
||||
[0, -1.657],
|
||||
],
|
||||
v: [
|
||||
[-13, -16],
|
||||
[13, -16],
|
||||
[16, -13],
|
||||
[16, 13],
|
||||
[13, 16],
|
||||
[-13, 16],
|
||||
[-16, 13],
|
||||
[-16, -13],
|
||||
],
|
||||
c: true,
|
||||
},
|
||||
ix: 2,
|
||||
},
|
||||
nm: 'Path 1',
|
||||
mn: 'ADBE Vector Shape - Group',
|
||||
hd: false,
|
||||
},
|
||||
{
|
||||
ty: 'st',
|
||||
c: { a: 0, k: [1, 1, 1, 1], ix: 3 },
|
||||
o: { a: 0, k: 100, ix: 4 },
|
||||
w: { a: 0, k: 2, ix: 5 },
|
||||
lc: 1,
|
||||
lj: 1,
|
||||
ml: 10,
|
||||
bm: 0,
|
||||
nm: 'Stroke 1',
|
||||
mn: 'ADBE Vector Graphic - Stroke',
|
||||
hd: false,
|
||||
},
|
||||
{
|
||||
ty: 'tr',
|
||||
p: { a: 0, k: [21, 21], ix: 2 },
|
||||
a: { a: 0, k: [0, 0], ix: 1 },
|
||||
s: { a: 0, k: [100, 100], ix: 3 },
|
||||
r: { a: 0, k: 0, ix: 6 },
|
||||
o: { a: 0, k: 100, ix: 7 },
|
||||
sk: { a: 0, k: 0, ix: 4 },
|
||||
sa: { a: 0, k: 0, ix: 5 },
|
||||
nm: 'Transform',
|
||||
},
|
||||
],
|
||||
nm: 'Group 1',
|
||||
np: 2,
|
||||
cix: 2,
|
||||
bm: 0,
|
||||
ix: 1,
|
||||
mn: 'ADBE Vector Group',
|
||||
hd: false,
|
||||
},
|
||||
],
|
||||
ip: 0,
|
||||
op: 106,
|
||||
st: 0,
|
||||
ct: 1,
|
||||
bm: 0,
|
||||
},
|
||||
{
|
||||
ddd: 0,
|
||||
ind: 5,
|
||||
ty: 4,
|
||||
nm: 'Layer 1/ohif-shape-new Outlines',
|
||||
sr: 1,
|
||||
ks: {
|
||||
o: { a: 0, k: 100, ix: 11 },
|
||||
r: {
|
||||
a: 1,
|
||||
k: [
|
||||
{
|
||||
i: { x: [0.833], y: [0.79] },
|
||||
o: { x: [0.278], y: [0.149] },
|
||||
t: 25,
|
||||
s: [0],
|
||||
},
|
||||
{
|
||||
i: { x: [0.828], y: [0.943] },
|
||||
o: { x: [0.446], y: [0.022] },
|
||||
t: 52,
|
||||
s: [90],
|
||||
},
|
||||
{
|
||||
i: { x: [0.83], y: [1.207] },
|
||||
o: { x: [0.167], y: [-0.246] },
|
||||
t: 65,
|
||||
s: [90],
|
||||
},
|
||||
{ t: 85, s: [180] },
|
||||
],
|
||||
ix: 10,
|
||||
},
|
||||
p: {
|
||||
a: 1,
|
||||
k: [
|
||||
{
|
||||
i: { x: 0.833, y: 0.833 },
|
||||
o: { x: 0.167, y: 0.167 },
|
||||
t: 0,
|
||||
s: [131, 84.75, 0],
|
||||
to: [0, 0, 0],
|
||||
ti: [0, 0, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.833, y: 0.808 },
|
||||
o: { x: 0.167, y: 0.014 },
|
||||
t: 9,
|
||||
s: [131, 84.75, 0],
|
||||
to: [3, -3.125, 0],
|
||||
ti: [-3, 3.125, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.872, y: 0.872 },
|
||||
o: { x: 0.167, y: 0.167 },
|
||||
t: 40,
|
||||
s: [149, 66, 0],
|
||||
to: [0, 0, 0],
|
||||
ti: [0, 0, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.856, y: 0.812 },
|
||||
o: { x: 0.167, y: 0.175 },
|
||||
t: 52,
|
||||
s: [149, 66, 0],
|
||||
to: [-0.906, 0.946, 0],
|
||||
ti: [2.052, -2.143, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.833, y: 0.833 },
|
||||
o: { x: 0.167, y: 0.013 },
|
||||
t: 76,
|
||||
s: [109.315, 112.893, 0],
|
||||
to: [-1.222, 1.276, 0],
|
||||
ti: [-2.102, 2.732, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.833, y: 0.835 },
|
||||
o: { x: 0.167, y: 0.167 },
|
||||
t: 85,
|
||||
s: [109.3, 112.9, 0],
|
||||
to: [1.242, -1.615, 0],
|
||||
ti: [-1.796, 2.277, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.833, y: 0.943 },
|
||||
o: { x: 0.167, y: 0.016 },
|
||||
t: 95,
|
||||
s: [115.852, 104.071, 0],
|
||||
to: [6.96, -8.823, 0],
|
||||
ti: [1.236, -1.29, 0],
|
||||
},
|
||||
{ t: 106, s: [131, 84.8, 0] },
|
||||
],
|
||||
ix: 2,
|
||||
l: 2,
|
||||
},
|
||||
a: { a: 0, k: [21, 21, 0], ix: 1, l: 2 },
|
||||
s: { a: 0, k: [100, 100, 100], ix: 6, l: 2 },
|
||||
},
|
||||
ao: 0,
|
||||
shapes: [
|
||||
{
|
||||
ty: 'gr',
|
||||
it: [
|
||||
{
|
||||
ind: 0,
|
||||
ty: 'sh',
|
||||
ix: 1,
|
||||
ks: {
|
||||
a: 0,
|
||||
k: {
|
||||
i: [
|
||||
[-1.657, 0],
|
||||
[0, 0],
|
||||
[0, -1.657],
|
||||
[0, 0],
|
||||
[1.657, 0],
|
||||
[0, 0],
|
||||
[0, 1.657],
|
||||
[0, 0],
|
||||
],
|
||||
o: [
|
||||
[0, 0],
|
||||
[1.657, 0],
|
||||
[0, 0],
|
||||
[0, 1.657],
|
||||
[0, 0],
|
||||
[-1.657, 0],
|
||||
[0, 0],
|
||||
[0, -1.657],
|
||||
],
|
||||
v: [
|
||||
[-13, -16],
|
||||
[13, -16],
|
||||
[16, -13],
|
||||
[16, 13],
|
||||
[13, 16],
|
||||
[-13, 16],
|
||||
[-16, 13],
|
||||
[-16, -13],
|
||||
],
|
||||
c: true,
|
||||
},
|
||||
ix: 2,
|
||||
},
|
||||
nm: 'Path 1',
|
||||
mn: 'ADBE Vector Shape - Group',
|
||||
hd: false,
|
||||
},
|
||||
{
|
||||
ty: 'st',
|
||||
c: { a: 0, k: [1, 1, 1, 1], ix: 3 },
|
||||
o: { a: 0, k: 100, ix: 4 },
|
||||
w: { a: 0, k: 2, ix: 5 },
|
||||
lc: 1,
|
||||
lj: 1,
|
||||
ml: 10,
|
||||
bm: 0,
|
||||
nm: 'Stroke 1',
|
||||
mn: 'ADBE Vector Graphic - Stroke',
|
||||
hd: false,
|
||||
},
|
||||
{
|
||||
ty: 'tr',
|
||||
p: { a: 0, k: [21, 21], ix: 2 },
|
||||
a: { a: 0, k: [0, 0], ix: 1 },
|
||||
s: { a: 0, k: [100, 100], ix: 3 },
|
||||
r: { a: 0, k: 0, ix: 6 },
|
||||
o: { a: 0, k: 100, ix: 7 },
|
||||
sk: { a: 0, k: 0, ix: 4 },
|
||||
sa: { a: 0, k: 0, ix: 5 },
|
||||
nm: 'Transform',
|
||||
},
|
||||
],
|
||||
nm: 'Group 1',
|
||||
np: 2,
|
||||
cix: 2,
|
||||
bm: 0,
|
||||
ix: 1,
|
||||
mn: 'ADBE Vector Group',
|
||||
hd: false,
|
||||
},
|
||||
],
|
||||
ip: 0,
|
||||
op: 106,
|
||||
st: 0,
|
||||
ct: 1,
|
||||
bm: 0,
|
||||
},
|
||||
{
|
||||
ddd: 0,
|
||||
ind: 7,
|
||||
ty: 4,
|
||||
nm: 'Layer 1/ohif-shape-new Outlines',
|
||||
sr: 1,
|
||||
ks: {
|
||||
o: { a: 0, k: 100, ix: 11 },
|
||||
r: {
|
||||
a: 1,
|
||||
k: [
|
||||
{
|
||||
i: { x: [0.833], y: [0.79] },
|
||||
o: { x: [0.278], y: [0.149] },
|
||||
t: 25,
|
||||
s: [0],
|
||||
},
|
||||
{
|
||||
i: { x: [0.823], y: [1] },
|
||||
o: { x: [0.446], y: [-0.058] },
|
||||
t: 52,
|
||||
s: [90],
|
||||
},
|
||||
{
|
||||
i: { x: [0.83], y: [1.207] },
|
||||
o: { x: [0.167], y: [-0.246] },
|
||||
t: 65,
|
||||
s: [90],
|
||||
},
|
||||
{ t: 85, s: [180] },
|
||||
],
|
||||
ix: 10,
|
||||
},
|
||||
p: {
|
||||
a: 1,
|
||||
k: [
|
||||
{
|
||||
i: { x: 0.833, y: 0.833 },
|
||||
o: { x: 0.167, y: 0.167 },
|
||||
t: 0,
|
||||
s: [80.5, 85, 0],
|
||||
to: [0, 0, 0],
|
||||
ti: [0, 0, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.833, y: 0.806 },
|
||||
o: { x: 0.167, y: 0.005 },
|
||||
t: 9,
|
||||
s: [80.5, 85, 0],
|
||||
to: [-2.75, -3, 0],
|
||||
ti: [2.75, 3, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.872, y: 0.872 },
|
||||
o: { x: 0.167, y: 0.167 },
|
||||
t: 40,
|
||||
s: [64, 67, 0],
|
||||
to: [0, 0, 0],
|
||||
ti: [0, 0, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.856, y: 0.812 },
|
||||
o: { x: 0.167, y: 0.177 },
|
||||
t: 52,
|
||||
s: [64, 67, 0],
|
||||
to: [0.831, 0.906, 0],
|
||||
ti: [-1.881, -2.052, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.833, y: 0.833 },
|
||||
o: { x: 0.167, y: 0.025 },
|
||||
t: 76,
|
||||
s: [109.295, 112.685, 0],
|
||||
to: [1.112, 1.213, 0],
|
||||
ti: [2.768, 2.657, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.833, y: 0.837 },
|
||||
o: { x: 0.167, y: 0.167 },
|
||||
t: 85,
|
||||
s: [109.3, 112.7, 0],
|
||||
to: [-1.648, -1.581, 0],
|
||||
ti: [2.299, 2.234, 0],
|
||||
},
|
||||
{
|
||||
i: { x: 0.833, y: 0.943 },
|
||||
o: { x: 0.167, y: 0.019 },
|
||||
t: 95,
|
||||
s: [100.116, 104.028, 0],
|
||||
to: [-8.935, -8.681, 0],
|
||||
ti: [-1.136, -1.239, 0],
|
||||
},
|
||||
{ t: 106, s: [80.5, 85, 0] },
|
||||
],
|
||||
ix: 2,
|
||||
l: 2,
|
||||
},
|
||||
a: { a: 0, k: [21, 21, 0], ix: 1, l: 2 },
|
||||
s: { a: 0, k: [100, 100, 100], ix: 6, l: 2 },
|
||||
},
|
||||
ao: 0,
|
||||
shapes: [
|
||||
{
|
||||
ty: 'gr',
|
||||
it: [
|
||||
{
|
||||
ind: 0,
|
||||
ty: 'sh',
|
||||
ix: 1,
|
||||
ks: {
|
||||
a: 0,
|
||||
k: {
|
||||
i: [
|
||||
[-1.657, 0],
|
||||
[0, 0],
|
||||
[0, -1.657],
|
||||
[0, 0],
|
||||
[1.657, 0],
|
||||
[0, 0],
|
||||
[0, 1.657],
|
||||
[0, 0],
|
||||
],
|
||||
o: [
|
||||
[0, 0],
|
||||
[1.657, 0],
|
||||
[0, 0],
|
||||
[0, 1.657],
|
||||
[0, 0],
|
||||
[-1.657, 0],
|
||||
[0, 0],
|
||||
[0, -1.657],
|
||||
],
|
||||
v: [
|
||||
[-13, -16],
|
||||
[13, -16],
|
||||
[16, -13],
|
||||
[16, 13],
|
||||
[13, 16],
|
||||
[-13, 16],
|
||||
[-16, 13],
|
||||
[-16, -13],
|
||||
],
|
||||
c: true,
|
||||
},
|
||||
ix: 2,
|
||||
},
|
||||
nm: 'Path 1',
|
||||
mn: 'ADBE Vector Shape - Group',
|
||||
hd: false,
|
||||
},
|
||||
{
|
||||
ty: 'st',
|
||||
c: { a: 0, k: [1, 1, 1, 1], ix: 3 },
|
||||
o: { a: 0, k: 100, ix: 4 },
|
||||
w: { a: 0, k: 2, ix: 5 },
|
||||
lc: 1,
|
||||
lj: 1,
|
||||
ml: 10,
|
||||
bm: 0,
|
||||
nm: 'Stroke 1',
|
||||
mn: 'ADBE Vector Graphic - Stroke',
|
||||
hd: false,
|
||||
},
|
||||
{
|
||||
ty: 'tr',
|
||||
p: { a: 0, k: [21, 21], ix: 2 },
|
||||
a: { a: 0, k: [0, 0], ix: 1 },
|
||||
s: { a: 0, k: [100, 100], ix: 3 },
|
||||
r: { a: 0, k: 0, ix: 6 },
|
||||
o: { a: 0, k: 100, ix: 7 },
|
||||
sk: { a: 0, k: 0, ix: 4 },
|
||||
sa: { a: 0, k: 0, ix: 5 },
|
||||
nm: 'Transform',
|
||||
},
|
||||
],
|
||||
nm: 'Group 1',
|
||||
np: 2,
|
||||
cix: 2,
|
||||
bm: 0,
|
||||
ix: 1,
|
||||
mn: 'ADBE Vector Group',
|
||||
hd: false,
|
||||
},
|
||||
],
|
||||
ip: 0,
|
||||
op: 106,
|
||||
st: 0,
|
||||
ct: 1,
|
||||
bm: 0,
|
||||
},
|
||||
],
|
||||
markers: [],
|
||||
};
|
||||
|
||||
const LoadingIndicator = ({ className }) => {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'absolute z-50 top-0 left-0 flex flex-col items-center justify-center',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Lottie animationData={LoadingAnimation} />;
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoadingIndicator;
|
||||
@@ -0,0 +1,2 @@
|
||||
import LoadingIndicator from './LoadingIndicator';
|
||||
export default LoadingIndicator;
|
||||
@@ -0,0 +1,34 @@
|
||||
.loading {
|
||||
background-color: #091731;
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 12em;
|
||||
}
|
||||
|
||||
.infinite-loading-bar {
|
||||
animation: side2side 2s ease-in-out infinite;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
border-radius: 4px;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.loading-bar {
|
||||
animation: side2side 2s ease-in-out infinite;
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
@keyframes side2side {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
50% {
|
||||
transform: translateX(150%);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Icon } from '@ohif/ui';
|
||||
|
||||
import './LoadingIndicatorProgress.css';
|
||||
|
||||
/**
|
||||
* A React component that renders a loading indicator.
|
||||
* if progress is not provided, it will render an infinite loading indicator
|
||||
* if progress is provided, it will render a progress bar
|
||||
* Optionally a textBlock can be provided to display a message
|
||||
*/
|
||||
function LoadingIndicatorProgress({ className, textBlock, progress }) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'absolute z-50 top-0 left-0 flex flex-col items-center justify-center space-y-5',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Icon name="loading-ohif-mark" className="text-white w-12 h-12" />
|
||||
<div className="loading">
|
||||
{progress === undefined || progress === null ? (
|
||||
<div className="infinite-loading-bar bg-primary-light"></div>
|
||||
) : (
|
||||
<div
|
||||
className="bg-primary-light"
|
||||
style={{
|
||||
width: `${progress}%`,
|
||||
height: '8px',
|
||||
}}
|
||||
></div>
|
||||
)}
|
||||
</div>
|
||||
{textBlock}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default LoadingIndicatorProgress;
|
||||
@@ -0,0 +1,2 @@
|
||||
import LoadingIndicatorProgress from './LoadingIndicatorProgress';
|
||||
export default LoadingIndicatorProgress;
|
||||
@@ -4,8 +4,9 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import MeasurementItem from './MeasurementItem';
|
||||
|
||||
const MeasurementTable = ({ data, title, amount, onClick, onEdit }) => {
|
||||
const MeasurementTable = ({ data, title, onClick, onEdit }) => {
|
||||
const { t } = useTranslation('MeasurementTable');
|
||||
const amount = data.length;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -45,7 +46,6 @@ const MeasurementTable = ({ data, title, amount, onClick, onEdit }) => {
|
||||
};
|
||||
|
||||
MeasurementTable.defaultProps = {
|
||||
amount: null,
|
||||
data: [],
|
||||
onClick: () => {},
|
||||
onEdit: () => {},
|
||||
@@ -53,7 +53,6 @@ MeasurementTable.defaultProps = {
|
||||
|
||||
MeasurementTable.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
amount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
data: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
uid: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
|
||||
@@ -9,7 +9,7 @@ const NavBar = ({ className, children, isSticky }) => {
|
||||
return (
|
||||
<div
|
||||
className={classnames(
|
||||
'flex flex-row items-center bg-secondary-dark px-3 border-b-4 border-black z-20',
|
||||
'flex flex-row items-center bg-secondary-dark px-1 border-b-4 border-black z-20',
|
||||
isSticky && stickyClasses,
|
||||
!isSticky && notStickyClasses,
|
||||
className
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Icon, Tooltip } from '../';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const classes = {
|
||||
infoHeader: 'text-base text-primary-light',
|
||||
infoText: 'text-base text-white max-w-24 truncate',
|
||||
firstRow: 'flex flex-col',
|
||||
row: 'flex flex-col ml-4',
|
||||
};
|
||||
|
||||
function PatientInfo({
|
||||
patientName,
|
||||
patientSex,
|
||||
patientAge,
|
||||
MRN,
|
||||
thickness,
|
||||
spacing,
|
||||
scanner,
|
||||
isOpen,
|
||||
showPatientInfoRef,
|
||||
}) {
|
||||
const { t } = useTranslation('PatientInfo');
|
||||
|
||||
while (patientAge.charAt(0) === '0') {
|
||||
patientAge = patientAge.substr(1);
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={showPatientInfoRef}>
|
||||
<Tooltip
|
||||
isSticky
|
||||
isDisabled={!isOpen}
|
||||
position="bottom-right"
|
||||
content={
|
||||
isOpen && (
|
||||
<div className="flex py-2">
|
||||
<div className="flex pt-1">
|
||||
<Icon name="info-link" className="w-4 text-primary-main" />
|
||||
</div>
|
||||
<div className="flex flex-col ml-2">
|
||||
<span
|
||||
className="text-base font-bold text-white"
|
||||
title={patientName}
|
||||
>
|
||||
{patientName}
|
||||
</span>
|
||||
<div className="flex pb-4 mt-4 mb-4 border-b border-secondary-main">
|
||||
<div className={classnames(classes.firstRow)}>
|
||||
<span className={classnames(classes.infoHeader)}>
|
||||
{t('Sex')}
|
||||
</span>
|
||||
<span
|
||||
className={classnames(classes.infoText)}
|
||||
title={patientSex}
|
||||
>
|
||||
{patientSex}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classnames(classes.row)}>
|
||||
<span className={classnames(classes.infoHeader)}>
|
||||
{t('Age')}
|
||||
</span>
|
||||
<span
|
||||
className={classnames(classes.infoText)}
|
||||
title={patientAge}
|
||||
>
|
||||
{patientAge}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classnames(classes.row)}>
|
||||
<span className={classnames(classes.infoHeader)}>
|
||||
{t('MRN')}
|
||||
</span>
|
||||
<span className={classnames(classes.infoText)} title={MRN}>
|
||||
{MRN}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<div className={classnames(classes.firstRow)}>
|
||||
<span className={classnames(classes.infoHeader)}>
|
||||
{t('Thickness')}
|
||||
</span>
|
||||
<span
|
||||
className={classnames(classes.infoText)}
|
||||
title={thickness}
|
||||
>
|
||||
{thickness}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classnames(classes.row)}>
|
||||
<span className={classnames(classes.infoHeader)}>
|
||||
{t('Spacing')}
|
||||
</span>
|
||||
<span
|
||||
className={classnames(classes.infoText)}
|
||||
title={spacing}
|
||||
>
|
||||
{spacing}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classnames(classes.row)}>
|
||||
<span className={classnames(classes.infoHeader)}>
|
||||
{t('Scanner')}
|
||||
</span>
|
||||
<span
|
||||
className={classnames(classes.infoText)}
|
||||
title={scanner}
|
||||
>
|
||||
{scanner}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
>
|
||||
<div className="relative flex justify-end cursor-pointer">
|
||||
<div className="relative">
|
||||
<Icon name="profile" className="w-5 text-white" />
|
||||
<Icon
|
||||
name="info-link"
|
||||
className="absolute w-5 text-white bg-black"
|
||||
style={{ right: -7, bottom: -10 }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
PatientInfo.propTypes = {
|
||||
patientName: PropTypes.string,
|
||||
patientSex: PropTypes.string,
|
||||
patientAge: PropTypes.string,
|
||||
MRN: PropTypes.string,
|
||||
thickness: PropTypes.string,
|
||||
spacing: PropTypes.string,
|
||||
scanner: PropTypes.string,
|
||||
isOpen: PropTypes.bool,
|
||||
showPatientInfoRef: PropTypes.object,
|
||||
};
|
||||
|
||||
export default PatientInfo;
|
||||
@@ -0,0 +1,2 @@
|
||||
import PatientInfo from './PatientInfo';
|
||||
export default PatientInfo;
|
||||
@@ -0,0 +1,256 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Icon, InputRange, CheckBox, InputNumber } from '../';
|
||||
import classNames from 'classnames';
|
||||
import { reducer } from './segmentationConfigReducer';
|
||||
|
||||
const ActiveSegmentationConfig = ({
|
||||
config,
|
||||
dispatch,
|
||||
setRenderOutline,
|
||||
setOutlineOpacityActive,
|
||||
setOutlineWidthActive,
|
||||
setRenderFill,
|
||||
setFillAlpha,
|
||||
usePercentage,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex justify-between text-[12px] pt-[13px] px-2">
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="text-white mb-[12px]">Active</div>
|
||||
<CheckBox
|
||||
label="Outline"
|
||||
checked={config.renderOutline}
|
||||
labelClassName="text-[12px] pl-1 pt-1"
|
||||
className="mb-[9px]"
|
||||
onChange={value => {
|
||||
dispatch({
|
||||
type: 'RENDER_OUTLINE',
|
||||
payload: {
|
||||
value,
|
||||
},
|
||||
});
|
||||
|
||||
setRenderOutline(value);
|
||||
}}
|
||||
/>
|
||||
<CheckBox
|
||||
label="Fill"
|
||||
checked={config.renderFill}
|
||||
labelClassName="text-[12px] pl-1 pt-1"
|
||||
className="mb-[9px]"
|
||||
onChange={value => {
|
||||
dispatch({
|
||||
type: 'RENDER_FILL',
|
||||
payload: {
|
||||
value,
|
||||
},
|
||||
});
|
||||
|
||||
setRenderFill(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center col-span-2">
|
||||
<div className="text-[#b3b3b3] text-[10px] mb-[12px]">Opacity</div>
|
||||
<InputRange
|
||||
minValue={0}
|
||||
maxValue={usePercentage ? 100 : 1}
|
||||
value={
|
||||
usePercentage ? config.outlineOpacity * 100 : config.outlineOpacity
|
||||
}
|
||||
onChange={value => {
|
||||
dispatch({
|
||||
type: 'SET_OUTLINE_OPACITY',
|
||||
payload: {
|
||||
value: value,
|
||||
},
|
||||
});
|
||||
|
||||
setOutlineOpacityActive(value);
|
||||
}}
|
||||
step={1}
|
||||
containerClassName="mt-[4px] mb-[9px]"
|
||||
inputClassName="w-[64px]"
|
||||
labelClassName="text-white text-[12px]"
|
||||
unit="%"
|
||||
/>
|
||||
<InputRange
|
||||
minValue={0}
|
||||
maxValue={usePercentage ? 100 : 1}
|
||||
value={usePercentage ? config.fillAlpha * 100 : config.fillAlpha}
|
||||
onChange={value => {
|
||||
dispatch({
|
||||
type: 'SET_FILL_ALPHA',
|
||||
payload: {
|
||||
value,
|
||||
},
|
||||
});
|
||||
|
||||
setFillAlpha(value);
|
||||
}}
|
||||
step={1}
|
||||
containerClassName="mt-[4px] mb-[9px]"
|
||||
inputClassName="w-[64px]"
|
||||
labelClassName="text-white text-[12px]"
|
||||
unit="%"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="text-[#b3b3b3] text-[10px] mb-[12px]">Size</div>
|
||||
<InputNumber
|
||||
value={config.outlineWidthActive}
|
||||
onChange={value => {
|
||||
dispatch({
|
||||
type: 'SET_OUTLINE_WIDTH',
|
||||
payload: {
|
||||
value,
|
||||
},
|
||||
});
|
||||
|
||||
setOutlineWidthActive(value);
|
||||
}}
|
||||
className="-mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const InactiveSegmentationConfig = ({
|
||||
config,
|
||||
dispatch,
|
||||
setRenderInactiveSegmentations,
|
||||
setFillAlphaInactive,
|
||||
usePercentage,
|
||||
}) => {
|
||||
return (
|
||||
<div className="px-2">
|
||||
<CheckBox
|
||||
label="Display Inactive Segmentations"
|
||||
checked={config.renderInactiveSegmentations}
|
||||
labelClassName="text-[12px] pt-1"
|
||||
className="mb-[9px]"
|
||||
onChange={value => {
|
||||
dispatch({
|
||||
type: 'RENDER_INACTIVE_SEGMENTATIONS',
|
||||
payload: {
|
||||
value,
|
||||
},
|
||||
});
|
||||
|
||||
setRenderInactiveSegmentations(value);
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex pl-4 items-center space-x-2">
|
||||
<span className="text-[10px] text-[#b3b3b3]">Opacity</span>
|
||||
<InputRange
|
||||
minValue={0}
|
||||
maxValue={usePercentage ? 100 : 1}
|
||||
value={
|
||||
usePercentage
|
||||
? config.fillAlphaInactive * 100
|
||||
: config.fillAlphaInactive
|
||||
}
|
||||
onChange={value => {
|
||||
dispatch({
|
||||
type: 'SET_FILL_ALPHA_INACTIVE',
|
||||
payload: {
|
||||
value: value,
|
||||
},
|
||||
});
|
||||
|
||||
setFillAlphaInactive(value);
|
||||
}}
|
||||
step={1}
|
||||
containerClassName="mt-[4px]"
|
||||
inputClassName="w-[64px]"
|
||||
labelClassName="text-white text-[12px]"
|
||||
unit="%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SegmentationConfig = ({
|
||||
segmentationConfig,
|
||||
setFillAlpha,
|
||||
setFillAlphaInactive,
|
||||
setOutlineWidthActive,
|
||||
setOutlineOpacityActive,
|
||||
setRenderFill,
|
||||
setRenderInactiveSegmentations,
|
||||
setRenderOutline,
|
||||
}) => {
|
||||
const [config, dispatch] = React.useReducer(
|
||||
reducer,
|
||||
segmentationConfig.initialConfig
|
||||
);
|
||||
|
||||
const [isMinimized, setIsMinimized] = useState(true);
|
||||
return (
|
||||
<div className="bg-primary-dark">
|
||||
<div
|
||||
className="flex cursor-pointer items-center"
|
||||
onClick={evt => {
|
||||
evt.stopPropagation();
|
||||
setIsMinimized(!isMinimized);
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name="panel-group-open-close"
|
||||
className={classNames(
|
||||
'w-5 h-5 text-white transition duration-300 cursor-pointer',
|
||||
{
|
||||
'transform rotate-90': !isMinimized,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
<span className="text-[#d8d8d8] text-[12px] font-[300]">
|
||||
{'Segmentation Appearance'}
|
||||
</span>
|
||||
</div>
|
||||
{/* active segmentation */}
|
||||
{!isMinimized && (
|
||||
<div>
|
||||
<ActiveSegmentationConfig
|
||||
config={config}
|
||||
dispatch={dispatch}
|
||||
setFillAlpha={setFillAlpha}
|
||||
setOutlineWidthActive={setOutlineWidthActive}
|
||||
setOutlineOpacityActive={setOutlineOpacityActive}
|
||||
setRenderFill={setRenderFill}
|
||||
setRenderOutline={setRenderOutline}
|
||||
usePercentage={segmentationConfig.usePercentage}
|
||||
/>
|
||||
{/* A small line */}
|
||||
<div className="h-[1px] bg-[#212456] mb-[8px] mx-1"></div>
|
||||
{/* inactive segmentation */}
|
||||
<div
|
||||
onClick={() => setIsMinimized(!isMinimized)}
|
||||
className="flex items-center cursor-pointer pl-2 pb-[9px]"
|
||||
>
|
||||
<span className="text-[#d8d8d8] text-[12px] font-[300]">
|
||||
{'Inactive Segmentations'}
|
||||
</span>
|
||||
</div>
|
||||
<InactiveSegmentationConfig
|
||||
config={config}
|
||||
dispatch={dispatch}
|
||||
setRenderInactiveSegmentations={setRenderInactiveSegmentations}
|
||||
setFillAlphaInactive={setFillAlphaInactive}
|
||||
usePercentage={segmentationConfig.usePercentage}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="h-[6px] bg-black "></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
SegmentationConfig.propTypes = {};
|
||||
|
||||
export default SegmentationConfig;
|
||||
@@ -0,0 +1,273 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import SegmentationGroupSegment from './SegmentationGroupSegment';
|
||||
import Dropdown from '../Dropdown';
|
||||
import classnames from 'classnames';
|
||||
import Icon from '../Icon';
|
||||
import IconButton from '../IconButton';
|
||||
|
||||
const AddNewSegmentRow = ({
|
||||
id,
|
||||
onConfigChange,
|
||||
onToggleSegmentationVisibility,
|
||||
onSegmentAdd,
|
||||
isVisible,
|
||||
showAddSegment,
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center pl-[29px] bg-black text-primary-active hover:opacity-80 cursor-pointer text-[12px] py-1">
|
||||
{showAddSegment && (
|
||||
<div className="flex items-center" onClick={() => onSegmentAdd()}>
|
||||
<Icon name="row-add" className="w-5 h-5" />
|
||||
<div className="">Add Segment</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-grow" />
|
||||
<div
|
||||
className="flex items-center pr-2"
|
||||
onClick={() => {
|
||||
onToggleSegmentationVisibility(id);
|
||||
}}
|
||||
>
|
||||
{isVisible ? (
|
||||
<Icon name="row-show-all" className="w-5 h-5" />
|
||||
) : (
|
||||
<Icon name="row-hide-all" className="w-5 h-5" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* {isSegmentationConfigOpen && (
|
||||
<SegmentationConfig onConfigChange={onConfigChange} />
|
||||
)} */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SegmentGroupHeader = ({
|
||||
isMinimized,
|
||||
onToggleMinimizeSegmentation,
|
||||
onSegmentationClick,
|
||||
id,
|
||||
label,
|
||||
isActive,
|
||||
segmentCount,
|
||||
onSegmentationEdit,
|
||||
onSegmentationDelete,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={classnames(
|
||||
'flex items-center pr-2 pl-[3px] h-[27px] gap-2 rounded-t-md border-b border-secondary-light cursor-pointer text-[12px]',
|
||||
{
|
||||
'bg-secondary-main': isActive,
|
||||
'bg-secondary-dark': !isActive,
|
||||
}
|
||||
)}
|
||||
onClick={evt => {
|
||||
evt.stopPropagation();
|
||||
onSegmentationClick(id);
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name="panel-group-open-close"
|
||||
onClick={evt => {
|
||||
evt.stopPropagation();
|
||||
onToggleMinimizeSegmentation(id);
|
||||
}}
|
||||
className={classnames(
|
||||
'w-5 h-5 text-white transition duration-300 cursor-pointer',
|
||||
{
|
||||
'transform rotate-90': !isMinimized,
|
||||
}
|
||||
)}
|
||||
/>
|
||||
<span className="text-white ">{label.toUpperCase()}</span>
|
||||
<div className="flex-grow" />
|
||||
<span className="text-white ">{segmentCount}</span>
|
||||
<div
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<Dropdown
|
||||
id="segmentation-dropdown"
|
||||
showDropdownIcon={false}
|
||||
list={[
|
||||
{
|
||||
title: 'Rename',
|
||||
onClick: () => {
|
||||
onSegmentationEdit(id);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Delete',
|
||||
onClick: () => {
|
||||
onSegmentationDelete(id);
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<IconButton
|
||||
id={''}
|
||||
variant="text"
|
||||
color="inherit"
|
||||
size="initial"
|
||||
className="text-primary-active"
|
||||
>
|
||||
<Icon name="panel-group-more" />
|
||||
</IconButton>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SegmentationGroup = ({
|
||||
label,
|
||||
id,
|
||||
segmentCount,
|
||||
isVisible,
|
||||
isActive,
|
||||
onSegmentClick,
|
||||
isMinimized,
|
||||
onSegmentColorClick,
|
||||
showAddSegment,
|
||||
segments,
|
||||
activeSegmentIndex,
|
||||
onSegmentAdd,
|
||||
onSegmentationClick,
|
||||
onClickSegmentColor,
|
||||
onSegmentationEdit,
|
||||
onToggleSegmentVisibility,
|
||||
onToggleSegmentationVisibility,
|
||||
onSegmentDelete,
|
||||
showSegmentDelete,
|
||||
onToggleMinimizeSegmentation,
|
||||
onSegmentationConfigChange,
|
||||
onSegmentationDelete,
|
||||
onSegmentEdit,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<SegmentGroupHeader
|
||||
id={id}
|
||||
label={label}
|
||||
isActive={isActive}
|
||||
isMinimized={isMinimized}
|
||||
onToggleMinimizeSegmentation={onToggleMinimizeSegmentation}
|
||||
onSegmentationClick={onSegmentationClick}
|
||||
segmentCount={segmentCount}
|
||||
onSegmentationEdit={onSegmentationEdit}
|
||||
onSegmentationDelete={onSegmentationDelete}
|
||||
/>
|
||||
{!isMinimized && (
|
||||
<div className="">
|
||||
<AddNewSegmentRow
|
||||
onConfigChange={onSegmentationConfigChange}
|
||||
onSegmentAdd={onSegmentAdd}
|
||||
isVisible={isVisible}
|
||||
onToggleSegmentationVisibility={onToggleSegmentationVisibility}
|
||||
id={id}
|
||||
showAddSegment={showAddSegment}
|
||||
/>
|
||||
<div
|
||||
className="ohif-scrollbar overflow-y-hidden"
|
||||
style={{ maxHeight: '40rem' }}
|
||||
>
|
||||
{!!segments.length &&
|
||||
segments.map(segment => {
|
||||
if (segment === undefined || segment === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
segmentIndex,
|
||||
color,
|
||||
label,
|
||||
isVisible,
|
||||
isLocked,
|
||||
} = segment;
|
||||
return (
|
||||
<div className="mb-[1px]" key={segmentIndex}>
|
||||
<SegmentationGroupSegment
|
||||
segmentationId={id}
|
||||
segmentIndex={segmentIndex}
|
||||
label={label}
|
||||
color={color}
|
||||
isActive={activeSegmentIndex === segmentIndex}
|
||||
isLocked={isLocked}
|
||||
isVisible={isVisible}
|
||||
onClick={onSegmentClick}
|
||||
onEdit={onSegmentEdit}
|
||||
onDelete={onSegmentDelete}
|
||||
showSegmentDelete={showSegmentDelete}
|
||||
onColor={onSegmentColorClick}
|
||||
onToggleVisibility={onToggleSegmentVisibility}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
SegmentationGroup.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
segments: PropTypes.array.isRequired,
|
||||
segmentCount: PropTypes.number.isRequired,
|
||||
isVisible: PropTypes.bool.isRequired,
|
||||
isActive: PropTypes.bool.isRequired,
|
||||
isMinimized: PropTypes.bool.isRequired,
|
||||
activeSegmentIndex: PropTypes.number,
|
||||
onClickNewSegment: PropTypes.func.isRequired,
|
||||
onClickSegment: PropTypes.func.isRequired,
|
||||
onClickSegmentEdit: PropTypes.func.isRequired,
|
||||
onClickSegmentDelete: PropTypes.func.isRequired,
|
||||
onToggleSegmentLocked: PropTypes.func,
|
||||
onToggleSegmentVisibility: PropTypes.func.isRequired,
|
||||
onToggleSegmentationVisibility: PropTypes.func.isRequired,
|
||||
onSegmentClick: PropTypes.func.isRequired,
|
||||
showAddSegment: PropTypes.bool.isRequired,
|
||||
onSegmentAdd: PropTypes.func.isRequired,
|
||||
onSegmentationClick: PropTypes.func.isRequired,
|
||||
onClickSegmentColor: PropTypes.func.isRequired,
|
||||
onSegmentationEdit: PropTypes.func.isRequired,
|
||||
onSegmentDelete: PropTypes.func.isRequired,
|
||||
onToggleMinimizeSegmentation: PropTypes.func.isRequired,
|
||||
onSegmentationConfigChange: PropTypes.func.isRequired,
|
||||
onSegmentationDelete: PropTypes.func.isRequired,
|
||||
onSegmentEdit: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
SegmentationGroup.defaultProps = {
|
||||
label: '',
|
||||
segmentCount: 0,
|
||||
segments: [],
|
||||
isVisible: true,
|
||||
isMinimized: false,
|
||||
onClickNewSegment: () => {},
|
||||
onClickSegment: () => {},
|
||||
onClickSegmentEdit: () => {},
|
||||
onClickSegmentDelete: () => {},
|
||||
onToggleSegmentLocked: () => {},
|
||||
onToggleSegmentVisibility: () => {},
|
||||
onToggleSegmentationVisibility: () => {},
|
||||
onSegmentClick: () => {},
|
||||
showAddSegment: false,
|
||||
onSegmentAdd: () => {},
|
||||
onSegmentationClick: () => {},
|
||||
onClickSegmentColor: () => {},
|
||||
onSegmentationEdit: () => {},
|
||||
onSegmentDelete: () => {},
|
||||
onToggleMinimizeSegmentation: () => {},
|
||||
onSegmentationConfigChange: () => {},
|
||||
onSegmentationDelete: () => {},
|
||||
onSegmentEdit: () => {},
|
||||
};
|
||||
|
||||
export default SegmentationGroup;
|
||||
@@ -0,0 +1,242 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { Icon } from '@ohif/ui';
|
||||
|
||||
const SegmentItem = ({
|
||||
segmentIndex,
|
||||
segmentationId,
|
||||
label,
|
||||
isActive,
|
||||
isVisible,
|
||||
color,
|
||||
showSegmentDelete,
|
||||
isLocked = false,
|
||||
onClick,
|
||||
onEdit,
|
||||
onDelete,
|
||||
onColor,
|
||||
onToggleVisibility,
|
||||
onToggleLocked,
|
||||
}) => {
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
const [isSegmentIndexHovering, setIsSegmentIndexHovering] = useState(false);
|
||||
|
||||
const onMouseEnter = () => setIsHovering(true);
|
||||
const onMouseLeave = () => setIsHovering(false);
|
||||
|
||||
const cssColor = `rgb(${color[0]},${color[1]},${color[2]})`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(
|
||||
'group relative flex cursor-pointer bg-primary-dark transition duration-300 text-[12px] overflow-hidden border',
|
||||
{
|
||||
'border-primary-light rounded-[3px]': isActive,
|
||||
},
|
||||
{
|
||||
'border-transparent': !isActive,
|
||||
}
|
||||
)}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onClick(segmentationId, segmentIndex);
|
||||
}}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
data-cy={'segment-item'}
|
||||
>
|
||||
<div
|
||||
className={classnames(
|
||||
'w-[27px] flex items-center justify-center border-r border-r-black text-[12px]',
|
||||
{
|
||||
'bg-primary-light text-black border-l border-l-primary-light rounded-l-sm': isActive,
|
||||
'bg-[#1d3c58] border-l border-l-[#1d3c58] rounded-l-sm':
|
||||
!isActive && isHovering,
|
||||
'bg-primary-dark text-aqua-pale':
|
||||
!isActive && !isHovering && isVisible,
|
||||
'bg-[#140e2e] opacity-60 text-[#537594]': !isActive && !isVisible,
|
||||
}
|
||||
)}
|
||||
onMouseEnter={() => setIsSegmentIndexHovering(true)}
|
||||
onMouseLeave={() => setIsSegmentIndexHovering(false)}
|
||||
>
|
||||
{isSegmentIndexHovering && showSegmentDelete ? (
|
||||
<Icon
|
||||
name="close"
|
||||
className={classnames('w-5 h-5 pr-2')}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onDelete(segmentationId, segmentIndex);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className={classnames('flex items-center pr-2 ')}>
|
||||
{segmentIndex}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={classnames(
|
||||
'flex items-center justify-between w-full pl-2 py-1 text-white border-r border-r-black relative ',
|
||||
{
|
||||
'bg-primary-dark text-primary-light': isActive,
|
||||
'bg-primary-dark text-aqua-pale': !isActive && isVisible,
|
||||
'bg-[#140e2e] opacity-60 text-[#3d5871]': !isVisible,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div className={classnames('flex items-baseline gap-2')}>
|
||||
<div
|
||||
className={classnames('shrink-0 w-[8px] h-[8px] rounded-full')}
|
||||
style={{ backgroundColor: cssColor }}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onColor(segmentationId, segmentIndex);
|
||||
}}
|
||||
/>
|
||||
<div className="">{label}</div>
|
||||
</div>
|
||||
{/* with faded background */}
|
||||
<div className="absolute right-0 bg-black/15 rounded-lg pr-[7px]">
|
||||
{!isVisible && !isHovering && (
|
||||
<div>
|
||||
<Icon
|
||||
name="row-hidden"
|
||||
className={classnames('w-5 h-5 text-[#3d5871] ')}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onToggleVisibility(segmentationId, segmentIndex);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isHovering && (
|
||||
<div className={classnames('flex items-center')}>
|
||||
<Icon
|
||||
name="row-edit"
|
||||
className={classnames('w-5 h-5', {
|
||||
'text-white': isLocked,
|
||||
'text-primary-light': !isLocked,
|
||||
})}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onEdit(segmentationId, segmentIndex);
|
||||
}}
|
||||
/>
|
||||
{isVisible ? (
|
||||
<Icon
|
||||
name="row-hide"
|
||||
className={classnames('w-5 h-5', {
|
||||
'text-white': isLocked,
|
||||
'text-primary-light': !isLocked,
|
||||
})}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onToggleVisibility(segmentationId, segmentIndex);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
name="row-unhide"
|
||||
className={classnames('w-5 h-5', {
|
||||
'text-white': isLocked,
|
||||
'text-primary-light': !isLocked,
|
||||
})}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onToggleVisibility(segmentationId, segmentIndex);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="relative flex flex-col w-full border-t border-t-black">
|
||||
<div className="fl`ex items-center mb-1 ml-2">
|
||||
<div className="flex items-center flex-1 text-base text-primary-light">
|
||||
<div
|
||||
className={classnames(
|
||||
'w-3 h-3 mt-1 mr-2 rounded-full cursor-pointer transition duration-300 hover:opacity-80'
|
||||
)}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onColor(segmentationId, segmentIndex);
|
||||
}}
|
||||
style={{ backgroundColor: cssColor }}
|
||||
></div>
|
||||
{label}
|
||||
</div>
|
||||
<div className="flex items-center w-1/3">
|
||||
<div className="px-1">
|
||||
<Icon
|
||||
className={classnames(
|
||||
'text-white w-4 cursor-pointer transition duration-300 hover:opacity-80'
|
||||
)}
|
||||
name={isVisible ? 'eye-visible' : 'eye-hidden'}
|
||||
onClick={e => {
|
||||
// stopPropagation needed to avoid disable the current active item
|
||||
e.stopPropagation();
|
||||
onToggleVisibility(segmentationId, segmentIndex);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{onToggleLocked !== undefined ? (
|
||||
<div className="px-1">
|
||||
<Icon
|
||||
className={classnames(
|
||||
'text-white w-4 cursor-pointer transition duration-300 hover:opacity-80'
|
||||
)}
|
||||
name={isLocked ? 'lock' : 'dotted-circle'}
|
||||
onClick={e => {
|
||||
// stopPropagation needed to avoid disable the current active item
|
||||
e.stopPropagation();
|
||||
onToggleLocked(segmentationId, segmentIndex);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="px-1">
|
||||
<Icon
|
||||
className={classnames(
|
||||
'text-white w-4 cursor-pointer transition duration-300 hover:opacity-80'
|
||||
)}
|
||||
name={'pencil'}
|
||||
onClick={e => {
|
||||
// stopPropagation needed to avoid disable the current active item
|
||||
e.stopPropagation();
|
||||
onEdit(segmentationId, segmentIndex);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
SegmentItem.propTypes = {
|
||||
segmentIndex: PropTypes.number.isRequired,
|
||||
segmentationId: PropTypes.string.isRequired,
|
||||
label: PropTypes.string,
|
||||
// color as array
|
||||
color: PropTypes.array,
|
||||
isActive: PropTypes.bool.isRequired,
|
||||
isVisible: PropTypes.bool.isRequired,
|
||||
isLocked: PropTypes.bool,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
onEdit: PropTypes.func.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
onToggleVisibility: PropTypes.func.isRequired,
|
||||
onToggleLocked: PropTypes.func,
|
||||
};
|
||||
|
||||
SegmentItem.defaultProps = {
|
||||
isActive: false,
|
||||
};
|
||||
|
||||
export default SegmentItem;
|
||||
@@ -0,0 +1,173 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Icon from '../Icon';
|
||||
import SegmentationGroup from './SegmentationGroup';
|
||||
import SegmentationConfig from './SegmentationConfig';
|
||||
|
||||
const GetSegmentationConfig = ({
|
||||
setFillAlpha,
|
||||
setFillAlphaInactive,
|
||||
setOutlineWidthActive,
|
||||
setRenderFill,
|
||||
setRenderInactiveSegmentations,
|
||||
setRenderOutline,
|
||||
setOutlineOpacityActive,
|
||||
segmentationConfig,
|
||||
}) => {
|
||||
return (
|
||||
<SegmentationConfig
|
||||
setFillAlpha={setFillAlpha}
|
||||
setFillAlphaInactive={setFillAlphaInactive}
|
||||
setOutlineWidthActive={setOutlineWidthActive}
|
||||
setOutlineOpacityActive={setOutlineOpacityActive}
|
||||
setRenderFill={setRenderFill}
|
||||
setRenderInactiveSegmentations={setRenderInactiveSegmentations}
|
||||
setRenderOutline={setRenderOutline}
|
||||
segmentationConfig={segmentationConfig}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const SegmentationGroupTable = ({
|
||||
segmentations,
|
||||
onSegmentationAdd,
|
||||
onSegmentationEdit,
|
||||
onSegmentationClick,
|
||||
onSegmentationDelete,
|
||||
showAddSegmentation,
|
||||
showAddSegment,
|
||||
onSegmentClick,
|
||||
onSegmentAdd,
|
||||
segmentationConfig,
|
||||
onSegmentDelete,
|
||||
onSegmentEdit,
|
||||
onToggleSegmentationVisibility,
|
||||
onToggleSegmentVisibility,
|
||||
onSegmentColorClick,
|
||||
isMinimized,
|
||||
onToggleMinimizeSegmentation,
|
||||
setFillAlpha,
|
||||
setFillAlphaInactive,
|
||||
setOutlineWidthActive,
|
||||
setOutlineOpacityActive,
|
||||
setRenderFill,
|
||||
setRenderInactiveSegmentations,
|
||||
setRenderOutline,
|
||||
}) => {
|
||||
return (
|
||||
<div className="font-inter font-[300]">
|
||||
<GetSegmentationConfig
|
||||
// showAddSegmentation={showAddSegmentation}
|
||||
// onSegmentationAdd={onSegmentationAdd}
|
||||
segmentationConfig={segmentationConfig}
|
||||
setFillAlpha={setFillAlpha}
|
||||
setFillAlphaInactive={setFillAlphaInactive}
|
||||
setOutlineWidthActive={setOutlineWidthActive}
|
||||
setOutlineOpacityActive={setOutlineOpacityActive}
|
||||
setRenderFill={setRenderFill}
|
||||
setRenderInactiveSegmentations={setRenderInactiveSegmentations}
|
||||
setRenderOutline={setRenderOutline}
|
||||
/>
|
||||
<div className="flex flex-col pr-[1px] mt-1">
|
||||
{!!segmentations.length &&
|
||||
segmentations.map((segmentation, index) => {
|
||||
const {
|
||||
id,
|
||||
label,
|
||||
displayText = [],
|
||||
segmentCount,
|
||||
segments,
|
||||
isVisible,
|
||||
isActive,
|
||||
activeSegmentIndex,
|
||||
} = segmentation;
|
||||
return (
|
||||
<>
|
||||
<SegmentationGroup
|
||||
id={id}
|
||||
label={label}
|
||||
isMinimized={isMinimized[id]}
|
||||
segments={segments}
|
||||
showAddSegment={showAddSegment}
|
||||
segmentCount={segmentCount}
|
||||
isActive={isActive}
|
||||
isVisible={isVisible}
|
||||
onSegmentColorClick={onSegmentColorClick}
|
||||
onSegmentationClick={() => onSegmentationClick(id)}
|
||||
activeSegmentIndex={activeSegmentIndex}
|
||||
onToggleMinimizeSegmentation={onToggleMinimizeSegmentation}
|
||||
onSegmentationEdit={onSegmentationEdit}
|
||||
onSegmentationDelete={onSegmentationDelete}
|
||||
onSegmentClick={onSegmentClick}
|
||||
onSegmentEdit={onSegmentEdit}
|
||||
onToggleSegmentVisibility={onToggleSegmentVisibility}
|
||||
onToggleSegmentationVisibility={
|
||||
onToggleSegmentationVisibility
|
||||
}
|
||||
onSegmentAdd={onSegmentAdd}
|
||||
showSegmentDelete={false}
|
||||
/>
|
||||
<div className="h-2 bg-black"></div>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{showAddSegmentation && (
|
||||
<div
|
||||
className="flex items-center cursor-pointer hover:opacity-80 text-primary-active bg-black text-[12px] pl-1 h-[45px]"
|
||||
onClick={() => onSegmentationAdd()}
|
||||
>
|
||||
<Icon name="row-add" className="w-5 h-5" />
|
||||
<div className="pl-1">Add New Segmentation</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
SegmentationGroupTable.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
segmentations: PropTypes.array.isRequired,
|
||||
activeSegmentationId: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
onEdit: PropTypes.func.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
onToggleLocked: PropTypes.func,
|
||||
onToggleVisibility: PropTypes.func.isRequired,
|
||||
onToggleVisibilityAll: PropTypes.func.isRequired,
|
||||
segmentationConfig: PropTypes.object,
|
||||
};
|
||||
|
||||
SegmentationGroupTable.defaultProps = {
|
||||
title: '',
|
||||
segmentations: [],
|
||||
activeSegmentationId: '',
|
||||
onClick: () => {},
|
||||
onEdit: () => {},
|
||||
onDelete: () => {},
|
||||
onToggleLocked: () => {},
|
||||
onToggleVisibility: () => {},
|
||||
onToggleVisibilityAll: () => {},
|
||||
segmentationConfig: {
|
||||
initialConfig: {
|
||||
fillAlpha: 0.5,
|
||||
fillAlphaInactive: 0.5,
|
||||
outlineWidthActive: 1,
|
||||
outlineOpacity: 1,
|
||||
outlineOpacityInactive: 0.5,
|
||||
renderFill: true,
|
||||
renderInactiveSegmentations: true,
|
||||
renderOutline: true,
|
||||
},
|
||||
usePercentage: true,
|
||||
},
|
||||
setFillAlpha: () => {},
|
||||
setFillAlphaInactive: () => {},
|
||||
setOutlineWidthActive: () => {},
|
||||
setOutlineOpacityActive: () => {},
|
||||
setRenderFill: () => {},
|
||||
setRenderInactiveSegmentations: () => {},
|
||||
setRenderOutline: () => {},
|
||||
};
|
||||
|
||||
export default SegmentationGroupTable;
|
||||
@@ -0,0 +1,141 @@
|
||||
import SegmentationGroupTable from '../SegmentationGroupTable';
|
||||
|
||||
import { ArgsTable, Story, Canvas, Meta } from '@storybook/addon-docs';
|
||||
import { createComponentTemplate } from '../../../storybook/functions/create-component-story';
|
||||
|
||||
export const argTypes = {
|
||||
component: SegmentationGroupTable,
|
||||
title: 'Components/SegmentationGroupTable',
|
||||
};
|
||||
|
||||
<Meta
|
||||
title="Components/SegmentationGroupTable"
|
||||
component={SegmentationGroupTable}
|
||||
/>
|
||||
|
||||
export const SegmentationGroupTableTemplate = args => (
|
||||
<div className="w-64 h-96 bg-primary-dark">
|
||||
<SegmentationGroupTable {...args} />
|
||||
</div>
|
||||
);
|
||||
|
||||
<Heading
|
||||
title="SegmentationGroupTable"
|
||||
componentRelativePath="SegmentationGroupTable/SegmentationGroupTable.tsx"
|
||||
/>
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Props](#props)
|
||||
- [Contribute](#contribute)
|
||||
|
||||
## Overview
|
||||
|
||||
SegmentationGroupTable is a component that renders the SegmentationGroupTables.
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="Overview"
|
||||
args={{
|
||||
activeSegmentationId: 'ed8d6f8d-4035-438d-9032-36d5912aaa3a',
|
||||
showAddSegmentation: true,
|
||||
isMinimized: {"4be5-b9b2-ed33-fa86970568e7": false},
|
||||
showAddSegment: true,
|
||||
segmentations: [
|
||||
{
|
||||
"id": "4be5-b9b2-ed33-fa86970568e7",
|
||||
"volumeId": "a3b0f15f-4be5-b9b2-ed33-fa86970568e7",
|
||||
"activeSegmentIndex": 1,
|
||||
"cachedStats": {},
|
||||
"label": "Segmentation",
|
||||
"segmentsLocked": {},
|
||||
"type": "LABELMAP",
|
||||
"displayText": [],
|
||||
"hydrated": true,
|
||||
"segmentCount": 3,
|
||||
"segments": [
|
||||
null,
|
||||
{
|
||||
"label": "Segment 1",
|
||||
"segmentIndex": 1,
|
||||
"color": [
|
||||
128,
|
||||
174,
|
||||
128
|
||||
],
|
||||
"opacity": 255,
|
||||
"isVisible": true,
|
||||
"isLocked": false,
|
||||
},
|
||||
{
|
||||
"label": "Segment 2",
|
||||
"segmentIndex": 2,
|
||||
"color": [
|
||||
241,
|
||||
214,
|
||||
145
|
||||
],
|
||||
"opacity": 255,
|
||||
"isVisible": true,
|
||||
"isLocked": false,
|
||||
},
|
||||
{
|
||||
"label": "Segment 3",
|
||||
"segmentIndex": 3,
|
||||
"color": [
|
||||
141,
|
||||
114,
|
||||
145
|
||||
],
|
||||
"opacity": 255,
|
||||
"isVisible": false,
|
||||
"isLocked": false
|
||||
}
|
||||
],
|
||||
"isActive": false,
|
||||
"colorLUTIndex": 1,
|
||||
"isVisible": true
|
||||
},
|
||||
{
|
||||
"id": "a3b0f15f-4be5-b9b2-ed33-fa86970568e7",
|
||||
"volumeId": "a3b0f15f-4be5-b9b2-ed33-fa86970568e7",
|
||||
"activeSegmentIndex": 1,
|
||||
"cachedStats": {},
|
||||
"label": "Segmentation2",
|
||||
"segmentsLocked": {},
|
||||
"type": "LABELMAP",
|
||||
"displayText": [],
|
||||
"hydrated": true,
|
||||
"segmentCount": 2,
|
||||
"segments": [
|
||||
null,
|
||||
{
|
||||
"label": "Segment 1",
|
||||
"segmentIndex": 1,
|
||||
"color": [
|
||||
228,
|
||||
174,
|
||||
128
|
||||
],
|
||||
"opacity": 255,
|
||||
"isVisible": true,
|
||||
"isLocked": false,
|
||||
},
|
||||
],
|
||||
"isActive": true,
|
||||
"colorLUTIndex": 1,
|
||||
"isVisible": true
|
||||
}
|
||||
]
|
||||
}}
|
||||
>
|
||||
{SegmentationGroupTableTemplate.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
## Props
|
||||
|
||||
<ArgsTable of={SegmentationGroupTable} />
|
||||
|
||||
## Contribute
|
||||
|
||||
<Footer componentRelativePath="SegmentationGroupTable/__stories__/SegmentationGroupTable.stories.mdx" />
|
||||
@@ -0,0 +1,3 @@
|
||||
import SegmentationGroupTable from './SegmentationGroupTable';
|
||||
|
||||
export default SegmentationGroupTable;
|
||||
@@ -0,0 +1,22 @@
|
||||
const reducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case 'RENDER_OUTLINE':
|
||||
return { ...state, renderOutline: action.payload.value };
|
||||
case 'RENDER_FILL':
|
||||
return { ...state, renderFill: action.payload.value };
|
||||
case 'SET_OUTLINE_OPACITY':
|
||||
return { ...state, outlineOpacity: action.payload.value };
|
||||
case 'SET_OUTLINE_WIDTH':
|
||||
return { ...state, outlineWidth: action.payload.value };
|
||||
case 'SET_FILL_ALPHA':
|
||||
return { ...state, fillAlpha: action.payload.value };
|
||||
case 'SET_FILL_ALPHA_INACTIVE':
|
||||
return { ...state, fillAlphaInactive: action.payload.value };
|
||||
case 'RENDER_INACTIVE_SEGMENTATIONS':
|
||||
return { ...state, renderInactiveSegmentations: action.payload.value };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export { reducer };
|
||||
@@ -5,7 +5,6 @@ import SegmentationItem from './SegmentationItem';
|
||||
|
||||
const SegmentationTable = ({
|
||||
title,
|
||||
amount,
|
||||
segmentations,
|
||||
activeSegmentationId,
|
||||
onClick,
|
||||
@@ -15,6 +14,7 @@ const SegmentationTable = ({
|
||||
onToggleVisibilityAll,
|
||||
}) => {
|
||||
const [hiddenSegmentationIds, setHiddenSegmentationIds] = useState([]);
|
||||
const amount = segmentations.length;
|
||||
|
||||
const handleToggleAll = () => {
|
||||
// filter segmentation ids that are hidden
|
||||
@@ -106,7 +106,6 @@ const SegmentationTable = ({
|
||||
|
||||
SegmentationTable.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
amount: PropTypes.number.isRequired,
|
||||
segmentations: PropTypes.array.isRequired,
|
||||
activeSegmentationId: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
@@ -118,7 +117,6 @@ SegmentationTable.propTypes = {
|
||||
|
||||
SegmentationTable.defaultProps = {
|
||||
title: '',
|
||||
amount: 0,
|
||||
segmentations: [],
|
||||
activeSegmentationId: '',
|
||||
onClick: () => {},
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
}
|
||||
|
||||
.customSelect__wrapper .customSelect__option {
|
||||
@apply text-black flex flex-row items-center;
|
||||
@apply flex flex-row items-center;
|
||||
}
|
||||
|
||||
.customSelect__wrapper .customSelect__option:focus {
|
||||
@@ -46,3 +46,52 @@
|
||||
.customSelect__wrapper .customSelect__value-container--is-multi {
|
||||
@apply px-3 py-2 inline-block truncate;
|
||||
}
|
||||
|
||||
.customSelect__menu {
|
||||
background-color: transparent !important;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.css-4ljt47-MenuList {
|
||||
overflow-x: hidden !important;
|
||||
margin-right: 4px;
|
||||
margin-top: 4px;
|
||||
padding-top: 0px !important;
|
||||
}
|
||||
.css-4ljt47-MenuList::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.css-4ljt47-MenuList::-webkit-scrollbar-thumb {
|
||||
background-color: #0944b3;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.customSelect__option:focus {
|
||||
background-color: #0944b391 !important;
|
||||
}
|
||||
.customSelect__option label {
|
||||
color: #d6d6d6;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.css-1n7v3ny-option {
|
||||
background-color: #0944b391 !important;
|
||||
}
|
||||
|
||||
.css-1n7v3ny-option:focus {
|
||||
background-color: #0944b391 !important;
|
||||
}
|
||||
|
||||
.customSelect__option:hover {
|
||||
background-color: #0944b391;
|
||||
}
|
||||
|
||||
.css-26l3qy-menu {
|
||||
border: 2px solid #0944b3;
|
||||
border-radius: 6px;
|
||||
background-color: #151515 !important;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import ReactSelect, { components } from 'react-select';
|
||||
import { Icon } from '@ohif/ui';
|
||||
|
||||
import './Select.css';
|
||||
|
||||
@@ -18,17 +19,20 @@ const MultiValue = props => {
|
||||
|
||||
const Option = props => {
|
||||
return (
|
||||
<div>
|
||||
<components.Option {...props}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={props.isSelected}
|
||||
className="w-6 h-6 mr-2"
|
||||
onChange={e => null}
|
||||
/>
|
||||
<label>{props.value} </label>
|
||||
</components.Option>
|
||||
</div>
|
||||
<components.Option {...props}>
|
||||
<div className="flex items-center">
|
||||
<div className="w-2 h-2">
|
||||
{props.isSelected ? (
|
||||
<Icon name={'checkbox-active'} />
|
||||
) : (
|
||||
<Icon name={'checkbox-default'} />
|
||||
)}
|
||||
</div>
|
||||
<label id={props.data.value} className="ml-3 mt-1">
|
||||
<span>{props.value}</span>
|
||||
</label>
|
||||
</div>
|
||||
</components.Option>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import SwiperCore, {
|
||||
A11y,
|
||||
Controller,
|
||||
Pagination,
|
||||
Scrollbar,
|
||||
Navigation,
|
||||
} from 'swiper';
|
||||
|
||||
import { Button, Icon } from '../';
|
||||
import { Button, Icon, IconButton, Tooltip } from '../';
|
||||
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/navigation';
|
||||
import './style.css';
|
||||
|
||||
const borderSize = 4;
|
||||
const expandedWidth = 248;
|
||||
const collapsedWidth = 56;
|
||||
const collapsedWidth = 25;
|
||||
|
||||
const baseStyle = {
|
||||
maxWidth: `${expandedWidth}px`,
|
||||
@@ -27,20 +39,20 @@ const styleMap = {
|
||||
};
|
||||
|
||||
const baseClasses =
|
||||
'transition-all duration-300 ease-in-out h-100 bg-primary-dark border-black flex flex-col justify-start box-content';
|
||||
'transition-all duration-300 ease-in-out h-100 bg-black border-black justify-start box-content flex flex-col';
|
||||
|
||||
const classesMap = {
|
||||
open: {
|
||||
left: `border-r-4`,
|
||||
right: `border-l-4`,
|
||||
left: `mr-1`,
|
||||
right: `ml-1`,
|
||||
},
|
||||
closed: {
|
||||
left: `border-r-4 items-end`,
|
||||
right: `border-l-4 items-start`,
|
||||
left: `mr-2 items-end`,
|
||||
right: `ml-2 items-start`,
|
||||
},
|
||||
};
|
||||
|
||||
const openIconName = {
|
||||
const openStateIconName = {
|
||||
left: 'push-left',
|
||||
right: 'push-right',
|
||||
};
|
||||
@@ -54,62 +66,100 @@ const position = {
|
||||
},
|
||||
};
|
||||
|
||||
const getChildComponent = (childComponents, componentOpen) => {
|
||||
if (Array.isArray(childComponents)) {
|
||||
return childComponents.find(
|
||||
_childComponent => _childComponent.name === componentOpen
|
||||
);
|
||||
} else {
|
||||
return childComponents;
|
||||
}
|
||||
};
|
||||
|
||||
const SidePanel = ({
|
||||
side,
|
||||
className,
|
||||
defaultComponentOpen,
|
||||
childComponents,
|
||||
activeTabIndex: activeTabIndexProp,
|
||||
tabs,
|
||||
}) => {
|
||||
const { t } = useTranslation('SidePanel');
|
||||
|
||||
const [componentOpen, setComponentOpen] = useState(defaultComponentOpen);
|
||||
const [panelOpen, setPanelOpen] = useState(activeTabIndexProp !== null);
|
||||
const [activeTabIndex, setActiveTabIndex] = useState(activeTabIndexProp || 0);
|
||||
const swiperRef = useRef() as any;
|
||||
const [swiper, setSwiper] = useState<any>();
|
||||
|
||||
const openStatus = componentOpen ? 'open' : 'closed';
|
||||
const prevRef = React.useRef();
|
||||
const nextRef = React.useRef();
|
||||
|
||||
const openStatus = panelOpen ? 'open' : 'closed';
|
||||
const style = Object.assign({}, styleMap[openStatus][side], baseStyle);
|
||||
|
||||
const childComponent = getChildComponent(childComponents, componentOpen);
|
||||
const ActiveComponent = tabs[activeTabIndex].content;
|
||||
|
||||
const getPanelButtons = () => {
|
||||
const _childComponents = Array.isArray(childComponents)
|
||||
? childComponents
|
||||
: [childComponents];
|
||||
return _childComponents.map((childComponent, i) => {
|
||||
return (
|
||||
<Button
|
||||
key={i}
|
||||
variant="text"
|
||||
color="inherit"
|
||||
border="none"
|
||||
useEffect(() => {
|
||||
if (panelOpen && swiper) {
|
||||
swiper.slideTo(activeTabIndex, 500);
|
||||
}
|
||||
}, [panelOpen, swiper]);
|
||||
|
||||
useEffect(() => {
|
||||
if (swiper) {
|
||||
swiper.params.navigation.prevEl = prevRef.current;
|
||||
swiper.params.navigation.nextEl = nextRef.current;
|
||||
swiper.navigation.init();
|
||||
swiper.navigation.update();
|
||||
}
|
||||
}, [swiper]);
|
||||
|
||||
const getCloseStateComponent = () => {
|
||||
const _childComponents = Array.isArray(tabs) ? tabs : [tabs];
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={classnames(
|
||||
'bg-secondary-dark h-[28px] flex items-center w-full rounded-md cursor-pointer',
|
||||
side === 'left' ? 'pr-2 justify-end' : 'pl-2 justify-start'
|
||||
)}
|
||||
onClick={() => {
|
||||
setComponentOpen(childComponent.name);
|
||||
setPanelOpen(prev => !prev);
|
||||
}}
|
||||
style={{
|
||||
minWidth: `${collapsedWidth}px`,
|
||||
width: `${collapsedWidth}px`,
|
||||
}}
|
||||
name={childComponent.name}
|
||||
className="flex flex-col text-xs px-1 text-white border-transparent border-b"
|
||||
data-cy={`side-panel-header-${side}`}
|
||||
>
|
||||
<Icon
|
||||
name={childComponent.iconName}
|
||||
className="text-primary-active"
|
||||
name={'navigation-panel-right-reveal'}
|
||||
className={classnames(
|
||||
'text-primary-active',
|
||||
side === 'left' && 'transform rotate-180'
|
||||
)}
|
||||
/>
|
||||
<span className="mt-2 text-white text-xs">
|
||||
{t(childComponent.iconLabel)}
|
||||
</span>
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
</div>
|
||||
<div className={classnames('flex flex-col space-y-3 mt-3')}>
|
||||
{_childComponents.map((childComponent, index) => (
|
||||
<Tooltip
|
||||
position={side === 'left' ? 'right' : 'left'}
|
||||
key={index}
|
||||
content={`${childComponent.label}`}
|
||||
className={classnames(
|
||||
'flex items-center',
|
||||
side === 'left' ? 'justify-end ' : 'justify-start '
|
||||
)}
|
||||
>
|
||||
<IconButton
|
||||
id={`${childComponent.name}-btn`}
|
||||
variant="text"
|
||||
color="inherit"
|
||||
size="initial"
|
||||
className="text-primary-active"
|
||||
onClick={() => {
|
||||
setActiveTabIndex(index);
|
||||
setPanelOpen(true);
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name={childComponent.iconName}
|
||||
className="text-primary-active"
|
||||
style={{
|
||||
width: '22px',
|
||||
height: '22px',
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -121,37 +171,75 @@ const SidePanel = ({
|
||||
)}
|
||||
style={style}
|
||||
>
|
||||
{componentOpen ? (
|
||||
{panelOpen ? (
|
||||
<React.Fragment>
|
||||
<div className="px-3 border-b border-secondary-light">
|
||||
{/** Panel Header with Arrow and Close Actions */}
|
||||
<div
|
||||
className={classnames(
|
||||
'px-[10px] bg-primary-dark h-9 cursor-pointer flex shrink-0',
|
||||
tabs.length === 1 && 'mb-1'
|
||||
)}
|
||||
onClick={() => {
|
||||
setPanelOpen(prev => !prev);
|
||||
// slideToActivePanel();
|
||||
}}
|
||||
data-cy={`side-panel-header-${side}`}
|
||||
>
|
||||
<Button
|
||||
variant="text"
|
||||
color="inherit"
|
||||
border="none"
|
||||
rounded="none"
|
||||
onClick={() => {
|
||||
setComponentOpen(null);
|
||||
}}
|
||||
name={childComponent.name}
|
||||
className="flex flex-row items-center px-3 h-12 relative w-full"
|
||||
className="flex flex-row items-center px-3 relative w-full"
|
||||
name={tabs.length === 1 ? `${tabs[activeTabIndex].name}` : ''}
|
||||
>
|
||||
<Icon
|
||||
name={openIconName[side]}
|
||||
name={openStateIconName[side]}
|
||||
className={classnames(
|
||||
'text-primary-active absolute',
|
||||
side === 'left' && 'order-last'
|
||||
)}
|
||||
style={{ ...position[side] }}
|
||||
/>
|
||||
<span className="flex-1 text-primary-active">
|
||||
{t(childComponent.label)}
|
||||
{/* Todo: ass secondary label here */}
|
||||
<span className="text-primary-active">
|
||||
{tabs.length === 1 && tabs[activeTabIndex].label}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
<childComponent.content />
|
||||
{tabs.length > 1 &&
|
||||
_getMoreThanOneTabLayout(
|
||||
swiperRef,
|
||||
setSwiper,
|
||||
prevRef,
|
||||
nextRef,
|
||||
tabs,
|
||||
activeTabIndex,
|
||||
setActiveTabIndex,
|
||||
setPanelOpen
|
||||
)}
|
||||
{/** carousel navigation with the arrows */}
|
||||
{/** only show carousel nav if tabs are more than 3 tabs */}
|
||||
{tabs.length > 3 && (
|
||||
<div className="text-primary-active w-full flex justify-end gap-2 bg-primary-dark py-1 px-2">
|
||||
<button ref={prevRef} className="swiper-button-prev-custom">
|
||||
<Icon
|
||||
name={'icon-prev'}
|
||||
className={classnames('text-primary-active')}
|
||||
/>
|
||||
</button>
|
||||
<button ref={nextRef} className="swiper-button-next-custom">
|
||||
<Icon
|
||||
name={'icon-next'}
|
||||
className={classnames('text-primary-active')}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<ActiveComponent />
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>{getPanelButtons()}</React.Fragment>
|
||||
<React.Fragment>{getCloseStateComponent()}</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -164,15 +252,8 @@ SidePanel.defaultProps = {
|
||||
SidePanel.propTypes = {
|
||||
side: PropTypes.oneOf(['left', 'right']).isRequired,
|
||||
className: PropTypes.string,
|
||||
defaultComponentOpen: PropTypes.string,
|
||||
childComponents: PropTypes.oneOfType([
|
||||
PropTypes.shape({
|
||||
iconName: PropTypes.string.isRequired,
|
||||
iconLabel: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
content: PropTypes.func, // TODO: Should be node, but it keeps complaining?
|
||||
}),
|
||||
activeTabIndex: PropTypes.number,
|
||||
tabs: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
iconName: PropTypes.string.isRequired,
|
||||
@@ -185,4 +266,78 @@ SidePanel.propTypes = {
|
||||
]),
|
||||
};
|
||||
|
||||
function _getMoreThanOneTabLayout(
|
||||
swiperRef: any,
|
||||
setSwiper: React.Dispatch<any>,
|
||||
prevRef: React.MutableRefObject<undefined>,
|
||||
nextRef: React.MutableRefObject<undefined>,
|
||||
tabs: any,
|
||||
activeTabIndex: any,
|
||||
setActiveTabIndex: React.Dispatch<any>,
|
||||
setPanelOpen: React.Dispatch<React.SetStateAction<boolean>>
|
||||
) {
|
||||
return (
|
||||
<div
|
||||
className="collapse-sidebar relative"
|
||||
style={{
|
||||
backgroundColor: '#06081f',
|
||||
}}
|
||||
>
|
||||
<div className="w-full">
|
||||
<Swiper
|
||||
onInit={(core: SwiperCore) => {
|
||||
swiperRef.current = core.el;
|
||||
}}
|
||||
simulateTouch={false}
|
||||
modules={[Navigation, Pagination, Scrollbar, A11y, Controller]}
|
||||
slidesPerView={3}
|
||||
spaceBetween={5}
|
||||
onSwiper={swiper => setSwiper(swiper)}
|
||||
navigation={{
|
||||
prevEl: prevRef?.current,
|
||||
nextEl: nextRef?.current,
|
||||
}}
|
||||
>
|
||||
{tabs.map((obj, index) => (
|
||||
<SwiperSlide key={index}>
|
||||
<div
|
||||
className={classnames(
|
||||
index === activeTabIndex
|
||||
? 'bg-secondary-main text-white'
|
||||
: 'text-aqua-pale',
|
||||
'flex cursor-pointer px-4 py-1 rounded-[4px] flex-col justify-center items-center text-center hover:text-white'
|
||||
)}
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setActiveTabIndex(index);
|
||||
setPanelOpen(true);
|
||||
}}
|
||||
data-cy={`${obj.name}-btn`}
|
||||
>
|
||||
<span>
|
||||
<Icon
|
||||
name={obj.iconName}
|
||||
className={classnames(
|
||||
index === activeTabIndex
|
||||
? 'text-white'
|
||||
: 'text-primary-active'
|
||||
)}
|
||||
style={{
|
||||
width: '22px',
|
||||
height: '22px',
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
<span className="text-[10px] select-none font-medium whitespace-nowrap mt-[5px]">
|
||||
{obj.label}
|
||||
</span>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SidePanel;
|
||||
@@ -0,0 +1,27 @@
|
||||
.swiper-button-next.swiper-button-disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.swiper-button-prev.swiper-button-disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
.swiper-button-prev {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
.swiper-button-next {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.swiper-button-next-custom.swiper-button-disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.swiper-button-prev-custom.swiper-button-disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
@@ -38,45 +38,10 @@
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.sb-closeBtn {
|
||||
text-shadow: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
@apply overflow-hidden opacity-100 rounded-full p-1 bg-white cursor-pointer absolute text-center duration-300 transition-all ease-in-out;
|
||||
}
|
||||
|
||||
.sb-closeBtn:hover {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.sb-closeIcon {
|
||||
@apply w-full relative overflow-hidden h-full block leading-none;
|
||||
}
|
||||
|
||||
.sb-closeIcon:after,
|
||||
.sb-closeIcon:before {
|
||||
content: ' ';
|
||||
height: 2px;
|
||||
width: 12px;
|
||||
@apply duration-300 transition-all ease-in-out block bg-black opacity-100 absolute;
|
||||
}
|
||||
|
||||
.sb-closeIcon:before {
|
||||
left: 4px;
|
||||
top: 3px;
|
||||
transform: rotate(45deg);
|
||||
transform-origin: 0px 50%;
|
||||
}
|
||||
|
||||
.sb-closeIcon:after {
|
||||
right: 3px;
|
||||
top: 5px;
|
||||
transform: rotate(-45deg);
|
||||
transform-origin: calc(100% - 3px) 50%;
|
||||
}
|
||||
|
||||
.sb-title {
|
||||
@apply break-normal text-lg font-bold;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Icon from '../Icon';
|
||||
import IconButton from '../IconButton';
|
||||
|
||||
import SnackbarTypes from './SnackbarTypes';
|
||||
|
||||
@@ -16,10 +18,11 @@ const SnackbarItem = ({ options, onClose }) => {
|
||||
[SnackbarTypes.INFO]: 'bg-primary-active',
|
||||
[SnackbarTypes.WARNING]: 'bg-yellow-600',
|
||||
[SnackbarTypes.SUCCESS]: 'bg-green-600',
|
||||
[SnackbarTypes.ERROR]: 'bg-red-600'
|
||||
[SnackbarTypes.ERROR]: 'bg-red-600',
|
||||
};
|
||||
|
||||
const hidden = 'duration-300 transition-all ease-in-out h-0 opacity-0 pt-0 mb-0 pb-0';
|
||||
const hidden =
|
||||
'duration-300 transition-all ease-in-out h-0 opacity-0 pt-0 mb-0 pb-0';
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -28,9 +31,15 @@ const SnackbarItem = ({ options, onClose }) => {
|
||||
typeClasses[options.type]
|
||||
)}
|
||||
>
|
||||
<span className="sb-closeBtn" onClick={handleClose}>
|
||||
<span className="sb-closeIcon">x</span>
|
||||
</span>
|
||||
{/* <span className="sb-closeBtn" onClick={handleClose}> */}
|
||||
{/* <span className="sb-closeIcon">x</span> */}
|
||||
<div
|
||||
onClick={handleClose}
|
||||
className="w-5 h-5 rounded-full absolute bg-white flex items-center justify-center right-0 top-0 mr-2 mt-2"
|
||||
>
|
||||
<Icon name="close" className="w-4 text-black" />
|
||||
</div>
|
||||
{/* </span> */}
|
||||
{options.title && <div className="sb-title">{options.title}</div>}
|
||||
{options.message && <div className="sb-message">{options.message}</div>}
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ButtonGroup, Button, StudyItem, ThumbnailList } from '../';
|
||||
import { StringNumber } from '../../types';
|
||||
|
||||
const buttonClasses = 'text-white text-base border-none p-2 min-w-18';
|
||||
|
||||
const getTrackedSeries = displaySets => {
|
||||
let trackedSeries = 0;
|
||||
displaySets.forEach(displaySet => {
|
||||
@@ -79,11 +76,7 @@ const StudyBrowser = ({
|
||||
className="flex flex-row items-center justify-center h-16 p-4 border-b w-100 border-secondary-light bg-primary-dark"
|
||||
data-cy={'studyBrowser-panel'}
|
||||
>
|
||||
<ButtonGroup
|
||||
variant="outlined"
|
||||
color="inherit"
|
||||
className="border rounded-md border-secondary-light"
|
||||
>
|
||||
<ButtonGroup variant="outlined" color="secondary" splitBorder={false}>
|
||||
{tabs.map(tab => {
|
||||
const { name, label, studies } = tab;
|
||||
const isActive = activeTabName === name;
|
||||
@@ -91,7 +84,7 @@ const StudyBrowser = ({
|
||||
return (
|
||||
<Button
|
||||
key={name}
|
||||
className={classnames(buttonClasses)}
|
||||
className={'text-white text-base p-2 min-w-18'}
|
||||
size="initial"
|
||||
bgColor={isActive ? 'bg-primary-main' : 'bg-black'}
|
||||
onClick={() => {
|
||||
|
||||
@@ -147,6 +147,8 @@ function _getModalityTooltip(modality) {
|
||||
|
||||
const _modalityTooltips = {
|
||||
SR: 'Structured Report',
|
||||
SEG: 'Segmentation',
|
||||
RT: 'RT Structure Set',
|
||||
};
|
||||
|
||||
export default ThumbnailList;
|
||||
@@ -29,6 +29,7 @@ const Tooltip = ({
|
||||
content,
|
||||
isSticky,
|
||||
position,
|
||||
className,
|
||||
tight,
|
||||
children,
|
||||
isDisabled,
|
||||
@@ -52,7 +53,7 @@ const Tooltip = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative"
|
||||
className={classnames('relative', className)}
|
||||
onMouseOver={handleMouseOver}
|
||||
onFocus={handleMouseOver}
|
||||
onMouseOut={handleMouseOut}
|
||||
@@ -110,6 +111,7 @@ Tooltip.propTypes = {
|
||||
isSticky: PropTypes.bool,
|
||||
tight: PropTypes.bool,
|
||||
children: PropTypes.node.isRequired,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Tooltip;
|
||||
@@ -2,12 +2,12 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ViewportActionBar, Notification } from '../';
|
||||
|
||||
const Viewport = ({ viewportIndex, onSeriesChange, studyData, children }) => {
|
||||
const Viewport = ({ viewportIndex, onArrowsClick, studyData, children }) => {
|
||||
return (
|
||||
<div className="relative flex flex-col h-full">
|
||||
<div className="absolute top-0 left-0 w-full">
|
||||
<ViewportActionBar
|
||||
onSeriesChange={onSeriesChange}
|
||||
onArrowsClick={onArrowsClick}
|
||||
studyData={studyData}
|
||||
/>
|
||||
|
||||
@@ -48,7 +48,7 @@ const Viewport = ({ viewportIndex, onSeriesChange, studyData, children }) => {
|
||||
|
||||
Viewport.propTypes = {
|
||||
viewportIndex: PropTypes.number.isRequired,
|
||||
onSeriesChange: PropTypes.func.isRequired,
|
||||
onArrowsClick: PropTypes.func.isRequired,
|
||||
studyData: PropTypes.shape({
|
||||
label: PropTypes.string.isRequired,
|
||||
isTracked: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -31,7 +31,7 @@ Viewport is a component that renders the Viewport action bar.
|
||||
name="Overview"
|
||||
args={{
|
||||
viewportIndex: 0,
|
||||
onSeriesChange: direction => alert(`Annotation ${direction}`),
|
||||
onArrowsClick: direction => alert(`Annotation ${direction}`),
|
||||
studyData: {
|
||||
label: 'A',
|
||||
isTracked: true,
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
import React from 'react'
|
||||
import ViewportActionBar from './ViewportActionBar'
|
||||
import React from 'react';
|
||||
import ViewportActionBar from './ViewportActionBar';
|
||||
|
||||
export default {
|
||||
component: ViewportActionBar,
|
||||
title: 'Components/ViewportActionBar',
|
||||
}
|
||||
};
|
||||
|
||||
export const Default = () => (
|
||||
<div className="w-1/2 p-4 h-64">
|
||||
<ViewportActionBar
|
||||
onSeriesChange={direction => alert(`Series ${direction}`)}
|
||||
studyData={{
|
||||
label: 'A',
|
||||
isTracked: false,
|
||||
isLocked: false,
|
||||
isRehydratable: true,
|
||||
studyDate: '07-Sep-2010',
|
||||
currentSeries: 1,
|
||||
seriesDescription:
|
||||
'Series description lorem ipsum dolor sit Series description lorem ipsum dolor sit Series description lorem ipsum dolor sit ',
|
||||
modality: 'CT',
|
||||
patientInformation: {
|
||||
patientName: 'Smith, Jane',
|
||||
patientSex: 'F',
|
||||
patientAge: '59',
|
||||
MRN: '10000001',
|
||||
thickness: '2.0mm',
|
||||
spacing: '1.25mm',
|
||||
scanner: 'Aquilion',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
<ViewportActionBar
|
||||
onArrowsClick={direction => alert(`Series ${direction}`)}
|
||||
studyData={{
|
||||
label: 'A',
|
||||
isTracked: false,
|
||||
isLocked: false,
|
||||
isRehydratable: true,
|
||||
studyDate: '07-Sep-2010',
|
||||
currentSeries: 1,
|
||||
seriesDescription:
|
||||
'Series description lorem ipsum dolor sit Series description lorem ipsum dolor sit Series description lorem ipsum dolor sit ',
|
||||
modality: 'CT',
|
||||
patientInformation: {
|
||||
patientName: 'Smith, Jane',
|
||||
patientSex: 'F',
|
||||
patientAge: '59',
|
||||
MRN: '10000001',
|
||||
thickness: '2.0mm',
|
||||
spacing: '1.25mm',
|
||||
scanner: 'Aquilion',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -1,18 +1,10 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { Icon, ButtonGroup, Button, Tooltip, CinePlayer } from '../';
|
||||
import { Icon, ButtonGroup, Button, CinePlayer } from '../';
|
||||
import useOnClickOutside from '../../utils/useOnClickOutside';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PatientInfo from '../PatientInfo';
|
||||
import { StringNumber } from '../../types';
|
||||
|
||||
const classes = {
|
||||
infoHeader: 'text-base text-primary-light',
|
||||
infoText: 'text-base text-white max-w-24 truncate',
|
||||
firstRow: 'flex flex-col',
|
||||
row: 'flex flex-col ml-4',
|
||||
};
|
||||
|
||||
const ViewportActionBar = ({
|
||||
studyData,
|
||||
showNavArrows,
|
||||
@@ -20,27 +12,15 @@ const ViewportActionBar = ({
|
||||
showCine,
|
||||
cineProps,
|
||||
showPatientInfo: patientInfoVisibility,
|
||||
onSeriesChange,
|
||||
onArrowsClick,
|
||||
onDoubleClick,
|
||||
//
|
||||
onPillClick,
|
||||
getStatusComponent,
|
||||
}) => {
|
||||
const [showPatientInfo, setShowPatientInfo] = useState(patientInfoVisibility);
|
||||
|
||||
// TODO -> Remake this component with a bunch of generic slots that can be filled,
|
||||
// Its not generic at all, isTracked etc shouldn't be parts of this component.
|
||||
// It shouldn't care that a tracking mode or SR exists.
|
||||
// Things like the right/left buttons should be made into smaller
|
||||
// Components you can compose.
|
||||
// OHIF-200 ticket.
|
||||
|
||||
const {
|
||||
label,
|
||||
isTracked,
|
||||
isLocked,
|
||||
isRehydratable,
|
||||
useAltStyling,
|
||||
modality,
|
||||
studyDate,
|
||||
currentSeries,
|
||||
seriesDescription,
|
||||
@@ -75,174 +55,10 @@ const ViewportActionBar = ({
|
||||
return () => clickOutsideListener.remove();
|
||||
}, [clickOutsideListener, showPatientInfo]);
|
||||
|
||||
const renderIconStatus = () => {
|
||||
if (modality === 'SR') {
|
||||
// 1 - Incompatible
|
||||
// 2 - Locked
|
||||
// 3 - Rehydratable / Open
|
||||
const state =
|
||||
isRehydratable && !isLocked ? 3 : isRehydratable && isLocked ? 2 : 1;
|
||||
let ToolTipMessage = null;
|
||||
let StatusIcon = null;
|
||||
|
||||
switch (state) {
|
||||
case 1:
|
||||
StatusIcon = () => (
|
||||
<div
|
||||
className="flex items-center justify-center -mr-1 rounded-full"
|
||||
style={{
|
||||
width: '18px',
|
||||
height: '18px',
|
||||
backgroundColor: '#98e5c1',
|
||||
border: 'solid 1.5px #000000',
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name="exclamation"
|
||||
style={{ color: '#000', width: '12px', height: '12px' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
ToolTipMessage = () => (
|
||||
<div>
|
||||
This structured report is not compatible
|
||||
<br />
|
||||
with this application.
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case 2:
|
||||
StatusIcon = () => (
|
||||
<div
|
||||
className="flex items-center justify-center -mr-1 bg-black rounded-full"
|
||||
style={{
|
||||
width: '18px',
|
||||
height: '18px',
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name="lock"
|
||||
style={{ color: '#05D97C', width: '8px', height: '11px' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
ToolTipMessage = () => (
|
||||
<div>
|
||||
This structured report is currently read-only
|
||||
<br />
|
||||
because you are tracking measurements in
|
||||
<br />
|
||||
another viewport.
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case 3:
|
||||
StatusIcon = () => (
|
||||
<div
|
||||
className="flex items-center justify-center -mr-1 bg-white rounded-full group-hover:bg-customblue-200"
|
||||
style={{
|
||||
width: '18px',
|
||||
height: '18px',
|
||||
border: 'solid 1.5px #000000',
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
name="arrow-left"
|
||||
style={{ color: '#000', width: '14px', height: '14px' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
ToolTipMessage = () => <div>Click to restore measurements.</div>;
|
||||
}
|
||||
|
||||
const StatusPill = () => (
|
||||
<div
|
||||
className={classnames(
|
||||
'group relative flex items-center justify-center px-2 rounded-full cursor-default bg-customgreen-100',
|
||||
{
|
||||
'hover:bg-customblue-100': state === 3,
|
||||
'cursor-pointer': state === 3,
|
||||
}
|
||||
)}
|
||||
style={{
|
||||
height: '24px',
|
||||
width: '55px',
|
||||
}}
|
||||
onClick={() => {
|
||||
if (state === 3) {
|
||||
// TODO: Gatsby build failing due
|
||||
// to ESLint's "no-unused-expressions"
|
||||
//onPillClick?.();
|
||||
if (onPillClick) {
|
||||
onPillClick();
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="pr-1 text-lg font-bold leading-none text-black">
|
||||
SR
|
||||
</span>
|
||||
<StatusIcon />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{ToolTipMessage && (
|
||||
<Tooltip content={<ToolTipMessage />} position="bottom-left">
|
||||
<StatusPill />
|
||||
</Tooltip>
|
||||
)}
|
||||
{!ToolTipMessage && <StatusPill />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const trackedIcon = isTracked ? 'tracked' : 'dotted-circle';
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<Tooltip
|
||||
position="bottom-left"
|
||||
content={
|
||||
<div className="flex py-2">
|
||||
<div className="flex pt-1">
|
||||
<Icon name="info-link" className="w-4 text-primary-main" />
|
||||
</div>
|
||||
<div className="flex ml-4">
|
||||
<span className="text-base text-common-light">
|
||||
{isTracked ? (
|
||||
<>
|
||||
Series is
|
||||
<span className="font-bold text-white"> tracked</span> and
|
||||
can be viewed <br /> in the measurement panel
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Measurements for
|
||||
<span className="font-bold text-white"> untracked </span>
|
||||
series <br /> will not be shown in the <br /> measurements
|
||||
panel
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Icon name={trackedIcon} className="w-6 text-primary-light" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const borderColor = useAltStyling ? '#365A6A' : '#1D205A';
|
||||
|
||||
let backgroundColor = '#020424';
|
||||
if (useAltStyling || isTracked) {
|
||||
if (useAltStyling) {
|
||||
backgroundColor = '#031923';
|
||||
}
|
||||
|
||||
@@ -254,11 +70,12 @@ const ViewportActionBar = ({
|
||||
backgroundColor: backgroundColor,
|
||||
}}
|
||||
onDoubleClick={onDoubleClick}
|
||||
onContextMenu={e => e.preventDefault()}
|
||||
>
|
||||
<div className="flex flex-1 grow mt-2 min-w-48">
|
||||
<div className="flex items-center">
|
||||
<span className="mr-2 text-white text-large">{label}</span>
|
||||
{showStatus && renderIconStatus()}
|
||||
{showStatus && getStatusComponent()}
|
||||
</div>
|
||||
<div className="flex flex-col justify-start ml-4">
|
||||
<div className="flex">
|
||||
@@ -288,7 +105,7 @@ const ViewportActionBar = ({
|
||||
size="initial"
|
||||
className="px-2 py-1 bg-black"
|
||||
border="light"
|
||||
onClick={() => onSeriesChange('left')}
|
||||
onClick={() => onArrowsClick('left')}
|
||||
>
|
||||
<Icon name="chevron-left" className="w-4 text-white" />
|
||||
</Button>
|
||||
@@ -296,7 +113,7 @@ const ViewportActionBar = ({
|
||||
size="initial"
|
||||
border="light"
|
||||
className="px-2 py-1 bg-black"
|
||||
onClick={() => onSeriesChange('right')}
|
||||
onClick={() => onArrowsClick('right')}
|
||||
>
|
||||
<Icon name="chevron-right" className="w-4 text-white" />
|
||||
</Button>
|
||||
@@ -326,7 +143,7 @@ const ViewportActionBar = ({
|
||||
};
|
||||
|
||||
ViewportActionBar.propTypes = {
|
||||
onSeriesChange: PropTypes.func.isRequired,
|
||||
onArrowsClick: PropTypes.func.isRequired,
|
||||
showNavArrows: PropTypes.bool,
|
||||
showCine: PropTypes.bool,
|
||||
cineProps: PropTypes.object,
|
||||
@@ -336,12 +153,9 @@ ViewportActionBar.propTypes = {
|
||||
useAltStyling: PropTypes.bool,
|
||||
//
|
||||
label: PropTypes.string.isRequired,
|
||||
isTracked: PropTypes.bool.isRequired,
|
||||
isRehydratable: PropTypes.bool.isRequired,
|
||||
studyDate: PropTypes.string.isRequired,
|
||||
currentSeries: StringNumber.isRequired,
|
||||
seriesDescription: PropTypes.string.isRequired,
|
||||
modality: PropTypes.string.isRequired,
|
||||
patientInformation: PropTypes.shape({
|
||||
patientName: PropTypes.string.isRequired,
|
||||
patientSex: PropTypes.string.isRequired,
|
||||
@@ -352,6 +166,7 @@ ViewportActionBar.propTypes = {
|
||||
scanner: PropTypes.string.isRequired,
|
||||
}),
|
||||
}).isRequired,
|
||||
getStatusComponent: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
ViewportActionBar.defaultProps = {
|
||||
@@ -362,127 +177,4 @@ ViewportActionBar.defaultProps = {
|
||||
showPatientInfo: false,
|
||||
};
|
||||
|
||||
function PatientInfo({
|
||||
patientName,
|
||||
patientSex,
|
||||
patientAge,
|
||||
MRN,
|
||||
thickness,
|
||||
spacing,
|
||||
scanner,
|
||||
isOpen,
|
||||
showPatientInfoRef,
|
||||
}) {
|
||||
const { t } = useTranslation('PatientInfo');
|
||||
|
||||
while (patientAge.charAt(0) === '0') {
|
||||
patientAge = patientAge.substr(1);
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={showPatientInfoRef}>
|
||||
<Tooltip
|
||||
isSticky
|
||||
isDisabled={!isOpen}
|
||||
position="bottom-right"
|
||||
content={
|
||||
isOpen && (
|
||||
<div className="flex py-2">
|
||||
<div className="flex pt-1">
|
||||
<Icon name="info-link" className="w-4 text-primary-main" />
|
||||
</div>
|
||||
<div className="flex flex-col ml-2">
|
||||
<span
|
||||
className="text-base font-bold text-white"
|
||||
title={patientName}
|
||||
>
|
||||
{patientName}
|
||||
</span>
|
||||
<div className="flex pb-4 mt-4 mb-4 border-b border-secondary-main">
|
||||
<div className={classnames(classes.firstRow)}>
|
||||
<span className={classnames(classes.infoHeader)}>
|
||||
{t('Sex')}
|
||||
</span>
|
||||
<span
|
||||
className={classnames(classes.infoText)}
|
||||
title={patientSex}
|
||||
>
|
||||
{patientSex}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classnames(classes.row)}>
|
||||
<span className={classnames(classes.infoHeader)}>
|
||||
{t('Age')}
|
||||
</span>
|
||||
<span
|
||||
className={classnames(classes.infoText)}
|
||||
title={patientAge}
|
||||
>
|
||||
{patientAge}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classnames(classes.row)}>
|
||||
<span className={classnames(classes.infoHeader)}>
|
||||
{t('MRN')}
|
||||
</span>
|
||||
<span className={classnames(classes.infoText)} title={MRN}>
|
||||
{MRN}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<div className={classnames(classes.firstRow)}>
|
||||
<span className={classnames(classes.infoHeader)}>
|
||||
{t('Thickness')}
|
||||
</span>
|
||||
<span
|
||||
className={classnames(classes.infoText)}
|
||||
title={thickness}
|
||||
>
|
||||
{thickness}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classnames(classes.row)}>
|
||||
<span className={classnames(classes.infoHeader)}>
|
||||
{t('Spacing')}
|
||||
</span>
|
||||
<span
|
||||
className={classnames(classes.infoText)}
|
||||
title={spacing}
|
||||
>
|
||||
{spacing}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classnames(classes.row)}>
|
||||
<span className={classnames(classes.infoHeader)}>
|
||||
{t('Scanner')}
|
||||
</span>
|
||||
<span
|
||||
className={classnames(classes.infoText)}
|
||||
title={scanner}
|
||||
>
|
||||
{scanner}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
>
|
||||
<div className="relative flex justify-end cursor-pointer">
|
||||
<div className="relative">
|
||||
<Icon name="profile" className="w-5 text-white" />
|
||||
<Icon
|
||||
name="info-link"
|
||||
className="absolute w-5 text-white bg-black"
|
||||
style={{ right: -7, bottom: -10 }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ViewportActionBar;
|
||||
@@ -7,8 +7,6 @@ import React, {
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import classnames from 'classnames';
|
||||
|
||||
import {
|
||||
Typography,
|
||||
Input,
|
||||
@@ -349,7 +347,7 @@ const ViewportDownloadForm = ({
|
||||
onLabelClick={() => {}}
|
||||
>
|
||||
<Select
|
||||
className="mt-2"
|
||||
className="mt-2 text-white"
|
||||
isClearable={false}
|
||||
value={fileType}
|
||||
data-cy="file-type"
|
||||
@@ -387,7 +385,7 @@ const ViewportDownloadForm = ({
|
||||
<Typography variant="h5">{t('Image preview')}</Typography>
|
||||
{activeViewportElement && (
|
||||
<div
|
||||
className="mx-auto my-0"
|
||||
className="mx-auto my-2"
|
||||
style={{
|
||||
height: viewportElementDimensions.height,
|
||||
width: viewportElementDimensions.width,
|
||||
@@ -404,7 +402,15 @@ const ViewportDownloadForm = ({
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end mt-4">
|
||||
<Button data-cy="cancel-btn" variant="outlined" onClick={onClose}>
|
||||
<Button
|
||||
data-cy="cancel-btn"
|
||||
variant="outlined"
|
||||
size="initial"
|
||||
color="black"
|
||||
border="secondary"
|
||||
onClick={onClose}
|
||||
className="p-2"
|
||||
>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
@@ -64,7 +64,7 @@ function ViewportPane({
|
||||
onScroll={onInteractionHandler}
|
||||
onWheel={onInteractionHandler}
|
||||
className={classnames(
|
||||
'w-full h-full rounded-lg overflow-hidden hover:border-primary-light transition duration-300 group',
|
||||
'w-full h-full rounded-md overflow-hidden hover:border-primary-light transition duration-300 group',
|
||||
{
|
||||
'border-2 border-primary-light': isActive,
|
||||
'border-2 border-transparent': !isActive,
|
||||
@@ -77,7 +77,7 @@ function ViewportPane({
|
||||
>
|
||||
<div
|
||||
className={classnames(
|
||||
'rounded-lg w-full h-full overflow-hidden group-hover:border-transparent',
|
||||
'rounded-md w-full h-full overflow-hidden group-hover:border-transparent',
|
||||
{
|
||||
'border border-transparent': isActive,
|
||||
'border border-secondary-light': !isActive,
|
||||
|
||||
@@ -24,6 +24,7 @@ import NavBar from './NavBar';
|
||||
import Notification from './Notification';
|
||||
import Select from './Select';
|
||||
import SegmentationTable from './SegmentationTable';
|
||||
import SegmentationGroupTable from './SegmentationGroupTable';
|
||||
import SidePanel from './SidePanel';
|
||||
import SplitButton from './SplitButton';
|
||||
import StudyBrowser from './StudyBrowser';
|
||||
@@ -63,6 +64,11 @@ import HotkeyField from './HotkeyField';
|
||||
import Header from './Header';
|
||||
import ImageScrollbar from './ImageScrollbar';
|
||||
import ViewportOverlay from './ViewportOverlay';
|
||||
import InputRange from './InputRange';
|
||||
import InputNumber from './InputNumber';
|
||||
import CheckBox from './CheckBox';
|
||||
import LoadingIndicatorProgress from './LoadingIndicatorProgress';
|
||||
import LoadingIndicator from './LoadingIndicator';
|
||||
|
||||
export {
|
||||
AboutModal,
|
||||
@@ -74,6 +80,7 @@ export {
|
||||
ButtonGroup,
|
||||
ContextMenu,
|
||||
CinePlayer,
|
||||
CheckBox,
|
||||
DateRange,
|
||||
Dialog,
|
||||
Dropdown,
|
||||
@@ -86,18 +93,23 @@ export {
|
||||
Input,
|
||||
InputDateRange,
|
||||
InputGroup,
|
||||
InputRange,
|
||||
InputNumber,
|
||||
InputLabelWrapper,
|
||||
InputMultiSelect,
|
||||
InputText,
|
||||
ImageScrollbar,
|
||||
Label,
|
||||
LayoutSelector,
|
||||
LoadingIndicator,
|
||||
LoadingIndicatorProgress,
|
||||
MeasurementTable,
|
||||
Modal,
|
||||
NavBar,
|
||||
Notification,
|
||||
Select,
|
||||
SegmentationTable,
|
||||
SegmentationGroupTable,
|
||||
SidePanel,
|
||||
SplitButton,
|
||||
StudyBrowser,
|
||||
|
||||
@@ -26,6 +26,7 @@ const DEFAULT_STATE = {
|
||||
},
|
||||
],
|
||||
activeViewportIndex: 0,
|
||||
cachedLayout: {},
|
||||
};
|
||||
|
||||
export const ViewportGridContext = createContext(DEFAULT_STATE);
|
||||
@@ -73,10 +74,15 @@ export function ViewportGridProvider({ children, service }) {
|
||||
viewportLabel: viewportLabels[viewportIndex],
|
||||
};
|
||||
|
||||
return { ...state, ...{ viewports }, cachedLayout: null };
|
||||
return { ...state, ...{ viewports } };
|
||||
}
|
||||
case 'SET_LAYOUT': {
|
||||
const { numCols, numRows, layoutType, layoutOptions } = action.payload;
|
||||
const {
|
||||
numCols,
|
||||
numRows,
|
||||
layoutOptions,
|
||||
layoutType = 'grid',
|
||||
} = action.payload;
|
||||
|
||||
// If empty viewportOptions, we use numRow and numCols to calculate number of viewports
|
||||
const numPanes = layoutOptions.length || numRows * numCols;
|
||||
@@ -119,7 +125,6 @@ export function ViewportGridProvider({ children, service }) {
|
||||
layoutType,
|
||||
viewports,
|
||||
},
|
||||
cachedLayout: null,
|
||||
};
|
||||
}
|
||||
case 'RESET': {
|
||||
@@ -139,13 +144,41 @@ export function ViewportGridProvider({ children, service }) {
|
||||
height: 100,
|
||||
},
|
||||
],
|
||||
cachedLayout: null,
|
||||
cachedLayout: {},
|
||||
};
|
||||
}
|
||||
|
||||
// The SET_CACHE_LAYOUT action can be used for caching a layout
|
||||
// for instance double clicking a viewport to maximize it.
|
||||
// and then restoring the previous layout when the viewport is
|
||||
// double clicked again.
|
||||
case 'SET_CACHED_LAYOUT': {
|
||||
return { ...state, cachedLayout: action.payload };
|
||||
const { cacheId, cachedLayout } = action.payload;
|
||||
|
||||
// deep copy the cachedLayout into the state
|
||||
return {
|
||||
...state,
|
||||
cachedLayout: {
|
||||
...state.cachedLayout,
|
||||
[cacheId]: JSON.parse(JSON.stringify(cachedLayout)),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case 'RESTORE_CACHED_LAYOUT': {
|
||||
const cacheId = action.payload;
|
||||
|
||||
if (!state.cachedLayout[cacheId]) {
|
||||
console.warn(
|
||||
`No cached layout found for cacheId: ${cacheId}. Ignoring...`
|
||||
);
|
||||
return state;
|
||||
}
|
||||
|
||||
const cachedLayout = state.cachedLayout;
|
||||
return { ...state.cachedLayout[cacheId], cachedLayout };
|
||||
}
|
||||
|
||||
case 'SET': {
|
||||
return {
|
||||
...state,
|
||||
@@ -163,7 +196,9 @@ export function ViewportGridProvider({ children, service }) {
|
||||
DEFAULT_STATE
|
||||
);
|
||||
|
||||
const getState = useCallback(() => viewportGridState, [viewportGridState]);
|
||||
const getState = useCallback(() => {
|
||||
return viewportGridState;
|
||||
}, [viewportGridState]);
|
||||
|
||||
const setActiveViewportIndex = useCallback(
|
||||
index => dispatch({ type: 'SET_ACTIVE_VIEWPORT_INDEX', payload: index }),
|
||||
@@ -189,6 +224,15 @@ export function ViewportGridProvider({ children, service }) {
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const setDisplaySetsForViewports = useCallback(
|
||||
viewports => {
|
||||
viewports.forEach(data => {
|
||||
setDisplaySetsForViewport(data);
|
||||
});
|
||||
},
|
||||
[setDisplaySetsForViewport]
|
||||
);
|
||||
|
||||
const setLayout = useCallback(
|
||||
({ layoutType, numRows, numCols, layoutOptions = [] }) =>
|
||||
dispatch({
|
||||
@@ -221,6 +265,16 @@ export function ViewportGridProvider({ children, service }) {
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const restoreCachedLayout = useCallback(
|
||||
cacheId => {
|
||||
dispatch({
|
||||
type: 'RESTORE_CACHED_LAYOUT',
|
||||
payload: cacheId,
|
||||
});
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const set = useCallback(
|
||||
payload =>
|
||||
dispatch({
|
||||
@@ -231,7 +285,7 @@ export function ViewportGridProvider({ children, service }) {
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the implementation of a modal service that can be used by extensions.
|
||||
* Sets the implementation of ViewportGridService that can be used by extensions.
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
@@ -241,9 +295,11 @@ export function ViewportGridProvider({ children, service }) {
|
||||
getState,
|
||||
setActiveViewportIndex,
|
||||
setDisplaySetsForViewport,
|
||||
setDisplaySetsForViewports,
|
||||
setLayout,
|
||||
reset,
|
||||
setCachedLayout,
|
||||
restoreCachedLayout,
|
||||
set,
|
||||
});
|
||||
}
|
||||
@@ -252,18 +308,22 @@ export function ViewportGridProvider({ children, service }) {
|
||||
service,
|
||||
setActiveViewportIndex,
|
||||
setDisplaySetsForViewport,
|
||||
setDisplaySetsForViewports,
|
||||
setLayout,
|
||||
reset,
|
||||
setCachedLayout,
|
||||
restoreCachedLayout,
|
||||
set,
|
||||
]);
|
||||
|
||||
const api = {
|
||||
getState,
|
||||
setActiveViewportIndex,
|
||||
setActiveViewportIndex: index => service.setActiveViewportIndex(index), // run it through the service itself since we want to publish events
|
||||
setDisplaySetsForViewport,
|
||||
setDisplaySetsForViewports,
|
||||
setLayout,
|
||||
setCachedLayout,
|
||||
restoreCachedLayout,
|
||||
reset,
|
||||
set,
|
||||
};
|
||||
|
||||
@@ -61,12 +61,15 @@ export {
|
||||
InputText,
|
||||
Label,
|
||||
LayoutSelector,
|
||||
LoadingIndicator,
|
||||
LoadingIndicatorProgress,
|
||||
MeasurementTable,
|
||||
Modal,
|
||||
NavBar,
|
||||
Notification,
|
||||
Select,
|
||||
SegmentationTable,
|
||||
SegmentationGroupTable,
|
||||
SidePanel,
|
||||
SplitButton,
|
||||
StudyBrowser,
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
/* IMPORT CUSTOM FONT */
|
||||
@import url('https://fonts.googleapis.com/css?family=Lato:100,300,400,500,700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css?family=Inter:100,300,400,500,700&display=swap');
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
|
||||
body {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
/* Truncate 2 lines utility */
|
||||
.truncate-2-lines {
|
||||
@@ -17,3 +19,51 @@
|
||||
height: 2.1rem;
|
||||
}
|
||||
|
||||
.customSelect__menu {
|
||||
background-color: transparent !important;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.css-4ljt47-MenuList {
|
||||
overflow-x: hidden !important;
|
||||
margin-right: 4px;
|
||||
margin-top: 4px;
|
||||
padding-top: 0px !important;
|
||||
}
|
||||
.css-4ljt47-MenuList::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.css-4ljt47-MenuList::-webkit-scrollbar-thumb {
|
||||
background-color: #0944b3;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.customSelect__option:focus {
|
||||
background-color: #0944b391 !important;
|
||||
}
|
||||
.customSelect__option label {
|
||||
color: #d6d6d6;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.css-1n7v3ny-option {
|
||||
background-color: #0944b391 !important;
|
||||
}
|
||||
|
||||
.css-1n7v3ny-option:focus {
|
||||
background-color: #0944b391 !important;
|
||||
}
|
||||
|
||||
.customSelect__option:hover {
|
||||
background-color: #0944b391;
|
||||
}
|
||||
|
||||
.css-26l3qy-menu {
|
||||
border: 2px solid #0944b3;
|
||||
border-radius: 6px;
|
||||
background-color: #151515 !important;
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
module.exports = {
|
||||
// Note: in Tailwind 3.0, JIT will purge unused styles by default
|
||||
// but in development, it is often useful to disable this to see
|
||||
// and try out all the styles that are available.
|
||||
// ...(process.env.NODE_ENV === 'development' && {
|
||||
// safelist: [{ pattern: /.*/ }],
|
||||
// }),
|
||||
content: ['./src/**/*.{jsx,js,ts,tsx,css,mdx}'],
|
||||
theme: {
|
||||
screens: {
|
||||
@@ -167,36 +173,6 @@ module.exports = {
|
||||
900: '#702459',
|
||||
},
|
||||
},
|
||||
spacing: {
|
||||
px: '1px',
|
||||
'0': '0',
|
||||
'1': '0.15rem',
|
||||
'2': '0.5rem',
|
||||
'3': '0.75rem',
|
||||
'4': '1rem',
|
||||
'5': '1.25rem',
|
||||
'6': '1.5rem',
|
||||
'8': '2rem',
|
||||
'10': '2.5rem',
|
||||
'12': '3rem',
|
||||
'14': '3.5rem',
|
||||
'16': '4rem',
|
||||
'18': '4.5rem',
|
||||
'20': '5rem',
|
||||
'24': '6rem',
|
||||
'32': '8rem',
|
||||
'40': '10rem',
|
||||
'48': '12rem',
|
||||
'56': '14rem',
|
||||
'64': '16rem',
|
||||
'72': '18rem',
|
||||
'80': '20rem',
|
||||
'88': '22rem',
|
||||
'96': '24rem',
|
||||
'104': '26rem',
|
||||
'112': '28rem',
|
||||
'250px': '250px',
|
||||
},
|
||||
backgroundColor: theme => theme('colors'),
|
||||
backgroundPosition: {
|
||||
bottom: 'bottom',
|
||||
@@ -218,21 +194,6 @@ module.exports = {
|
||||
...theme('colors'),
|
||||
DEFAULT: theme('colors.gray.300', 'currentColor'),
|
||||
}),
|
||||
borderRadius: {
|
||||
none: '0',
|
||||
sm: '0.125rem',
|
||||
DEFAULT: '0.25rem',
|
||||
md: '0.375rem',
|
||||
lg: '0.5rem',
|
||||
full: '9999px',
|
||||
},
|
||||
borderWidth: {
|
||||
DEFAULT: '1px',
|
||||
'0': '0',
|
||||
'2': '2px',
|
||||
'4': '4px',
|
||||
'8': '8px',
|
||||
},
|
||||
boxShadow: {
|
||||
xs: '0 0 0 1px rgba(0, 0, 0, 0.05)',
|
||||
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
|
||||
@@ -278,50 +239,11 @@ module.exports = {
|
||||
'0': '0',
|
||||
DEFAULT: '1',
|
||||
},
|
||||
fontFamily: {
|
||||
sans: [
|
||||
'Lato',
|
||||
'system-ui',
|
||||
'-apple-system',
|
||||
'BlinkMacSystemFont',
|
||||
'"Segoe UI"',
|
||||
'Roboto',
|
||||
'"Helvetica Neue"',
|
||||
'Arial',
|
||||
'"Noto Sans"',
|
||||
'sans-serif',
|
||||
'"Apple Color Emoji"',
|
||||
'"Segoe UI Emoji"',
|
||||
'"Segoe UI Symbol"',
|
||||
'"Noto Color Emoji"',
|
||||
],
|
||||
serif: ['Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'],
|
||||
mono: [
|
||||
'Menlo',
|
||||
'Monaco',
|
||||
'Consolas',
|
||||
'"Liberation Mono"',
|
||||
'"Courier New"',
|
||||
'monospace',
|
||||
],
|
||||
},
|
||||
fontSize: {
|
||||
xs: '0.65rem',
|
||||
sm: '0.75rem',
|
||||
base: '0.875rem',
|
||||
lg: '1rem',
|
||||
xl: '1.25rem',
|
||||
'2xl': '1.5rem',
|
||||
'3xl': '1.875rem',
|
||||
'4xl': '2.25rem',
|
||||
'5xl': '3rem',
|
||||
'6xl': '4rem',
|
||||
},
|
||||
fontWeight: {
|
||||
hairline: '100',
|
||||
thin: '200',
|
||||
light: '300',
|
||||
normal: '400',
|
||||
normal: '300',
|
||||
medium: '500',
|
||||
semibold: '600',
|
||||
bold: '700',
|
||||
|
||||